<commit_before>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"context"
	"fmt"
	"time"

	"github.com/pkg/errors"
	"github.com/uber-go/tally"
	"github.com/uber/tchannel-go"
	"go.uber.org/zap"
	netContext "golang.org/x/net/context"
)

const (
	logFieldClientID = "clientID"
	// thrift service::method of client thrift spec
	logFieldClientThriftMethod = "clientThriftMethod"
	// the backend service corresponding to the client
	logFieldClientService = "clientService"
	// the method name for a particular client method call
	logFieldClientMethod = "clientMethod"
)

// TChannelClientOption is used when creating a new TChannelClient
type TChannelClientOption struct {
	ServiceName       string
	ClientID          string
	Timeout           time.Duration
	TimeoutPerAttempt time.Duration
	RoutingKey        *string

	// MethodNames is a map from "ThriftService::method" to "ZanzibarMethodName",
	// where ThriftService and method are from the service's Thrift IDL, and
	// ZanzibarMethodName is the public method name exposed on the Zanzibar-generated
	// client, from the zanzibar configuration. For example, if a client named FooClient
	// has a methodMap of map[string]string{"Foo::bar":"Bar"}, then one can do
	// `FooClient.Bar()` to issue a RPC to Thrift service `Foo`'s `bar` method.
	MethodNames map[string]string

	// An alternate subchannel that can optionally be used to make a TChannel call
	// instead; e.g. can allow the service to be overridden when a "X-Zanzibar-Use-Staging"
	// header is present
	AltSubchannelName string

	// the header key that is used together with the request uuid on context to
	// form a header when sending the request to downstream, e.g. "x-request-uuid"
	RequestUUIDHeaderKey string
}

// TChannelClient implements TChannelCaller and makes outgoing Thrift calls.
type TChannelClient struct {
	ClientID string
	Loggers  map[string]*zap.Logger

	ch                *tchannel.Channel
	sc                *tchannel.SubChannel
	scAlt             *tchannel.SubChannel
	serviceName       string
	methodNames       map[string]string
	timeout           time.Duration
	timeoutPerAttempt time.Duration
	routingKey        *string
	metrics           ContextMetrics
	contextExtractor  ContextExtractor

	requestUUIDHeaderKey string
}

// NewTChannelClient is deprecated, use NewTChannelClientContext instead
func NewTChannelClient(
	ch *tchannel.Channel,
	logger *zap.Logger,
	scope tally.Scope,
	contextExtractor ContextExtractor,
	opt *TChannelClientOption,
) *TChannelClient {
	return NewTChannelClientContext(
		ch,
		logger,
		NewContextMetrics(scope),
		contextExtractor,
		opt,
	)
}

// NewTChannelClientContext returns a TChannelClient that makes calls over the given tchannel to the given thrift service.
func NewTChannelClientContext(
	ch *tchannel.Channel,
	logger *zap.Logger,
	metrics ContextMetrics,
	contextExtractor ContextExtractor,
	opt *TChannelClientOption,
) *TChannelClient {
	numMethods := len(opt.MethodNames)
	loggers := make(map[string]*zap.Logger, numMethods)

	for serviceMethod, methodName := range opt.MethodNames {
		loggers[serviceMethod] = logger.With(
			zap.String(logFieldClientID, opt.ClientID),
			zap.String(logFieldClientService, opt.ServiceName),
			zap.String(logFieldClientMethod, methodName),
			zap.String(logFieldClientThriftMethod, serviceMethod),
		)
	}

	client := &TChannelClient{
		ch:                ch,
		sc:                ch.GetSubChannel(opt.ServiceName),
		serviceName:       opt.ServiceName,
		ClientID:          opt.ClientID,
		methodNames:       opt.MethodNames,
		timeout:           opt.Timeout,
		timeoutPerAttempt: opt.TimeoutPerAttempt,
		routingKey:        opt.RoutingKey,
		Loggers:           loggers,
		metrics:           metrics,
		contextExtractor:  contextExtractor,

		requestUUIDHeaderKey: opt.RequestUUIDHeaderKey,
	}
	if opt.AltSubchannelName != "" {
		client.scAlt = ch.GetSubChannel(opt.AltSubchannelName)
	}

	return client
}

// Call makes a RPC call to the given service.
func (c *TChannelClient) Call(
	ctx context.Context,
	thriftService, methodName string,
	reqHeaders map[string]string,
	req, resp RWTStruct,
) (success bool, resHeaders map[string]string, err error) {
	serviceMethod := thriftService + "::" + methodName
	scopeTags := map[string]string{
		scopeTagClient:          c.ClientID,
		scopeTagClientMethod:    methodName,
		scopeTagsTargetService:  c.serviceName,
		scopeTagsTargetEndpoint: serviceMethod,
	}
	ctx = WithScopeTags(ctx, scopeTags)
	call := &tchannelOutboundCall{
		client:        c,
		methodName:    c.methodNames[serviceMethod],
		serviceMethod: serviceMethod,
		reqHeaders:    reqHeaders,
		logger:        c.Loggers[serviceMethod],
		metrics:       c.metrics,
	}

	return c.call(ctx, call, reqHeaders, req, resp, false)
}

// CallThruAltChannel makes a RPC call using a configured alternate channel
func (c *TChannelClient) CallThruAltChannel(
	ctx context.Context,
	thriftService, methodName string,
	reqHeaders map[string]string,
	req, resp RWTStruct,
) (success bool, resHeaders map[string]string, err error) {
	serviceMethod := thriftService + "::" + methodName
	scopeTags := map[string]string{
		scopeTagClient:          c.ClientID,
		scopeTagClientMethod:    methodName,
		scopeTagsTargetService:  c.serviceName,
		scopeTagsTargetEndpoint: serviceMethod,
	}

	ctx = WithScopeTags(ctx, scopeTags)
	call := &tchannelOutboundCall{
		client:        c,
		methodName:    c.methodNames[serviceMethod],
		serviceMethod: serviceMethod,
		reqHeaders:    reqHeaders,
		logger:        c.Loggers[serviceMethod],
		metrics:       c.metrics,
	}

	return c.call(ctx, call, reqHeaders, req, resp, true)
}

func (c *TChannelClient) call(
	ctx context.Context,
	call *tchannelOutboundCall,
	reqHeaders map[string]string,
	req, resp RWTStruct,
	useAltSubchannel bool,
) (success bool, resHeaders map[string]string, err error) {
	defer func() { call.finish(ctx, err) }()
	call.start()

	reqUUID := RequestUUIDFromCtx(ctx)
	if reqUUID != "" {
		if reqHeaders == nil {
			reqHeaders = make(map[string]string)
		}
		reqHeaders[c.requestUUIDHeaderKey] = reqUUID
	}

	retryOpts := tchannel.RetryOptions{
		TimeoutPerAttempt: c.timeoutPerAttempt,
	}
	ctxBuilder := tchannel.NewContextBuilder(c.timeout).
		SetParentContext(ctx).
		SetRetryOptions(&retryOpts)
	if c.routingKey != nil {
		ctxBuilder.SetRoutingKey(*c.routingKey)
	}
	rd := GetRoutingDelegateFromCtx(ctx)
	if rd != "" {
		ctxBuilder.SetRoutingDelegate(rd)
	}

	sk := GetShardKeyFromCtx(ctx)
	if sk != "" {
		fmt.Println("sk="+sk)
		ctxBuilder.SetShardKey(sk)
	}

	ctx, cancel := ctxBuilder.Build()
	defer cancel()

	err = c.ch.RunWithRetry(ctx, func(ctx netContext.Context, rs *tchannel.RequestState) (cerr error) {
		call.resHeaders, call.success = nil, false

		sc := c.sc
		if useAltSubchannel {
			if c.scAlt == nil {
				return errors.Errorf("alternate subchannel not configured for %s", call.client.ClientID)
			}
			sc = c.scAlt
		}

		call.call, cerr = sc.BeginCall(ctx, call.serviceMethod, &tchannel.CallOptions{
			Format:       tchannel.Thrift,
			RequestState: rs,
		})
		if cerr != nil {
			return errors.Wrapf(
				err, "Could not begin outbound %s.%s (%s %s) request",
				call.client.ClientID, call.methodName, call.client.serviceName, call.serviceMethod,
			)
		}

		// trace request
		reqHeaders = tchannel.InjectOutboundSpan(call.call.Response(), reqHeaders)

		if cerr := call.writeReqHeaders(reqHeaders); cerr != nil {
			return cerr
		}
		if cerr := call.writeReqBody(ctx, req); cerr != nil {
			return cerr
		}

		response := call.call.Response()
		if cerr = call.readResHeaders(response); cerr != nil {
			return cerr
		}
		if cerr = call.readResBody(response, resp); cerr != nil {
			return cerr
		}

		return cerr
	})

	if err != nil {
		// Do not wrap system errors.
		if _, ok := err.(tchannel.SystemError); ok {
			return call.success, nil, err
		}
		return call.success, nil, errors.Wrapf(
			err, "Could not make outbound %s.%s (%s %s) response",
			call.client.ClientID, call.methodName, call.client.serviceName, call.serviceMethod,
		)
	}

	return call.success, call.resHeaders, err
}
<commit_msg>Set ShardKey from the context if its available<commit_after>// Copyright (c) 2019 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package zanzibar

import (
	"context"
	"fmt"
	"time"

	"github.com/pkg/errors"
	"github.com/uber-go/tally"
	"github.com/uber/tchannel-go"
	"go.uber.org/zap"
	netContext "golang.org/x/net/context"
)

const (
	logFieldClientID = "clientID"
	// thrift service::method of client thrift spec
	logFieldClientThriftMethod = "clientThriftMethod"
	// the backend service corresponding to the client
	logFieldClientService = "clientService"
	// the method name for a particular client method call
	logFieldClientMethod = "clientMethod"
)

// TChannelClientOption is used when creating a new TChannelClient
type TChannelClientOption struct {
	ServiceName       string
	ClientID          string
	Timeout           time.Duration
	TimeoutPerAttempt time.Duration
	RoutingKey        *string

	// MethodNames is a map from "ThriftService::method" to "ZanzibarMethodName",
	// where ThriftService and method are from the service's Thrift IDL, and
	// ZanzibarMethodName is the public method name exposed on the Zanzibar-generated
	// client, from the zanzibar configuration. For example, if a client named FooClient
	// has a methodMap of map[string]string{"Foo::bar":"Bar"}, then one can do
	// `FooClient.Bar()` to issue a RPC to Thrift service `Foo`'s `bar` method.
	MethodNames map[string]string

	// An alternate subchannel that can optionally be used to make a TChannel call
	// instead; e.g. can allow the service to be overridden when a "X-Zanzibar-Use-Staging"
	// header is present
	AltSubchannelName string

	// the header key that is used together with the request uuid on context to
	// form a header when sending the request to downstream, e.g. "x-request-uuid"
	RequestUUIDHeaderKey string
}

// TChannelClient implements TChannelCaller and makes outgoing Thrift calls.
type TChannelClient struct {
	ClientID string
	Loggers  map[string]*zap.Logger

	ch                *tchannel.Channel
	sc                *tchannel.SubChannel
	scAlt             *tchannel.SubChannel
	serviceName       string
	methodNames       map[string]string
	timeout           time.Duration
	timeoutPerAttempt time.Duration
	routingKey        *string
	metrics           ContextMetrics
	contextExtractor  ContextExtractor

	requestUUIDHeaderKey string
}

// NewTChannelClient is deprecated, use NewTChannelClientContext instead
func NewTChannelClient(
	ch *tchannel.Channel,
	logger *zap.Logger,
	scope tally.Scope,
	contextExtractor ContextExtractor,
	opt *TChannelClientOption,
) *TChannelClient {
	return NewTChannelClientContext(
		ch,
		logger,
		NewContextMetrics(scope),
		contextExtractor,
		opt,
	)
}

// NewTChannelClientContext returns a TChannelClient that makes calls over the given tchannel to the given thrift service.
func NewTChannelClientContext(
	ch *tchannel.Channel,
	logger *zap.Logger,
	metrics ContextMetrics,
	contextExtractor ContextExtractor,
	opt *TChannelClientOption,
) *TChannelClient {
	numMethods := len(opt.MethodNames)
	loggers := make(map[string]*zap.Logger, numMethods)

	for serviceMethod, methodName := range opt.MethodNames {
		loggers[serviceMethod] = logger.With(
			zap.String(logFieldClientID, opt.ClientID),
			zap.String(logFieldClientService, opt.ServiceName),
			zap.String(logFieldClientMethod, methodName),
			zap.String(logFieldClientThriftMethod, serviceMethod),
		)
	}

	client := &TChannelClient{
		ch:                ch,
		sc:                ch.GetSubChannel(opt.ServiceName),
		serviceName:       opt.ServiceName,
		ClientID:          opt.ClientID,
		methodNames:       opt.MethodNames,
		timeout:           opt.Timeout,
		timeoutPerAttempt: opt.TimeoutPerAttempt,
		routingKey:        opt.RoutingKey,
		Loggers:           loggers,
		metrics:           metrics,
		contextExtractor:  contextExtractor,

		requestUUIDHeaderKey: opt.RequestUUIDHeaderKey,
	}
	if opt.AltSubchannelName != "" {
		client.scAlt = ch.GetSubChannel(opt.AltSubchannelName)
	}

	return client
}

// Call makes a RPC call to the given service.
func (c *TChannelClient) Call(
	ctx context.Context,
	thriftService, methodName string,
	reqHeaders map[string]string,
	req, resp RWTStruct,
) (success bool, resHeaders map[string]string, err error) {
	serviceMethod := thriftService + "::" + methodName
	scopeTags := map[string]string{
		scopeTagClient:          c.ClientID,
		scopeTagClientMethod:    methodName,
		scopeTagsTargetService:  c.serviceName,
		scopeTagsTargetEndpoint: serviceMethod,
	}
	ctx = WithScopeTags(ctx, scopeTags)
	call := &tchannelOutboundCall{
		client:        c,
		methodName:    c.methodNames[serviceMethod],
		serviceMethod: serviceMethod,
		reqHeaders:    reqHeaders,
		logger:        c.Loggers[serviceMethod],
		metrics:       c.metrics,
	}

	return c.call(ctx, call, reqHeaders, req, resp, false)
}

// CallThruAltChannel makes a RPC call using a configured alternate channel
func (c *TChannelClient) CallThruAltChannel(
	ctx context.Context,
	thriftService, methodName string,
	reqHeaders map[string]string,
	req, resp RWTStruct,
) (success bool, resHeaders map[string]string, err error) {
	serviceMethod := thriftService + "::" + methodName
	scopeTags := map[string]string{
		scopeTagClient:          c.ClientID,
		scopeTagClientMethod:    methodName,
		scopeTagsTargetService:  c.serviceName,
		scopeTagsTargetEndpoint: serviceMethod,
	}

	ctx = WithScopeTags(ctx, scopeTags)
	call := &tchannelOutboundCall{
		client:        c,
		methodName:    c.methodNames[serviceMethod],
		serviceMethod: serviceMethod,
		reqHeaders:    reqHeaders,
		logger:        c.Loggers[serviceMethod],
		metrics:       c.metrics,
	}

	return c.call(ctx, call, reqHeaders, req, resp, true)
}

func (c *TChannelClient) call(
	ctx context.Context,
	call *tchannelOutboundCall,
	reqHeaders map[string]string,
	req, resp RWTStruct,
	useAltSubchannel bool,
) (success bool, resHeaders map[string]string, err error) {
	defer func() { call.finish(ctx, err) }()
	call.start()

	reqUUID := RequestUUIDFromCtx(ctx)
	if reqUUID != "" {
		if reqHeaders == nil {
			reqHeaders = make(map[string]string)
		}
		reqHeaders[c.requestUUIDHeaderKey] = reqUUID
	}

	retryOpts := tchannel.RetryOptions{
		TimeoutPerAttempt: c.timeoutPerAttempt,
	}
	ctxBuilder := tchannel.NewContextBuilder(c.timeout).
		SetParentContext(ctx).
		SetRetryOptions(&retryOpts)
	if c.routingKey != nil {
		ctxBuilder.SetRoutingKey(*c.routingKey)
	}
	rd := GetRoutingDelegateFromCtx(ctx)
	if rd != "" {
		ctxBuilder.SetRoutingDelegate(rd)
	}

	sk := GetShardKeyFromCtx(ctx)
	if sk != "" {
		fmt.Println("sk=" + sk)
		ctxBuilder.SetShardKey(sk)
	}

	ctx, cancel := ctxBuilder.Build()
	defer cancel()

	err = c.ch.RunWithRetry(ctx, func(ctx netContext.Context, rs *tchannel.RequestState) (cerr error) {
		call.resHeaders, call.success = nil, false

		sc := c.sc
		if useAltSubchannel {
			if c.scAlt == nil {
				return errors.Errorf("alternate subchannel not configured for %s", call.client.ClientID)
			}
			sc = c.scAlt
		}

		call.call, cerr = sc.BeginCall(ctx, call.serviceMethod, &tchannel.CallOptions{
			Format:       tchannel.Thrift,
			RequestState: rs,
		})
		if cerr != nil {
			return errors.Wrapf(
				err, "Could not begin outbound %s.%s (%s %s) request",
				call.client.ClientID, call.methodName, call.client.serviceName, call.serviceMethod,
			)
		}

		// trace request
		reqHeaders = tchannel.InjectOutboundSpan(call.call.Response(), reqHeaders)

		if cerr := call.writeReqHeaders(reqHeaders); cerr != nil {
			return cerr
		}
		if cerr := call.writeReqBody(ctx, req); cerr != nil {
			return cerr
		}

		response := call.call.Response()
		if cerr = call.readResHeaders(response); cerr != nil {
			return cerr
		}
		if cerr = call.readResBody(response, resp); cerr != nil {
			return cerr
		}

		return cerr
	})

	if err != nil {
		// Do not wrap system errors.
		if _, ok := err.(tchannel.SystemError); ok {
			return call.success, nil, err
		}
		return call.success, nil, errors.Wrapf(
			err, "Could not make outbound %s.%s (%s %s) response",
			call.client.ClientID, call.methodName, call.client.serviceName, call.serviceMethod,
		)
	}

	return call.success, call.resHeaders, err
}
<|endoftext|>


<commit_before>package signals

import (
	"encoding/gob"
)

func init() {
	gob.Register(Modulated{})
	gob.Register(Composite{})
	gob.Register(Stacked{})
}

// Modulated is a PeriodicLimitedSignal, generated by multiplying together Signal(s).(Signal's can be PeriodicLimitedSignal's, so this can be hierarchical.)
// Multiplication scales so that, unitY*unitY=unitY.
// Modulated's MaxX() comes from the smallest contstituent MaxX(), (0 if none of the contained Signals are LimitedSignals.)
// Modulated's Period() comes from its first member.
// As with 'AND' logic, all sources have to be unitY (at a particular x) for Modulated to be unitY, whereas, ANY Signal at zero will generate a Modulated of zero.
type Modulated []Signal

func (c Modulated) property(p x) (total y) {
	total = unitY
	for _, s := range c {
		l := s.property(p)
		switch l {
		case 0:
			total = 0
			break
		case unitY:
			continue
		default:
			//total = (total / Halfy) * (l / Halfy)*2
			total = (total >> halfyBits) * (l >> halfyBits) * 2
		}
	}
	return
}

func (c Modulated) Period() (period x) {
	// TODO needs to be longest period and all constituents but only when the shorter are multiples of it.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the smallest Max X of the constituents.
func (c Modulated) MaxX() (min x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmin := sls.MaxX(); newmin > 0 && (min == 0 || newmin < min) {
				min = newmin
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewModulated(c ...Signal) Modulated {
	return Modulated(c)
}

// Composite is a PeriodicLimitedSignal, generated by adding together Signal(s). (PeriodicLimitedSignal's are Signal's so this can be hierarchical.)
// Composite's MaxX() comes from the largest contstituent MaxX(), (0 if none of the contained Signals are LimitedSignals.)
// Composite's Period() comes from its first member.
// As with 'OR' logic, all sources have to be zero (at a particular x) for Composite to be zero.
type Composite []Signal

func (c Composite) property(p x) (total y) {
	for _, s := range c {
		total += s.property(p)
	}
	return
}

func (c Composite) Period() (period x) {
	// TODO could helpfully be the longest period and all constituents but only when the shorter are multiples of it.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the largest Max X of the constituents.
func (c Composite) MaxX() (max x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmax := sls.MaxX(); newmax > max {
				max = newmax
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewComposite(c ...Signal) Composite {
	return Composite(c)
}

// Same as Composite except that Stacked scales down by the number of signals, making it impossible to exceed unitY.
type Stacked []Signal

func (c Stacked) property(p x) (total y) {
	for _, s := range c {
		total += s.property(p) / y(len(c))
	}
	return
}

func (c Stacked) Period() (period x) {
	// TODO needs to be longest period and all constituents but only when the shorter are multiples of it.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the largest Max X of the constituents.
func (c Stacked) MaxX() (max x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmax := sls.MaxX(); newmax > max {
				max = newmax
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewStacked(c ...Signal) Stacked {
	return Stacked(c)
}

// Sequenced is a LimitedSignal, generated by appending together LimitedSignal(s).
type Sequenced []LimitedSignal

func (c Sequenced) property(p x) y {
	for _, s := range c {
		if l:=s.MaxX();p-l<0{
			return s.property(p)
		}else{
			p -= l
		}
	}
	return 0
}

// sum of all MaxX's in slice.
func (c Sequenced) MaxX() (max x) {
	for _, s := range c {
		max+=s.MaxX()
	}
	return
}

func NewSequence(c ...LimitedSignal) Sequenced {
	return Sequenced(c)
}

// Converters to promote slices of interfaces, needed when using variadic parameters called using a slice since go doesn't automatically promote a narrow interface inside the slice to be able to use a broader interface.
// for example: without these you couldn't use a slice of LimitedSignal's in a variadic call to a func requiring Signal's. (when you can use separate LimitedSignal's in the same call.)

// converts to []Signal
func PromoteToSignals(s interface{}) []Signal {
	var out []Signal
	switch st := s.(type) {
	case []Signal:
		return st
	case []LimitedSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	case []PeriodicLimitedSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	case []PeriodicSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	}
	return out
}

// converts to []LimitedSignal
func PromoteToLimitedSignals(s interface{}) []LimitedSignal {
	var out []LimitedSignal
	switch st := s.(type) {
	case []LimitedSignal:
		return st
	case []PeriodicLimitedSignal:
		out = make([]LimitedSignal, len(st))
		for i := range out {
			out[i] = st[i].(LimitedSignal)
		}
	}
	return out
}

// converts to []PeriodicSignal
func PromoteToPeriodicSignals(s interface{}) []PeriodicSignal {
	var out []PeriodicSignal
	switch st := s.(type) {
	case []PeriodicSignal:
		return st
	case []PeriodicLimitedSignal:
		out = make([]PeriodicSignal, len(st))
		for i := range out {
			out[i] = st[i].(PeriodicSignal)
		}
	}
	return out
}

<commit_msg>comment<commit_after>package signals

import (
	"encoding/gob"
)

func init() {
	gob.Register(Modulated{})
	gob.Register(Composite{})
	gob.Register(Stacked{})
}

// Modulated is a PeriodicLimitedSignal, generated by multiplying together Signal(s).(Signal's can be PeriodicLimitedSignal's, so this can be hierarchical.)
// Multiplication scales so that, unitY*unitY=unitY.
// Modulated's MaxX() comes from the smallest contstituent MaxX(), (0 if none of the contained Signals are LimitedSignals.)
// Modulated's Period() comes from its first member.
// As with 'AND' logic, all sources have to be unitY (at a particular x) for Modulated to be unitY, whereas, ANY Signal at zero will generate a Modulated of zero.
type Modulated []Signal

func (c Modulated) property(p x) (total y) {
	total = unitY
	for _, s := range c {
		l := s.property(p)
		switch l {
		case 0:
			total = 0
			break
		case unitY:
			continue
		default:
			//total = (total / Halfy) * (l / Halfy)*2
			total = (total >> halfyBits) * (l >> halfyBits) * 2
		}
	}
	return
}

func (c Modulated) Period() (period x) {
	// TODO could helpfully be the shortest period and any constituent.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the smallest Max X of the constituents.
func (c Modulated) MaxX() (min x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmin := sls.MaxX(); newmin > 0 && (min == 0 || newmin < min) {
				min = newmin
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewModulated(c ...Signal) Modulated {
	return Modulated(c)
}

// Composite is a PeriodicLimitedSignal, generated by adding together Signal(s). (PeriodicLimitedSignal's are Signal's so this can be hierarchical.)
// Composite's MaxX() comes from the largest contstituent MaxX(), (0 if none of the contained Signals are LimitedSignals.)
// Composite's Period() comes from its first member.
// As with 'OR' logic, all sources have to be zero (at a particular x) for Composite to be zero.
type Composite []Signal

func (c Composite) property(p x) (total y) {
	for _, s := range c {
		total += s.property(p)
	}
	return
}

func (c Composite) Period() (period x) {
	// TODO could be longest period muliple common to all constituents.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the largest Max X of the constituents.
func (c Composite) MaxX() (max x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmax := sls.MaxX(); newmax > max {
				max = newmax
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewComposite(c ...Signal) Composite {
	return Composite(c)
}

// Same as Composite except that Stacked scales down by the number of signals, making it impossible to exceed unitY.
type Stacked []Signal

func (c Stacked) property(p x) (total y) {
	for _, s := range c {
		total += s.property(p) / y(len(c))
	}
	return
}

func (c Stacked) Period() (period x) {
	// TODO could be longest period muliple common to all constituents.
	if len(c) > 0 {
		if s, ok := c[0].(PeriodicSignal); ok {
			return s.Period()
		}
	}
	return
}

// the largest Max X of the constituents.
func (c Stacked) MaxX() (max x) {
	for _, s := range c {
		if sls, ok := s.(LimitedSignal); ok {
			if newmax := sls.MaxX(); newmax > max {
				max = newmax
			}
		}
	}
	return
}

// helper to enable generation from another slice.
// will in general need to use a slice interface promoter function.
func NewStacked(c ...Signal) Stacked {
	return Stacked(c)
}

// Sequenced is a LimitedSignal, generated by appending together LimitedSignal(s).
type Sequenced []LimitedSignal

func (c Sequenced) property(p x) y {
	for _, s := range c {
		if l:=s.MaxX();p-l<0{
			return s.property(p)
		}else{
			p -= l
		}
	}
	return 0
}

// sum of all MaxX's in slice.
func (c Sequenced) MaxX() (max x) {
	for _, s := range c {
		max+=s.MaxX()
	}
	return
}

func NewSequence(c ...LimitedSignal) Sequenced {
	return Sequenced(c)
}

// Converters to promote slices of interfaces, needed when using variadic parameters called using a slice since go doesn't automatically promote a narrow interface inside the slice to be able to use a broader interface.
// for example: without these you couldn't use a slice of LimitedSignal's in a variadic call to a func requiring Signal's. (when you can use separate LimitedSignal's in the same call.)

// converts to []Signal
func PromoteToSignals(s interface{}) []Signal {
	var out []Signal
	switch st := s.(type) {
	case []Signal:
		return st
	case []LimitedSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	case []PeriodicLimitedSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	case []PeriodicSignal:
		out = make([]Signal, len(st))
		for i := range out {
			out[i] = st[i].(Signal)
		}
	}
	return out
}

// converts to []LimitedSignal
func PromoteToLimitedSignals(s interface{}) []LimitedSignal {
	var out []LimitedSignal
	switch st := s.(type) {
	case []LimitedSignal:
		return st
	case []PeriodicLimitedSignal:
		out = make([]LimitedSignal, len(st))
		for i := range out {
			out[i] = st[i].(LimitedSignal)
		}
	}
	return out
}

// converts to []PeriodicSignal
func PromoteToPeriodicSignals(s interface{}) []PeriodicSignal {
	var out []PeriodicSignal
	switch st := s.(type) {
	case []PeriodicSignal:
		return st
	case []PeriodicLimitedSignal:
		out = make([]PeriodicSignal, len(st))
		for i := range out {
			out[i] = st[i].(PeriodicSignal)
		}
	}
	return out
}

<|endoftext|>


<commit_before>package main

import (
	"bufio"
	log "code.google.com/p/log4go"
	"github.com/Terry-Mao/goim/define"
	"github.com/Terry-Mao/goim/libs/io/ioutil"
	"net"
	"sync"
	"time"
)

const (
	maxPackLen    = 1 << 10
	rawHeaderLen  = int16(16)
	packLenSize   = 4
	headerLenSize = 2
	maxPackIntBuf = 4
)

// InitTCP listen all tcp.bind and start accept connections.
func InitTCP() (err error) {
	var (
		listener *net.TCPListener
		addr     *net.TCPAddr
	)
	for _, bind := range Conf.TCPBind {
		if addr, err = net.ResolveTCPAddr("tcp4", bind); err != nil {
			log.Error("net.ResolveTCPAddr(\"tcp4\", \"%s\") error(%v)", bind, err)
			return
		}
		if listener, err = net.ListenTCP("tcp4", addr); err != nil {
			log.Error("net.ListenTCP(\"tcp4\", \"%s\") error(%v)", bind, err)
			return
		}
		log.Debug("start tcp listen: \"%s\"", bind)
		// split N core accept
		for i := 0; i < Conf.MaxProc; i++ {
			go acceptTCP(DefaultServer, listener)
		}
	}
	return
}

// Accept accepts connections on the listener and serves requests
// for each incoming connection.  Accept blocks; the caller typically
// invokes it in a go statement.
func acceptTCP(server *Server, lis *net.TCPListener) {
	var (
		conn *net.TCPConn
		err  error
		r    int
	)
	for {
		if conn, err = lis.AcceptTCP(); err != nil {
			// if listener close then return
			log.Error("listener.Accept(\"%s\") error(%v)", lis.Addr().String(), err)
			return
		}
		if err = conn.SetKeepAlive(Conf.TCPKeepalive); err != nil {
			log.Error("conn.SetKeepAlive() error(%v)", err)
			return
		}
		if err = conn.SetReadBuffer(Conf.TCPSndbuf); err != nil {
			log.Error("conn.SetReadBuffer() error(%v)", err)
			return
		}
		if err = conn.SetWriteBuffer(Conf.TCPRcvbuf); err != nil {
			log.Error("conn.SetWriteBuffer() error(%v)", err)
			return
		}
		go serveTCP(server, conn, r)
		if r++; r == maxInt {
			r = 0
		}
	}
}

func serveTCP(server *Server, conn *net.TCPConn, r int) {
	var (
		// bufpool
		rrp = server.round.Reader(r) // reader
		wrp = server.round.Writer(r) // writer
		// timer
		tr = server.round.Timer(r)
		// buf
		rr = NewBufioReaderSize(rrp, conn, Conf.ReadBufSize)  // reader buf
		wr = NewBufioWriterSize(wrp, conn, Conf.WriteBufSize) // writer buf
		// ip addr
		lAddr = conn.LocalAddr().String()
		rAddr = conn.RemoteAddr().String()
	)
	log.Debug("start tcp serve \"%s\" with \"%s\"", lAddr, rAddr)
	server.serveTCP(conn, rrp, wrp, rr, wr, tr)
}

func (server *Server) serveTCP(conn *net.TCPConn, rrp, wrp *sync.Pool, rr *bufio.Reader, wr *bufio.Writer, tr *Timer) {
	var (
		b   *Bucket
		p   *Proto
		key string
		hb  time.Duration // heartbeat
		err error
		trd *TimerData
		ch  = NewChannel(Conf.CliProto, Conf.SvrProto, define.NoRoom)
	)
	// auth
	if trd, err = tr.Add(Conf.HandshakeTimeout, conn); err != nil {
		log.Error("handshake: timer.Add() error(%v)", err)
	} else {
		if key, hb, err = server.authTCP(rr, wr, ch); err != nil {
			log.Error("handshake: server.auth error(%v)", err)
		}
		//deltimer
		tr.Del(trd)
	}
	// failed
	if err != nil {
		if err = conn.Close(); err != nil {
			log.Error("handshake: conn.Close() error(%v)", err)
		}
		PutBufioReader(rrp, rr)
		return
	}
	// register key->channel
	b = server.Bucket(key)
	b.Put(key, ch)
	// hanshake ok start dispatch goroutine
	go server.dispatchTCP(conn, wrp, wr, ch, hb, tr)
	for {
		// fetch a proto from channel free list
		if p, err = ch.CliProto.Set(); err != nil {
			log.Error("%s fetch client proto error(%v)", key, err)
			goto failed
		}
		// parse request protocol
		if err = server.readTCPRequest(rr, p); err != nil {
			log.Error("%s read client request error(%v)", key, err)
			goto failed
		}
		// send to writer
		ch.CliProto.SetAdv()
		ch.Signal()
	}
failed:
	// dialog finish
	// may call twice
	if err = conn.Close(); err != nil {
		log.Error("reader: conn.Close() error(%v)", err)
	}
	PutBufioReader(rrp, rr)
	b.Del(key)
	log.Debug("wake up dispatch goroutine")
	ch.Finish()
	if err = server.operator.Disconnect(key, ch.RoomId); err != nil {
		log.Error("%s operator do disconnect error(%v)", key, err)
	}
	log.Debug("%s serverconn goroutine exit", key)
	return
}

// dispatch accepts connections on the listener and serves requests
// for each incoming connection.  dispatch blocks; the caller typically
// invokes it in a go statement.
func (server *Server) dispatchTCP(conn *net.TCPConn, wrp *sync.Pool, wr *bufio.Writer, ch *Channel, hb time.Duration, tr *Timer) {
	var (
		p   *Proto
		err error
		trd *TimerData
	)
	log.Debug("start dispatch goroutine")
	if trd, err = tr.Add(hb, conn); err != nil {
		log.Error("dispatch: timer.Add() error(%v)", err)
		goto failed
	}
	for {
		if !ch.Ready() {
			goto failed
		}
		// fetch message from clibox(client send)
		for {
			if p, err = ch.CliProto.Get(); err != nil {
				break
			}
			if p.Operation == define.OP_HEARTBEAT {
				// Use a previous timer value if difference between it and a new
				// value is less than TIMER_LAZY_DELAY milliseconds: this allows
				// to minimize the minheap operations for fast connections.
				if !trd.Lazy(hb) {
					tr.Del(trd)
					if trd, err = tr.Add(hb, conn); err != nil {
						log.Error("dispatch: timer.Add() error(%v)", err)
						goto failed
					}
				}
				// heartbeat
				p.Body = nil
				p.Operation = define.OP_HEARTBEAT_REPLY
			} else {
				// process message
				if err = server.operator.Operate(p); err != nil {
					log.Error("operator.Operate() error(%v)", err)
					goto failed
				}
			}
			if err = server.writeTCPResponse(wr, p); err != nil {
				log.Error("server.writeTCPResponse() error(%v)", err)
				goto failed
			}
			ch.CliProto.GetAdv()
		}
		// fetch message from svrbox(server send)
		for {
			if p, err = ch.SvrProto.Get(); err != nil {
				log.Warn("ch.SvrProto.Get() error(%v)", err)
				break
			}
			// just forward the message
			if err = server.writeTCPResponse(wr, p); err != nil {
				log.Error("server.writeTCPResponse() error(%v)", err)
				goto failed
			}
			ch.SvrProto.GetAdv()
		}
	}
failed:
	// wake reader up
	if err = conn.Close(); err != nil {
		log.Warn("conn.Close() error(%v)", err)
	}
	// deltimer
	tr.Del(trd)
	PutBufioWriter(wrp, wr)
	log.Debug("dispatch goroutine exit")
	return
}

// auth for goim handshake with client, use rsa & aes.
func (server *Server) authTCP(rr *bufio.Reader, wr *bufio.Writer, ch *Channel) (subKey string, heartbeat time.Duration, err error) {
	var p *Proto
	// WARN
	// don't adv the cli proto, after auth simply discard it.
	if p, err = ch.CliProto.Set(); err != nil {
		return
	}
	if err = server.readTCPRequest(rr, p); err != nil {
		return
	}
	if p.Operation != define.OP_AUTH {
		log.Warn("auth operation not valid: %d", p.Operation)
		err = ErrOperation
		return
	}
	if subKey, ch.RoomId, heartbeat, err = server.operator.Connect(p); err != nil {
		log.Error("operator.Connect error(%v)", err)
		return
	}
	p.Body = nil
	p.Operation = define.OP_AUTH_REPLY
	if err = server.writeTCPResponse(wr, p); err != nil {
		log.Error("[%s] server.sendTCPResponse() error(%v)", subKey, err)
	}
	return
}

// readRequest
func (server *Server) readTCPRequest(rr *bufio.Reader, proto *Proto) (err error) {
	var (
		packLen   int32
		headerLen int16
		bodyLen   int
	)
	if packLen, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("packLen: %d", packLen)
	}
	if packLen > maxPackLen {
		return ErrProtoPackLen
	}
	if headerLen, err = ioutil.ReadBigEndianInt16(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("headerLen: %d", headerLen)
	}
	if headerLen != rawHeaderLen {
		return ErrProtoHeaderLen
	}
	if proto.Ver, err = ioutil.ReadBigEndianInt16(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("protoVer: %d", proto.Ver)
	}
	if proto.Operation, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("operation: %d", proto.Operation)
	}
	if proto.SeqId, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("seqId: %d", proto.SeqId)
	}
	bodyLen = int(packLen - int32(headerLen))
	if Conf.Debug {
		log.Debug("read body len: %d", bodyLen)
	}
	if bodyLen > 0 {
		// TODO reuse buf
		proto.Body = make([]byte, bodyLen)
		if err = ioutil.ReadAll(rr, proto.Body); err != nil {
			log.Error("body: ReadAll() error(%v)", err)
			return
		}
	} else {
		proto.Body = nil
	}
	if Conf.Debug {
		log.Debug("read proto: %v", proto)
	}
	return
}

// sendResponse send resp to client, sendResponse must be goroutine safe.
func (server *Server) writeTCPResponse(wr *bufio.Writer, proto *Proto) (err error) {
	log.Debug("write proto: %v", proto)
	if err = ioutil.WriteBigEndianInt32(wr, int32(rawHeaderLen)+int32(len(proto.Body))); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt16(wr, rawHeaderLen); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt16(wr, proto.Ver); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt32(wr, proto.Operation); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt32(wr, proto.SeqId); err != nil {
		return
	}
	if proto.Body != nil {
		if _, err = wr.Write(proto.Body); err != nil {
			return
		}
	}
	if err = wr.Flush(); err != nil {
		log.Error("tcp wr.Flush() error(%v)", err)
	}
	proto.Reset()
	return
}
<commit_msg>improve performance<commit_after>package main

import (
	"bufio"
	log "code.google.com/p/log4go"
	"github.com/Terry-Mao/goim/define"
	"github.com/Terry-Mao/goim/libs/io/ioutil"
	"net"
	"sync"
	"time"
)

const (
	maxPackLen    = 1 << 10
	rawHeaderLen  = int16(16)
	packLenSize   = 4
	headerLenSize = 2
	maxPackIntBuf = 4
)

// InitTCP listen all tcp.bind and start accept connections.
func InitTCP() (err error) {
	var (
		listener *net.TCPListener
		addr     *net.TCPAddr
	)
	for _, bind := range Conf.TCPBind {
		if addr, err = net.ResolveTCPAddr("tcp4", bind); err != nil {
			log.Error("net.ResolveTCPAddr(\"tcp4\", \"%s\") error(%v)", bind, err)
			return
		}
		if listener, err = net.ListenTCP("tcp4", addr); err != nil {
			log.Error("net.ListenTCP(\"tcp4\", \"%s\") error(%v)", bind, err)
			return
		}
		log.Debug("start tcp listen: \"%s\"", bind)
		// split N core accept
		for i := 0; i < Conf.MaxProc; i++ {
			go acceptTCP(DefaultServer, listener)
		}
	}
	return
}

// Accept accepts connections on the listener and serves requests
// for each incoming connection.  Accept blocks; the caller typically
// invokes it in a go statement.
func acceptTCP(server *Server, lis *net.TCPListener) {
	var (
		conn *net.TCPConn
		err  error
		r    int
	)
	for {
		if conn, err = lis.AcceptTCP(); err != nil {
			// if listener close then return
			log.Error("listener.Accept(\"%s\") error(%v)", lis.Addr().String(), err)
			return
		}
		if err = conn.SetKeepAlive(Conf.TCPKeepalive); err != nil {
			log.Error("conn.SetKeepAlive() error(%v)", err)
			return
		}
		if err = conn.SetReadBuffer(Conf.TCPSndbuf); err != nil {
			log.Error("conn.SetReadBuffer() error(%v)", err)
			return
		}
		if err = conn.SetWriteBuffer(Conf.TCPRcvbuf); err != nil {
			log.Error("conn.SetWriteBuffer() error(%v)", err)
			return
		}
		go serveTCP(server, conn, r)
		if r++; r == maxInt {
			r = 0
		}
	}
}

func serveTCP(server *Server, conn *net.TCPConn, r int) {
	var (
		// bufpool
		rrp = server.round.Reader(r) // reader
		wrp = server.round.Writer(r) // writer
		// timer
		tr = server.round.Timer(r)
		// buf
		rr = NewBufioReaderSize(rrp, conn, Conf.ReadBufSize)  // reader buf
		wr = NewBufioWriterSize(wrp, conn, Conf.WriteBufSize) // writer buf
		// ip addr
		lAddr = conn.LocalAddr().String()
		rAddr = conn.RemoteAddr().String()
	)
	log.Debug("start tcp serve \"%s\" with \"%s\"", lAddr, rAddr)
	server.serveTCP(conn, rrp, wrp, rr, wr, tr)
}

func (server *Server) serveTCP(conn *net.TCPConn, rrp, wrp *sync.Pool, rr *bufio.Reader, wr *bufio.Writer, tr *Timer) {
	var (
		b   *Bucket
		p   *Proto
		key string
		hb  time.Duration // heartbeat
		err error
		trd *TimerData
		ch  = NewChannel(Conf.CliProto, Conf.SvrProto, define.NoRoom)
	)
	// auth
	if trd, err = tr.Add(Conf.HandshakeTimeout, conn); err != nil {
		log.Error("handshake: timer.Add() error(%v)", err)
	} else {
		if key, hb, err = server.authTCP(rr, wr, ch); err != nil {
			log.Error("handshake: server.auth error(%v)", err)
		}
		//deltimer
		tr.Del(trd)
	}
	// failed
	if err != nil {
		if err = conn.Close(); err != nil {
			log.Error("handshake: conn.Close() error(%v)", err)
		}
		PutBufioReader(rrp, rr)
		return
	}
	// register key->channel
	b = server.Bucket(key)
	b.Put(key, ch)
	// hanshake ok start dispatch goroutine
	go server.dispatchTCP(conn, wrp, wr, ch, hb, tr)
	for {
		// fetch a proto from channel free list
		if p, err = ch.CliProto.Set(); err != nil {
			// if full, simply close connection
			log.Error("%s fetch client proto error(%v)", key, err)
			goto failed
		}
		// parse request protocol
		if err = server.readTCPRequest(rr, p); err != nil {
			log.Error("%s read client request error(%v)", key, err)
			goto failed
		}
		// send to writer
		ch.CliProto.SetAdv()
		ch.Signal()
	}
failed:
	// dialog finish
	// may call twice
	if err = conn.Close(); err != nil {
		log.Error("reader: conn.Close() error(%v)", err)
	}
	PutBufioReader(rrp, rr)
	b.Del(key)
	log.Debug("wake up dispatch goroutine")
	ch.Finish()
	if err = server.operator.Disconnect(key, ch.RoomId); err != nil {
		log.Error("%s operator do disconnect error(%v)", key, err)
	}
	log.Debug("%s serverconn goroutine exit", key)
	return
}

// dispatch accepts connections on the listener and serves requests
// for each incoming connection.  dispatch blocks; the caller typically
// invokes it in a go statement.
func (server *Server) dispatchTCP(conn *net.TCPConn, wrp *sync.Pool, wr *bufio.Writer, ch *Channel, hb time.Duration, tr *Timer) {
	var (
		p   *Proto
		err error
		trd *TimerData
	)
	log.Debug("start dispatch goroutine")
	if trd, err = tr.Add(hb, conn); err != nil {
		log.Error("dispatch: timer.Add() error(%v)", err)
		goto failed
	}
	for {
		if !ch.Ready() {
			goto failed
		}
		// fetch message from clibox(client send)
		for {
			if p, err = ch.CliProto.Get(); err != nil {
				// must be empty error
				break
			}
			if p.Operation == define.OP_HEARTBEAT {
				// Use a previous timer value if difference between it and a new
				// value is less than TIMER_LAZY_DELAY milliseconds: this allows
				// to minimize the minheap operations for fast connections.
				if !trd.Lazy(hb) {
					tr.Del(trd)
					if trd, err = tr.Add(hb, conn); err != nil {
						log.Error("dispatch: timer.Add() error(%v)", err)
						goto failed
					}
				}
				// heartbeat
				p.Body = nil
				p.Operation = define.OP_HEARTBEAT_REPLY
			} else {
				// process message
				if err = server.operator.Operate(p); err != nil {
					log.Error("operator.Operate() error(%v)", err)
					goto failed
				}
			}
			if err = server.writeTCPResponse(wr, p); err != nil {
				log.Error("server.writeTCPResponse() error(%v)", err)
				goto failed
			}
			ch.CliProto.GetAdv()
		}
		// fetch message from svrbox(server send)
		for {
			if p, err = ch.SvrProto.Get(); err != nil {
				// must be empty error
				break
			}
			// just forward the message
			if err = server.writeTCPResponse(wr, p); err != nil {
				log.Error("server.writeTCPResponse() error(%v)", err)
				goto failed
			}
			ch.SvrProto.GetAdv()
		}
	}
failed:
	// wake reader up
	if err = conn.Close(); err != nil {
		log.Warn("conn.Close() error(%v)", err)
	}
	// deltimer
	tr.Del(trd)
	PutBufioWriter(wrp, wr)
	log.Debug("dispatch goroutine exit")
	return
}

// auth for goim handshake with client, use rsa & aes.
func (server *Server) authTCP(rr *bufio.Reader, wr *bufio.Writer, ch *Channel) (subKey string, heartbeat time.Duration, err error) {
	var p *Proto
	// WARN
	// don't adv the cli proto, after auth simply discard it.
	if p, err = ch.CliProto.Set(); err != nil {
		return
	}
	if err = server.readTCPRequest(rr, p); err != nil {
		return
	}
	if p.Operation != define.OP_AUTH {
		log.Warn("auth operation not valid: %d", p.Operation)
		err = ErrOperation
		return
	}
	if subKey, ch.RoomId, heartbeat, err = server.operator.Connect(p); err != nil {
		log.Error("operator.Connect error(%v)", err)
		return
	}
	p.Body = nil
	p.Operation = define.OP_AUTH_REPLY
	if err = server.writeTCPResponse(wr, p); err != nil {
		log.Error("[%s] server.sendTCPResponse() error(%v)", subKey, err)
	}
	return
}

// readRequest
func (server *Server) readTCPRequest(rr *bufio.Reader, proto *Proto) (err error) {
	var (
		packLen   int32
		headerLen int16
		bodyLen   int
	)
	if packLen, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("packLen: %d", packLen)
	}
	if packLen > maxPackLen {
		return ErrProtoPackLen
	}
	if headerLen, err = ioutil.ReadBigEndianInt16(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("headerLen: %d", headerLen)
	}
	if headerLen != rawHeaderLen {
		return ErrProtoHeaderLen
	}
	if proto.Ver, err = ioutil.ReadBigEndianInt16(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("protoVer: %d", proto.Ver)
	}
	if proto.Operation, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("operation: %d", proto.Operation)
	}
	if proto.SeqId, err = ioutil.ReadBigEndianInt32(rr); err != nil {
		return
	}
	if Conf.Debug {
		log.Debug("seqId: %d", proto.SeqId)
	}
	bodyLen = int(packLen - int32(headerLen))
	if Conf.Debug {
		log.Debug("read body len: %d", bodyLen)
	}
	if bodyLen > 0 {
		// TODO reuse buf
		proto.Body = make([]byte, bodyLen)
		if err = ioutil.ReadAll(rr, proto.Body); err != nil {
			log.Error("body: ReadAll() error(%v)", err)
			return
		}
	} else {
		proto.Body = nil
	}
	if Conf.Debug {
		log.Debug("read proto: %v", proto)
	}
	return
}

// sendResponse send resp to client, sendResponse must be goroutine safe.
func (server *Server) writeTCPResponse(wr *bufio.Writer, proto *Proto) (err error) {
	if debug {
		log.Debug("write proto: %v", proto)
	}
	if err = ioutil.WriteBigEndianInt32(wr, int32(rawHeaderLen)+int32(len(proto.Body))); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt16(wr, rawHeaderLen); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt16(wr, proto.Ver); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt32(wr, proto.Operation); err != nil {
		return
	}
	if err = ioutil.WriteBigEndianInt32(wr, proto.SeqId); err != nil {
		return
	}
	if proto.Body != nil {
		if _, err = wr.Write(proto.Body); err != nil {
			return
		}
	}
	if err = wr.Flush(); err != nil {
		log.Error("tcp wr.Flush() error(%v)", err)
	}
	proto.Reset()
	return
}
<|endoftext|>


<commit_before>package unit

import (
	"sort"
	"strconv"
	"sync"
)

// Uniter is an interface representing a type that can be converted
// to a unit.
type Uniter interface {
	Unit() *Unit
}

// Dimension is a type representing an SI base dimension or other
// orthogonal dimension. If a new dimension is desired for a
// domain-specific problem, NewDimension should be used. Integers
// should never be cast as type dimension
//	// Good: Create a package constant with an init function
//	var MyDimension unit.Dimension
//	init(){
//		MyDimension = NewDimension("my")
//	}
//	main(){
//		var := MyDimension(28.2)
//	}
type Dimension int

const (
	reserved Dimension = iota
	// SI Base Units
	CurrentDim Dimension
	LengthDim
	LuminousIntensityDim
	MassDim
	TemperatureDim
	TimeDim
	// Start of other SI Units
	AngleDim             // e.g. radians
	lastPackageDimension // Used in create dimension
)

var lastCreatedDimension Dimension = lastPackageDimension
var dimensionToSymbol map[Dimension]string = make(map[Dimension]string) // for printing
var symbolToDimension map[string]Dimension = make(map[string]Dimension) // for guaranteeing there aren't two identical symbols

// TODO: Should we actually reserve "common" SI unit symbols ("N", "J", etc.) so there isn't confusion
// TODO: If we have a fancier ParseUnit, maybe the 'reserved' symbols should be a different map
// 		map[string]string which says how they go?
func init() {
	dimensionToSymbol[CurrentDim] = "A"
	symbolToDimension["A"] = CurrentDim
	dimensionToSymbol[LengthDim] = "m"
	symbolToDimension["m"] = LengthDim
	dimensionToSymbol[LuminousIntensityDim] = "cd"
	symbolToDimension["cd"] = LuminousIntensityDim
	dimensionToSymbol[MassDim] = "kg"
	symbolToDimension["kg"] = MassDim
	dimensionToSymbol[TemperatureDim] = "K"
	symbolToDimension["K"] = TemperatureDim
	dimensionToSymbol[TimeDim] = "s"
	symbolToDimension["s"] = TimeDim
	dimensionToSymbol[AngleDim] = "rad"
	symbolToDimension["rad"] = AngleDim

	// Reserve common SI symbols
	// base units
	symbolToDimension["mol"] = reserved
	// prefixes
	symbolToDimension["Y"] = reserved
	symbolToDimension["Z"] = reserved
	symbolToDimension["E"] = reserved
	symbolToDimension["P"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["G"] = reserved
	symbolToDimension["M"] = reserved
	symbolToDimension["k"] = reserved
	symbolToDimension["h"] = reserved
	symbolToDimension["da"] = reserved
	symbolToDimension["d"] = reserved
	symbolToDimension["c"] = reserved
	symbolToDimension["m"] = reserved
	symbolToDimension["μ"] = reserved
	symbolToDimension["n"] = reserved
	symbolToDimension["p"] = reserved
	symbolToDimension["f"] = reserved
	symbolToDimension["a"] = reserved
	symbolToDimension["z"] = reserved
	symbolToDimension["y"] = reserved
	// SI Derived units with special symbols
	symbolToDimension["sr"] = reserved
	symbolToDimension["F"] = reserved
	symbolToDimension["C"] = reserved
	symbolToDimension["S"] = reserved
	symbolToDimension["H"] = reserved
	symbolToDimension["V"] = reserved
	symbolToDimension["Ω"] = reserved
	symbolToDimension["J"] = reserved
	symbolToDimension["N"] = reserved
	symbolToDimension["Hz"] = reserved
	symbolToDimension["lx"] = reserved
	symbolToDimension["lm"] = reserved
	symbolToDimension["Wb"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["W"] = reserved
	symbolToDimension["Pa"] = reserved
	symbolToDimension["Bq"] = reserved
	symbolToDimension["Gy"] = reserved
	symbolToDimension["Sv"] = reserved
	symbolToDimension["kat"] = reserved
	// Units in use with SI
	symbolToDimension["ha"] = reserved
	symbolToDimension["L"] = reserved
	symbolToDimension["l"] = reserved
	// Units in Use Temporarily with SI
	symbolToDimension["bar"] = reserved
	symbolToDimension["b"] = reserved
	symbolToDimension["Ci"] = reserved
	symbolToDimension["R"] = reserved
	symbolToDimension["rd"] = reserved
	symbolToDimension["rem"] = reserved
}

// Dimensions represent the dimensionality of the unit in powers
// of that dimension. If a key is not present, the power of that
// dimension is zero. Dimensions is used in conjuction with NewUnit
type Dimensions map[Dimension]int

var newUnitMutex *sync.Mutex = &sync.Mutex{} // so there is no race condition for dimension

// NewDimension returns a new dimension variable which will have a
// unique representation across packages to prevent accidental overlap.
// The input string represents a symbol name which will be used for printing
// Unit types. This symbol may not overlap with any of the SI base units
// or other symbols of common use in SI ("kg", "J", "μ", etc.). A list of
// such symbols can be found at http://lamar.colostate.edu/~hillger/basic.htm or
// by consulting the package source. Furthermore, the provided symbol is also
// forbidden from overlapping with other packages calling NewDimension. NewDimension
// is expecting to be used only during initialization, and as such it will panic
// if the symbol matching an existing symbol
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. Please see the package-level
// documentation for further explanation
func NewDimension(symbol string) Dimension {
	newUnitMutex.Lock()
	defer newUnitMutex.Unlock()
	lastCreatedDimension++
	_, ok := symbolToDimension[symbol]
	if ok {
		panic("unit: dimension string " + symbol + " already used")
	}
	dimensionToSymbol[lastCreatedDimension] = symbol
	symbolToDimension[symbol] = lastCreatedDimension
	return lastCreatedDimension
}

// Unit is a type a value with generic SI units. Most useful for
// translating between dimensions, for example, by multiplying
// an acceleration with a mass to get a force. Please see the
// package documentation for further explanation.
type Unit struct {
	dimensions map[Dimension]int // Map for custom dimensions
	value      float64
}

// NewUnit creates a new variable of type Unit which has the value
// specified by value and the dimensions specified by the
// base units struct. The value is always in SI Units.
//
// Example: To create an acceleration of 3 m/s^2, one could do
// myvar := CreateUnit(3.0, &Dimensions{unit.LengthDim: 1, unit.TimeDim: -2})
func NewUnit(value float64, d Dimensions) *Unit {
	u := &Unit{
		dimensions: make(map[Dimension]int),
	}
	for key, val := range d {
		u.dimensions[key] = val
	}
	u.value = value
	return u
}

// DimensionsMatch checks if the dimensions of two Uniters are the same
func DimensionsMatch(a, b Uniter) bool {
	aUnit := a.Unit()
	bUnit := b.Unit()
	if len(aUnit.dimensions) != len(bUnit.dimensions) {
		return false
	}
	for key, val := range aUnit.dimensions {
		if bUnit.dimensions[key] != val {
			return false
		}
	}
	return true
}

// Add adds the function argument to the reciever. Panics if the units of
// the receiver and the argument don't match.
func (u *Unit) Add(uniter Uniter) *Unit {
	a := uniter.Unit()
	if !DimensionsMatch(u, a) {
		panic("Attempted to add the values of two units whose dimensions do not match.")
	}
	u.value += a.value
	return u
}

// Unit implements the Uniter interface
func (u *Unit) Unit() *Unit {
	return u
}

// Mul multiply the receiver by the input changing the dimensions
// of the receiver as appropriate. The input is not changed
func (u *Unit) Mul(uniter Uniter) *Unit {
	a := uniter.Unit()
	for key, val := range a.dimensions {
		u.dimensions[key] += val
	}
	u.value *= a.value
	return u
}

// Div divides the receiver by the argument changing the
// dimensions of the receiver as appropriate
func (u *Unit) Div(uniter Uniter) *Unit {
	a := uniter.Unit()
	u.value /= a.value
	for key, val := range a.dimensions {
		u.dimensions[key] -= val
	}
	return u
}

// Value return the raw value of the unit as a float64. Use of this
// method is, in general, not recommended, though it can be useful
// for printing. Instead, the FromUnit type of a specific
// dimension should be used to guarantee dimension consistency
func (u *Unit) Value() float64 {
	return u.value
}

type symbolString struct {
	symbol string
	pow    int
}

type unitPrinters []symbolString

func (u unitPrinters) Len() int {
	return len(u)
}

func (u unitPrinters) Less(i, j int) bool {
	return u[i].symbol < u[j].symbol
}

func (u unitPrinters) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// String makes Unit satisfy the stringer interface. The unit is printed
// using strconv.FormatFloat(unit.value, 'e', -1, 64), with dimensions
// appended. If the power if the dimension is not zero or one,
// symbol^power is appended, if the power is one, just the symbol is appended
// and if the power is zero, nothing is appended. Dimensions are appended
// in order by symbol name.
func (u Unit) String() string {
	str := strconv.FormatFloat(u.value, 'e', -1, 64)
	// Map iterates randomly, but print should be in a fixed order. Can't use
	// dimension number, because for user-defined dimension that number may
	// not be fixed from run to run.
	data := make(unitPrinters, 0, 10)
	for dimension, power := range u.dimensions {
		if power != 0 {
			data = append(data, symbolString{dimensionToSymbol[dimension], power})
		}
	}
	sort.Sort(data)
	for _, s := range data {
		str += " " + s.symbol
		if s.pow != 1 {
			str += "^" + strconv.Itoa(s.pow)
		}
	}
	return str
}
<commit_msg>better comment in dimension constants<commit_after>package unit

import (
	"sort"
	"strconv"
	"sync"
)

// Uniter is an interface representing a type that can be converted
// to a unit.
type Uniter interface {
	Unit() *Unit
}

// Dimension is a type representing an SI base dimension or other
// orthogonal dimension. If a new dimension is desired for a
// domain-specific problem, NewDimension should be used. Integers
// should never be cast as type dimension
//	// Good: Create a package constant with an init function
//	var MyDimension unit.Dimension
//	init(){
//		MyDimension = NewDimension("my")
//	}
//	main(){
//		var := MyDimension(28.2)
//	}
type Dimension int

const (
	reserved Dimension = iota
	// SI Base Units
	CurrentDim Dimension
	LengthDim
	LuminousIntensityDim
	MassDim
	TemperatureDim
	TimeDim
	// Other common SI Dimensions
	AngleDim             // e.g. radians
	lastPackageDimension // Used in create dimension
)

var lastCreatedDimension Dimension = lastPackageDimension
var dimensionToSymbol map[Dimension]string = make(map[Dimension]string) // for printing
var symbolToDimension map[string]Dimension = make(map[string]Dimension) // for guaranteeing there aren't two identical symbols

// TODO: Should we actually reserve "common" SI unit symbols ("N", "J", etc.) so there isn't confusion
// TODO: If we have a fancier ParseUnit, maybe the 'reserved' symbols should be a different map
// 		map[string]string which says how they go?
func init() {
	dimensionToSymbol[CurrentDim] = "A"
	symbolToDimension["A"] = CurrentDim
	dimensionToSymbol[LengthDim] = "m"
	symbolToDimension["m"] = LengthDim
	dimensionToSymbol[LuminousIntensityDim] = "cd"
	symbolToDimension["cd"] = LuminousIntensityDim
	dimensionToSymbol[MassDim] = "kg"
	symbolToDimension["kg"] = MassDim
	dimensionToSymbol[TemperatureDim] = "K"
	symbolToDimension["K"] = TemperatureDim
	dimensionToSymbol[TimeDim] = "s"
	symbolToDimension["s"] = TimeDim
	dimensionToSymbol[AngleDim] = "rad"
	symbolToDimension["rad"] = AngleDim

	// Reserve common SI symbols
	// base units
	symbolToDimension["mol"] = reserved
	// prefixes
	symbolToDimension["Y"] = reserved
	symbolToDimension["Z"] = reserved
	symbolToDimension["E"] = reserved
	symbolToDimension["P"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["G"] = reserved
	symbolToDimension["M"] = reserved
	symbolToDimension["k"] = reserved
	symbolToDimension["h"] = reserved
	symbolToDimension["da"] = reserved
	symbolToDimension["d"] = reserved
	symbolToDimension["c"] = reserved
	symbolToDimension["m"] = reserved
	symbolToDimension["μ"] = reserved
	symbolToDimension["n"] = reserved
	symbolToDimension["p"] = reserved
	symbolToDimension["f"] = reserved
	symbolToDimension["a"] = reserved
	symbolToDimension["z"] = reserved
	symbolToDimension["y"] = reserved
	// SI Derived units with special symbols
	symbolToDimension["sr"] = reserved
	symbolToDimension["F"] = reserved
	symbolToDimension["C"] = reserved
	symbolToDimension["S"] = reserved
	symbolToDimension["H"] = reserved
	symbolToDimension["V"] = reserved
	symbolToDimension["Ω"] = reserved
	symbolToDimension["J"] = reserved
	symbolToDimension["N"] = reserved
	symbolToDimension["Hz"] = reserved
	symbolToDimension["lx"] = reserved
	symbolToDimension["lm"] = reserved
	symbolToDimension["Wb"] = reserved
	symbolToDimension["T"] = reserved
	symbolToDimension["W"] = reserved
	symbolToDimension["Pa"] = reserved
	symbolToDimension["Bq"] = reserved
	symbolToDimension["Gy"] = reserved
	symbolToDimension["Sv"] = reserved
	symbolToDimension["kat"] = reserved
	// Units in use with SI
	symbolToDimension["ha"] = reserved
	symbolToDimension["L"] = reserved
	symbolToDimension["l"] = reserved
	// Units in Use Temporarily with SI
	symbolToDimension["bar"] = reserved
	symbolToDimension["b"] = reserved
	symbolToDimension["Ci"] = reserved
	symbolToDimension["R"] = reserved
	symbolToDimension["rd"] = reserved
	symbolToDimension["rem"] = reserved
}

// Dimensions represent the dimensionality of the unit in powers
// of that dimension. If a key is not present, the power of that
// dimension is zero. Dimensions is used in conjuction with NewUnit
type Dimensions map[Dimension]int

var newUnitMutex *sync.Mutex = &sync.Mutex{} // so there is no race condition for dimension

// NewDimension returns a new dimension variable which will have a
// unique representation across packages to prevent accidental overlap.
// The input string represents a symbol name which will be used for printing
// Unit types. This symbol may not overlap with any of the SI base units
// or other symbols of common use in SI ("kg", "J", "μ", etc.). A list of
// such symbols can be found at http://lamar.colostate.edu/~hillger/basic.htm or
// by consulting the package source. Furthermore, the provided symbol is also
// forbidden from overlapping with other packages calling NewDimension. NewDimension
// is expecting to be used only during initialization, and as such it will panic
// if the symbol matching an existing symbol
// NewDimension should only be called for unit types that are actually orthogonal
// to the base dimensions defined in this package. Please see the package-level
// documentation for further explanation
func NewDimension(symbol string) Dimension {
	newUnitMutex.Lock()
	defer newUnitMutex.Unlock()
	lastCreatedDimension++
	_, ok := symbolToDimension[symbol]
	if ok {
		panic("unit: dimension string " + symbol + " already used")
	}
	dimensionToSymbol[lastCreatedDimension] = symbol
	symbolToDimension[symbol] = lastCreatedDimension
	return lastCreatedDimension
}

// Unit is a type a value with generic SI units. Most useful for
// translating between dimensions, for example, by multiplying
// an acceleration with a mass to get a force. Please see the
// package documentation for further explanation.
type Unit struct {
	dimensions map[Dimension]int // Map for custom dimensions
	value      float64
}

// NewUnit creates a new variable of type Unit which has the value
// specified by value and the dimensions specified by the
// base units struct. The value is always in SI Units.
//
// Example: To create an acceleration of 3 m/s^2, one could do
// myvar := CreateUnit(3.0, &Dimensions{unit.LengthDim: 1, unit.TimeDim: -2})
func NewUnit(value float64, d Dimensions) *Unit {
	u := &Unit{
		dimensions: make(map[Dimension]int),
	}
	for key, val := range d {
		u.dimensions[key] = val
	}
	u.value = value
	return u
}

// DimensionsMatch checks if the dimensions of two Uniters are the same
func DimensionsMatch(a, b Uniter) bool {
	aUnit := a.Unit()
	bUnit := b.Unit()
	if len(aUnit.dimensions) != len(bUnit.dimensions) {
		return false
	}
	for key, val := range aUnit.dimensions {
		if bUnit.dimensions[key] != val {
			return false
		}
	}
	return true
}

// Add adds the function argument to the reciever. Panics if the units of
// the receiver and the argument don't match.
func (u *Unit) Add(uniter Uniter) *Unit {
	a := uniter.Unit()
	if !DimensionsMatch(u, a) {
		panic("Attempted to add the values of two units whose dimensions do not match.")
	}
	u.value += a.value
	return u
}

// Unit implements the Uniter interface
func (u *Unit) Unit() *Unit {
	return u
}

// Mul multiply the receiver by the input changing the dimensions
// of the receiver as appropriate. The input is not changed
func (u *Unit) Mul(uniter Uniter) *Unit {
	a := uniter.Unit()
	for key, val := range a.dimensions {
		u.dimensions[key] += val
	}
	u.value *= a.value
	return u
}

// Div divides the receiver by the argument changing the
// dimensions of the receiver as appropriate
func (u *Unit) Div(uniter Uniter) *Unit {
	a := uniter.Unit()
	u.value /= a.value
	for key, val := range a.dimensions {
		u.dimensions[key] -= val
	}
	return u
}

// Value return the raw value of the unit as a float64. Use of this
// method is, in general, not recommended, though it can be useful
// for printing. Instead, the FromUnit type of a specific
// dimension should be used to guarantee dimension consistency
func (u *Unit) Value() float64 {
	return u.value
}

type symbolString struct {
	symbol string
	pow    int
}

type unitPrinters []symbolString

func (u unitPrinters) Len() int {
	return len(u)
}

func (u unitPrinters) Less(i, j int) bool {
	return u[i].symbol < u[j].symbol
}

func (u unitPrinters) Swap(i, j int) {
	u[i], u[j] = u[j], u[i]
}

// String makes Unit satisfy the stringer interface. The unit is printed
// using strconv.FormatFloat(unit.value, 'e', -1, 64), with dimensions
// appended. If the power if the dimension is not zero or one,
// symbol^power is appended, if the power is one, just the symbol is appended
// and if the power is zero, nothing is appended. Dimensions are appended
// in order by symbol name.
func (u Unit) String() string {
	str := strconv.FormatFloat(u.value, 'e', -1, 64)
	// Map iterates randomly, but print should be in a fixed order. Can't use
	// dimension number, because for user-defined dimension that number may
	// not be fixed from run to run.
	data := make(unitPrinters, 0, 10)
	for dimension, power := range u.dimensions {
		if power != 0 {
			data = append(data, symbolString{dimensionToSymbol[dimension], power})
		}
	}
	sort.Sort(data)
	for _, s := range data {
		str += " " + s.symbol
		if s.pow != 1 {
			str += "^" + strconv.Itoa(s.pow)
		}
	}
	return str
}
<|endoftext|>


<commit_before>package erbrenderer

const templateEvaluationContextRb = `
# Based on common/properties/template_evaluation_context.rb
require "ostruct"
require "json"
require "erb"
require "yaml"

class Hash
  def recursive_merge!(other)
    self.merge!(other) do |_, old_value, new_value|
      if old_value.class == Hash && new_value.class == Hash
        old_value.recursive_merge!(new_value)
      else
        new_value
      end
    end
    self
  end
end

class TemplateEvaluationContext
  attr_reader :name, :index
  attr_reader :properties, :raw_properties
  attr_reader :spec

  def initialize(spec)
    @name = spec["job"]["name"] if spec["job"].is_a?(Hash)
    @index = spec["index"]

    properties1 = spec['global_properties'].recursive_merge!(spec['cluster_properties'])
    properties = {}
    spec['default_properties'].each do |name, value|
      copy_property(properties, properties1, name, value)
    end

    @properties = openstruct(properties)
    @raw_properties = properties
    @spec = openstruct(spec)
  end

  def get_binding
    binding.taint
  end

  def p(*args)
    names = Array(args[0])

    names.each do |name|
      result = lookup_property(@raw_properties, name)
      return result unless result.nil?
    end

    return args[1] if args.length == 2
    raise UnknownProperty.new(names)
  end

  def if_p(*names)
    values = names.map do |name|
      value = lookup_property(@raw_properties, name)
      return ActiveElseBlock.new(self) if value.nil?
      value
    end

    yield *values
    InactiveElseBlock.new
  end

  def if_link(name)
    false
  end

  private

  def copy_property(dst, src, name, default = nil)
    keys = name.split(".")
    src_ref = src
    dst_ref = dst

    keys.each do |key|
      src_ref = src_ref[key]
      break if src_ref.nil? # no property with this name is src
    end

    keys[0..-2].each do |key|
      dst_ref[key] ||= {}
      dst_ref = dst_ref[key]
    end

    dst_ref[keys[-1]] ||= {}
    dst_ref[keys[-1]] = src_ref.nil? ? default : src_ref
  end

  def openstruct(object)
    case object
      when Hash
        mapped = object.inject({}) { |h, (k,v)| h[k] = openstruct(v); h }
        OpenStruct.new(mapped)
      when Array
        object.map { |item| openstruct(item) }
      else
        object
    end
  end

  def lookup_property(collection, name)
    keys = name.split(".")
    ref = collection

    keys.each do |key|
      ref = ref[key]
      return nil if ref.nil?
    end

    ref
  end

  class UnknownProperty < StandardError
    attr_reader :name

    def initialize(names)
      @names = names
      super("Can't find property '#{names.join("', or '")}'")
    end
  end

  class ActiveElseBlock
    def initialize(template)
      @context = template
    end

    def else
      yield
    end

    def else_if_p(*names, &block)
      @context.if_p(*names, &block)
    end
  end

  class InactiveElseBlock
    def else; end

    def else_if_p(*names)
      InactiveElseBlock.new
    end
  end
end

# todo do not use JSON in releases
class << JSON
  alias dump_array_or_hash dump

  def dump(*args)
    arg = args[0]
    if arg.is_a?(String) || arg.is_a?(Numeric)
      arg.inspect
    else
      dump_array_or_hash(*args)
    end
  end
end

class ERBRenderer
  def initialize(context)
    @context = context
  end

  def render(src_path, dst_path)
    erb = ERB.new(File.read(src_path))
    erb.filename = src_path

    File.open(dst_path, "w") do |f|
      f.write(erb.result(@context.get_binding))
    end

  rescue Exception => e
    name = "#{@context.name}/#{@context.index}"

    line_i = e.backtrace.index { |l| l.include?(erb.filename) }
    line_num = line_i ? e.backtrace[line_i].split(':')[1] : "unknown"
    location = "(line #{line_num}: #{e.inspect})"

    raise("Error filling in template '#{src_path}' for #{name} #{location}")
  end
end

if $0 == __FILE__
  context_path, src_path, dst_path = *ARGV

  context_hash = JSON.load(File.read(context_path))
  context = TemplateEvaluationContext.new(context_hash)

  renderer = ERBRenderer.new(context)
  renderer.render(src_path, dst_path)
end
`
<commit_msg>Restores rubygems requirement, for Ruby 1.8<commit_after>package erbrenderer

const templateEvaluationContextRb = `
# Based on common/properties/template_evaluation_context.rb
require "rubygems"
require "ostruct"
require "json"
require "erb"
require "yaml"

class Hash
  def recursive_merge!(other)
    self.merge!(other) do |_, old_value, new_value|
      if old_value.class == Hash && new_value.class == Hash
        old_value.recursive_merge!(new_value)
      else
        new_value
      end
    end
    self
  end
end

class TemplateEvaluationContext
  attr_reader :name, :index
  attr_reader :properties, :raw_properties
  attr_reader :spec

  def initialize(spec)
    @name = spec["job"]["name"] if spec["job"].is_a?(Hash)
    @index = spec["index"]

    properties1 = spec['global_properties'].recursive_merge!(spec['cluster_properties'])
    properties = {}
    spec['default_properties'].each do |name, value|
      copy_property(properties, properties1, name, value)
    end

    @properties = openstruct(properties)
    @raw_properties = properties
    @spec = openstruct(spec)
  end

  def get_binding
    binding.taint
  end

  def p(*args)
    names = Array(args[0])

    names.each do |name|
      result = lookup_property(@raw_properties, name)
      return result unless result.nil?
    end

    return args[1] if args.length == 2
    raise UnknownProperty.new(names)
  end

  def if_p(*names)
    values = names.map do |name|
      value = lookup_property(@raw_properties, name)
      return ActiveElseBlock.new(self) if value.nil?
      value
    end

    yield *values
    InactiveElseBlock.new
  end

  def if_link(name)
    false
  end

  private

  def copy_property(dst, src, name, default = nil)
    keys = name.split(".")
    src_ref = src
    dst_ref = dst

    keys.each do |key|
      src_ref = src_ref[key]
      break if src_ref.nil? # no property with this name is src
    end

    keys[0..-2].each do |key|
      dst_ref[key] ||= {}
      dst_ref = dst_ref[key]
    end

    dst_ref[keys[-1]] ||= {}
    dst_ref[keys[-1]] = src_ref.nil? ? default : src_ref
  end

  def openstruct(object)
    case object
      when Hash
        mapped = object.inject({}) { |h, (k,v)| h[k] = openstruct(v); h }
        OpenStruct.new(mapped)
      when Array
        object.map { |item| openstruct(item) }
      else
        object
    end
  end

  def lookup_property(collection, name)
    keys = name.split(".")
    ref = collection

    keys.each do |key|
      ref = ref[key]
      return nil if ref.nil?
    end

    ref
  end

  class UnknownProperty < StandardError
    attr_reader :name

    def initialize(names)
      @names = names
      super("Can't find property '#{names.join("', or '")}'")
    end
  end

  class ActiveElseBlock
    def initialize(template)
      @context = template
    end

    def else
      yield
    end

    def else_if_p(*names, &block)
      @context.if_p(*names, &block)
    end
  end

  class InactiveElseBlock
    def else; end

    def else_if_p(*names)
      InactiveElseBlock.new
    end
  end
end

# todo do not use JSON in releases
class << JSON
  alias dump_array_or_hash dump

  def dump(*args)
    arg = args[0]
    if arg.is_a?(String) || arg.is_a?(Numeric)
      arg.inspect
    else
      dump_array_or_hash(*args)
    end
  end
end

class ERBRenderer
  def initialize(context)
    @context = context
  end

  def render(src_path, dst_path)
    erb = ERB.new(File.read(src_path))
    erb.filename = src_path

    File.open(dst_path, "w") do |f|
      f.write(erb.result(@context.get_binding))
    end

  rescue Exception => e
    name = "#{@context.name}/#{@context.index}"

    line_i = e.backtrace.index { |l| l.include?(erb.filename) }
    line_num = line_i ? e.backtrace[line_i].split(':')[1] : "unknown"
    location = "(line #{line_num}: #{e.inspect})"

    raise("Error filling in template '#{src_path}' for #{name} #{location}")
  end
end

if $0 == __FILE__
  context_path, src_path, dst_path = *ARGV

  context_hash = JSON.load(File.read(context_path))
  context = TemplateEvaluationContext.new(context_hash)

  renderer = ERBRenderer.new(context)
  renderer.render(src_path, dst_path)
end
`
<|endoftext|>


<commit_before>// +build !windows,!darwin

package volume

import (
	"errors"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

var useAmixer bool

func init() {
	if _, err := exec.LookPath("pactl"); err != nil {
		useAmixer = true
	}
	if _, err := exec.LookPath("pacmd"); err != nil {
		useAmixer = true
	}
}

func cmdEnv() []string {
	return []string{"LANG=C", "LC_ALL=C"}
}

func getVolumeCmd() []string {
	if useAmixer {
		return []string{"amixer", "get", "Master"}
	}
	return []string{"pacmd", "list-sinks"}
}

var volumePattern = regexp.MustCompile(`\d+%`)

func parseVolume(out string) (int, error) {
	lines := strings.Split(out, "\n")
	pa_default_sink_hooked := false

	for _, line := range lines {
		s := strings.TrimLeft(line, " \t")

		if !useAmixer && strings.HasPrefix(s, "* index") {
			pa_default_sink_hooked = true
		}

		if useAmixer && strings.Contains(s, "Playback") && strings.Contains(s, "%") ||
			!useAmixer && pa_default_sink_hooked && strings.HasPrefix(s, "volume:") {
			volumeStr := volumePattern.FindString(s)
			return strconv.Atoi(volumeStr[:len(volumeStr)-1])
		}
	}
	return 0, errors.New("no volume found")
}

func setVolumeCmd(volume int) []string {
	if useAmixer {
		return []string{"amixer", "set", "Master", strconv.Itoa(volume) + "%"}
	}
	return []string{"pactl", "set-sink-volume", "@DEFAULT_SINK@", strconv.Itoa(volume) + "%"}
}

func increaseVolumeCmd(diff int) []string {
	var sign string
	if diff >= 0 {
		sign = "+"
	} else if useAmixer {
		diff = -diff
		sign = "-"
	}
	if useAmixer {
		return []string{"amixer", "set", "Master", strconv.Itoa(diff) + "%" + sign}
	}
	return []string{"pactl", "--", "set-sink-volume", "@DEFAULT_SINK@", sign + strconv.Itoa(diff) + "%"}
}

func getMutedCmd() []string {
	if useAmixer {
		return []string{"amixer", "get", "Master"}
	}
	return []string{"pactl", "list", "sinks"}
}

func parseMuted(out string) (bool, error) {
	lines := strings.Split(out, "\n")
	for _, line := range lines {
		s := strings.TrimLeft(line, " \t")
		if useAmixer && strings.Contains(s, "Playback") && strings.Contains(s, "%") ||
			!useAmixer && strings.HasPrefix(s, "Mute: ") {
			if strings.Contains(s, "[off]") || strings.Contains(s, "yes") {
				return true, nil
			} else if strings.Contains(s, "[on]") || strings.Contains(s, "no") {
				return false, nil
			}
		}
	}
	return false, errors.New("no muted information found")
}

func muteCmd() []string {
	if useAmixer {
		return []string{"amixer", "-D", "pulse", "set", "Master", "mute"}
	}
	return []string{"pactl", "set-sink-mute", "@DEFAULT_SINK@", "1"}
}

func unmuteCmd() []string {
	if useAmixer {
		return []string{"amixer", "-D", "pulse", "set", "Master", "unmute"}
	}
	return []string{"pactl", "set-sink-mute", "@DEFAULT_SINK@", "0"}
}
<commit_msg>PulseAudio: Get default sink via Server String: localhost Library Protocol Version: 32 Server Protocol Version: 32 Is Local: no Client Index: 248 Tile Size: 65472 User Name: hikaru Host Name: hikaru-thinkpad Server Name: pulseaudio Server Version: 11.1 Default Sample Specification: s16le 2ch 44100Hz Default Channel Map: front-left,front-right Default Sink: MySink Default Source: alsa_output.pci-0000_00_1f.3.analog-stereo.monitor Cookie: 6325:d303<commit_after>// +build !windows,!darwin

package volume

import (
	"errors"
	"os/exec"
	"regexp"
	"strconv"
	"strings"
)

var useAmixer bool

func init() {
	if _, err := exec.LookPath("pactl"); err != nil {
		useAmixer = true
	}
}

func cmdEnv() []string {
	return []string{"LANG=C", "LC_ALL=C"}
}

func getVolumeCmd() []string {
	if useAmixer {
		return []string{"amixer", "get", "Master"}
	}
	return []string{"pactl", "list", "sinks"}
}

func getPADefaultSink() (string, error) {
	out, err := execCmd([]string{"pactl", "info"})

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

	lines := strings.Split(string(out), "\n")

	defaultSinkStr := "Default Sink: "
	for _, line := range lines {
		s := strings.TrimLeft(line, " \t")
		if strings.HasPrefix(s, defaultSinkStr) {
			return strings.TrimSpace(strings.Replace(s, defaultSinkStr, "", 1)), nil
		}
	}
	return "", errors.New("Could not find PulseAudio Default Sink")
}

var volumePattern = regexp.MustCompile(`\d+%`)

func parseVolume(out string) (int, error) {
	sinkName, sinkNameErr := getPADefaultSink()

	pa_default_sink_hooked := false

	if sinkNameErr != nil {
		pa_default_sink_hooked = true
	}

	lines := strings.Split(out, "\n")

	for _, line := range lines {
		s := strings.TrimLeft(line, " \t")

		if !useAmixer && strings.Contains(s, "Name: "+string(sinkName)) {
			pa_default_sink_hooked = true
		}

		if useAmixer && strings.Contains(s, "Playback") && strings.Contains(s, "%") ||
			!useAmixer && pa_default_sink_hooked && strings.HasPrefix(s, "Volume:") {
			volumeStr := volumePattern.FindString(s)
			return strconv.Atoi(volumeStr[:len(volumeStr)-1])
		}
	}
	return 0, errors.New("no volume found")
}

func setVolumeCmd(volume int) []string {
	if useAmixer {
		return []string{"amixer", "set", "Master", strconv.Itoa(volume) + "%"}
	}
	return []string{"pactl", "set-sink-volume", "@DEFAULT_SINK@", strconv.Itoa(volume) + "%"}
}

func increaseVolumeCmd(diff int) []string {
	var sign string
	if diff >= 0 {
		sign = "+"
	} else if useAmixer {
		diff = -diff
		sign = "-"
	}
	if useAmixer {
		return []string{"amixer", "set", "Master", strconv.Itoa(diff) + "%" + sign}
	}
	return []string{"pactl", "--", "set-sink-volume", "@DEFAULT_SINK@", sign + strconv.Itoa(diff) + "%"}
}

func getMutedCmd() []string {
	if useAmixer {
		return []string{"amixer", "get", "Master"}
	}
	return []string{"pactl", "list", "sinks"}
}

func parseMuted(out string) (bool, error) {
	sinkName, sinkNameErr := getPADefaultSink()

	pa_default_sink_hooked := false

	if sinkNameErr != nil {
		pa_default_sink_hooked = true
	}

	lines := strings.Split(out, "\n")
	for _, line := range lines {
		s := strings.TrimLeft(line, " \t")

		if !useAmixer && strings.Contains(s, "Name: "+string(sinkName)) {
			pa_default_sink_hooked = true
		}

		if useAmixer && strings.Contains(s, "Playback") && strings.Contains(s, "%") ||
			!useAmixer && pa_default_sink_hooked && strings.HasPrefix(s, "Mute: ") {
			if strings.Contains(s, "[off]") || strings.Contains(s, "yes") {
				return true, nil
			} else if strings.Contains(s, "[on]") || strings.Contains(s, "no") {
				return false, nil
			}
		}
	}
	return false, errors.New("no muted information found")
}

func muteCmd() []string {
	if useAmixer {
		return []string{"amixer", "-D", "pulse", "set", "Master", "mute"}
	}
	return []string{"pactl", "set-sink-mute", "@DEFAULT_SINK@", "1"}
}

func unmuteCmd() []string {
	if useAmixer {
		return []string{"amixer", "-D", "pulse", "set", "Master", "unmute"}
	}
	return []string{"pactl", "set-sink-mute", "@DEFAULT_SINK@", "0"}
}
<|endoftext|>


<commit_before>package fs

import (
	"encoding/base64"
	"io/ioutil"
	"log"
	"reflect"
	"testing"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

var (
	fs     *kite.Kite
	remote *kite.Client
)

func init() {
	fs = kite.New("fs", "0.0.1")
	fs.Config.DisableAuthentication = true
	fs.Config.Port = 3636
	fs.HandleFunc("readDirectory", ReadDirectory)
	fs.HandleFunc("glob", Glob)
	fs.HandleFunc("readFile", ReadFile)
	fs.HandleFunc("writeFile", WriteFile)
	fs.HandleFunc("uniquePath", UniquePath)
	fs.HandleFunc("getInfo", GetInfo)
	fs.HandleFunc("setPermissions", SetPermissions)
	fs.HandleFunc("remove", Remove)
	fs.HandleFunc("rename", Rename)
	fs.HandleFunc("createDirectory", CreateDirectory)
	fs.HandleFunc("move", Move)
	fs.HandleFunc("copy", Copy)

	go fs.Run()
	<-fs.ServerReadyNotify()

	client := kite.New("client", "0.0.1")
	client.Config.DisableAuthentication = true
	remote = client.NewClientString("ws://127.0.0.1:3636")
	err := remote.Dial()
	if err != nil {
		log.Fatal("err")
	}
}

func TestReadDirectory(t *testing.T) {
	testDir := "."

	files, err := ioutil.ReadDir(testDir)
	if err != nil {
		t.Fatal(err)
	}

	currentFiles := make([]string, len(files))
	for i, f := range files {
		currentFiles[i] = f.Name()
	}

	resp, err := remote.Tell("readDirectory", struct {
		Path     string
		OnChange dnode.Function
	}{
		Path:     testDir,
		OnChange: dnode.Function{},
	})

	if err != nil {
		t.Fatal(err)
	}

	f, err := resp.Map()
	if err != nil {
		t.Fatal(err)
	}

	entries, err := f["files"].SliceOfLength(len(files))
	if err != nil {
		t.Fatal(err)
	}

	respFiles := make([]string, len(files))
	for i, e := range entries {
		f := &FileEntry{}
		err := e.Unmarshal(f)
		if err != nil {
			t.Fatal(err)
		}

		respFiles[i] = f.Name
	}

	if !reflect.DeepEqual(respFiles, currentFiles) {
		t.Error("got %+v, expected %+v", respFiles, currentFiles)
	}
}

func TestGlob(t *testing.T) {
	testGlob := "*"

	files, err := glob(testGlob)
	if err != nil {
		t.Fatal(err)
	}

	resp, err := remote.Tell("glob", struct {
		Pattern string
	}{
		Pattern: testGlob,
	})
	if err != nil {
		t.Fatal(err)
	}

	var r []string
	err = resp.Unmarshal(&r)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(r, files) {
		t.Errorf("got %+v, expected %+v", r, files)
	}
}

func TestReadFile(t *testing.T) {
	testFile := "testdata/testfile1.txt"

	content, err := ioutil.ReadFile(testFile)
	if err != nil {
		t.Error(err)
	}

	resp, err := remote.Tell("readFile", struct {
		Path string
	}{
		Path: testFile,
	})
	if err != nil {
		t.Fatal(err)
	}

	buf := resp.MustMap()["content"].MustString()

	s, err := base64.StdEncoding.DecodeString(buf)
	if err != nil {
		t.Error(err)
	}

	if string(s) != string(content) {
		t.Errorf("got %s, expecting %s", string(s), string(content))
	}

}

func TestWriteFile(t *testing.T)       {}
func TestUniquePath(t *testing.T)      {}
func TestGetInfo(t *testing.T)         {}
func TestSetPermissions(t *testing.T)  {}
func TestRemove(t *testing.T)          {}
func TestRename(t *testing.T)          {}
func TestCreateDirectory(t *testing.T) {}
func TestMove(t *testing.T)            {}
func TestCopy(t *testing.T)            {}
<commit_msg>kite-handler/fs: add WriteFile test<commit_after>package fs

import (
	"encoding/base64"
	"io/ioutil"
	"log"
	"os"
	"reflect"
	"testing"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

var (
	fs     *kite.Kite
	remote *kite.Client
)

func init() {
	fs = kite.New("fs", "0.0.1")
	fs.Config.DisableAuthentication = true
	fs.Config.Port = 3636
	fs.HandleFunc("readDirectory", ReadDirectory)
	fs.HandleFunc("glob", Glob)
	fs.HandleFunc("readFile", ReadFile)
	fs.HandleFunc("writeFile", WriteFile)
	fs.HandleFunc("uniquePath", UniquePath)
	fs.HandleFunc("getInfo", GetInfo)
	fs.HandleFunc("setPermissions", SetPermissions)
	fs.HandleFunc("remove", Remove)
	fs.HandleFunc("rename", Rename)
	fs.HandleFunc("createDirectory", CreateDirectory)
	fs.HandleFunc("move", Move)
	fs.HandleFunc("copy", Copy)

	go fs.Run()
	<-fs.ServerReadyNotify()

	client := kite.New("client", "0.0.1")
	client.Config.DisableAuthentication = true
	remote = client.NewClientString("ws://127.0.0.1:3636")
	err := remote.Dial()
	if err != nil {
		log.Fatal("err")
	}
}

func TestReadDirectory(t *testing.T) {
	testDir := "."

	files, err := ioutil.ReadDir(testDir)
	if err != nil {
		t.Fatal(err)
	}

	currentFiles := make([]string, len(files))
	for i, f := range files {
		currentFiles[i] = f.Name()
	}

	resp, err := remote.Tell("readDirectory", struct {
		Path     string
		OnChange dnode.Function
	}{
		Path:     testDir,
		OnChange: dnode.Function{},
	})

	if err != nil {
		t.Fatal(err)
	}

	f, err := resp.Map()
	if err != nil {
		t.Fatal(err)
	}

	entries, err := f["files"].SliceOfLength(len(files))
	if err != nil {
		t.Fatal(err)
	}

	respFiles := make([]string, len(files))
	for i, e := range entries {
		f := &FileEntry{}
		err := e.Unmarshal(f)
		if err != nil {
			t.Fatal(err)
		}

		respFiles[i] = f.Name
	}

	if !reflect.DeepEqual(respFiles, currentFiles) {
		t.Error("got %+v, expected %+v", respFiles, currentFiles)
	}
}

func TestGlob(t *testing.T) {
	testGlob := "*"

	files, err := glob(testGlob)
	if err != nil {
		t.Fatal(err)
	}

	resp, err := remote.Tell("glob", struct {
		Pattern string
	}{
		Pattern: testGlob,
	})
	if err != nil {
		t.Fatal(err)
	}

	var r []string
	err = resp.Unmarshal(&r)
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(r, files) {
		t.Errorf("got %+v, expected %+v", r, files)
	}
}

func TestReadFile(t *testing.T) {
	testFile := "testdata/testfile1.txt"

	content, err := ioutil.ReadFile(testFile)
	if err != nil {
		t.Error(err)
	}

	resp, err := remote.Tell("readFile", struct {
		Path string
	}{
		Path: testFile,
	})
	if err != nil {
		t.Fatal(err)
	}

	buf := resp.MustMap()["content"].MustString()

	s, err := base64.StdEncoding.DecodeString(buf)
	if err != nil {
		t.Error(err)
	}

	if string(s) != string(content) {
		t.Errorf("got %s, expecting %s", string(s), string(content))
	}

}

func TestWriteFile(t *testing.T) {
	testFile, err := ioutil.TempFile("", "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(testFile.Name())

	content := []byte("hello kite")

	resp, err := remote.Tell("writeFile", struct {
		Path           string
		Content        []byte
		DoNotOverwrite bool
		Append         bool
	}{
		Path:    testFile.Name(),
		Content: content,
	})
	if err != nil {
		t.Fatal(err)
	}

	if int(resp.MustFloat64()) != len(content) {
		t.Errorf("content len is wrong. got %d expected %d", int(resp.MustFloat64()), len(content))
	}

	buf, err := ioutil.ReadFile(testFile.Name())
	if err != nil {
		t.Fatal(err)
	}

	if !reflect.DeepEqual(buf, content) {
		t.Errorf("content is wrong. got '%s' expected '%s'", string(buf), string(content))
	}
}

func TestUniquePath(t *testing.T)      {}
func TestGetInfo(t *testing.T)         {}
func TestSetPermissions(t *testing.T)  {}
func TestRemove(t *testing.T)          {}
func TestRename(t *testing.T)          {}
func TestCreateDirectory(t *testing.T) {}
func TestMove(t *testing.T)            {}
func TestCopy(t *testing.T)            {}
<|endoftext|>