<commit_before>package request

import (
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/awsutil"
)

// WaiterResourceNotReadyErrorCode is the error code returned by a waiter when
// the waiter's max attempts have been exhausted.
const WaiterResourceNotReadyErrorCode = "ResourceNotReady"

// A WaiterOption is a function that will update the Waiter value's fields to
// configure the waiter.
type WaiterOption func(*Waiter)

// WithWaiterMaxAttempts returns the maximum number of times the waiter should
// attempt to check the resource for the target state.
func WithWaiterMaxAttempts(max int) WaiterOption {
	return func(w *Waiter) {
		w.MaxAttempts = max
	}
}

// WaiterDelay will return a delay the waiter should pause between attempts to
// check the resource state. The passed in attempt is the number of times the
// Waiter has checked the resource state.
//
// Attempt is the number of attempts the Waiter has made checking the resource
// state.
type WaiterDelay func(attempt int) time.Duration

// ConstantWaiterDelay returns a WaiterDelay that will always return a constant
// delay the waiter should use between attempts. It ignores the number of
// attempts made.
func ConstantWaiterDelay(delay time.Duration) WaiterDelay {
	return func(attempt int) time.Duration {
		return delay
	}
}

// WithWaiterDelay will set the Waiter to use the WaiterDelay passed in.
func WithWaiterDelay(delayer WaiterDelay) WaiterOption {
	return func(w *Waiter) {
		w.Delay = delayer
	}
}

// WithWaiterLogger returns a waiter option to set the logger a waiter
// should use to log warnings and errors to.
func WithWaiterLogger(logger aws.Logger) WaiterOption {
	return func(w *Waiter) {
		w.Logger = logger
	}
}

// WithWaiterRequestOptions returns a waiter option setting the request
// options for each request the waiter makes. Appends to waiter's request
// options already set.
func WithWaiterRequestOptions(opts ...Option) WaiterOption {
	return func(w *Waiter) {
		w.RequestOptions = append(w.RequestOptions, opts...)
	}
}

// A Waiter provides the functionality to performing blocking call which will
// wait for an resource state to be satisfied a service.
//
// This type should not be used directly. The API operations provided in the
// service packages prefixed with "WaitUntil" should be used instead.
type Waiter struct {
	Name      string
	Acceptors []WaiterAcceptor
	Logger    aws.Logger

	MaxAttempts int
	Delay       WaiterDelay

	RequestOptions []Option
	NewRequest     func([]Option) (*Request, error)
}

// ApplyOptions updates the waiter with the list of waiter options provided.
func (w *Waiter) ApplyOptions(opts ...WaiterOption) {
	for _, fn := range opts {
		fn(w)
	}
}

// WaiterState are states the waiter uses based on WaiterAcceptor definitions
// to identify if the resource state the waiter is waiting on has occurred.
type WaiterState int

// String returns the string representation of the waiter state.
func (s WaiterState) String() string {
	switch s {
	case SuccessWaiterState:
		return "success"
	case FailureWaiterState:
		return "failure"
	case RetryWaiterState:
		return "retry"
	default:
		return "unknown waiter state"
	}
}

// States the waiter acceptors will use to identify target resource states.
const (
	SuccessWaiterState WaiterState = iota // waiter successful
	FailureWaiterState                    // waiter failed
	RetryWaiterState                      // waiter needs to be retried
)

// WaiterMatchMode is the mode that the waiter will use to match the WaiterAcceptor
// definition's Expected attribute.
type WaiterMatchMode int

// Modes the waiter will use when inspecting API response to identify target
// resource states.
const (
	PathAllWaiterMatch  WaiterMatchMode = iota // match on all paths
	PathWaiterMatch                            // match on specific path
	PathAnyWaiterMatch                         // match on any path
	PathListWaiterMatch                        // match on list of paths
	StatusWaiterMatch                          // match on status code
	ErrorWaiterMatch                           // match on error
)

// String returns the string representation of the waiter match mode.
func (m WaiterMatchMode) String() string {
	switch m {
	case PathAllWaiterMatch:
		return "pathAll"
	case PathWaiterMatch:
		return "path"
	case PathAnyWaiterMatch:
		return "pathAny"
	case PathListWaiterMatch:
		return "pathList"
	case StatusWaiterMatch:
		return "status"
	case ErrorWaiterMatch:
		return "error"
	default:
		return "unknown waiter match mode"
	}
}

// WaitWithContext will make requests for the API operation using NewRequest to
// build API requests. The request's response will be compared against the
// Waiter's Acceptors to determine the successful state of the resource the
// waiter is inspecting.
//
// The passed in context must not be nil. If it is nil a panic will occur. The
// Context will be used to cancel the waiter's pending requests and retry delays.
// Use aws.BackgroundContext if no context is available.
//
// The waiter will continue until the target state defined by the Acceptors,
// or the max attempts expires.
//
// Will return the WaiterResourceNotReadyErrorCode error code if the waiter's
// retryer ShouldRetry returns false. This normally will happen when the max
// wait attempts expires.
func (w Waiter) WaitWithContext(ctx aws.Context) error {

	for attempt := 1; ; attempt++ {
		req, err := w.NewRequest(w.RequestOptions)
		if err != nil {
			waiterLogf(w.Logger, "unable to create request %v", err)
			return err
		}
		req.Handlers.Build.PushBack(MakeAddToUserAgentFreeFormHandler("Waiter"))
		err = req.Send()

		// See if any of the acceptors match the request's response, or error
		for _, a := range w.Acceptors {
			if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched {
				return matchErr
			}
		}

		// The Waiter should only check the resource state MaxAttempts times
		// This is here instead of in the for loop above to prevent delaying
		// unnecessary when the waiter will not retry.
		if attempt == w.MaxAttempts {
			break
		}

		// Delay to wait before inspecting the resource again
		delay := w.Delay(attempt)
		if sleepFn := req.Config.SleepDelay; sleepFn != nil {
			// Support SleepDelay for backwards compatibility and testing
			sleepFn(delay)
		} else if err := aws.SleepWithContext(ctx, delay); err != nil {
			return awserr.New(CanceledErrorCode, "waiter context canceled", err)
		}
	}

	return awserr.New(WaiterResourceNotReadyErrorCode, "exceeded wait attempts", nil)
}

// A WaiterAcceptor provides the information needed to wait for an API operation
// to complete.
type WaiterAcceptor struct {
	State    WaiterState
	Matcher  WaiterMatchMode
	Argument string
	Expected interface{}
}

// match returns if the acceptor found a match with the passed in request
// or error. True is returned if the acceptor made a match, error is returned
// if there was an error attempting to perform the match.
func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err error) (bool, error) {
	result := false
	var vals []interface{}

	switch a.Matcher {
	case PathAllWaiterMatch, PathWaiterMatch:
		// Require all matches to be equal for result to match
		vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
		if len(vals) == 0 {
			break
		}
		result = true
		for _, val := range vals {
			if !awsutil.DeepEqual(val, a.Expected) {
				result = false
				break
			}
		}
	case PathAnyWaiterMatch:
		// Only a single match needs to equal for the result to match
		vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
		for _, val := range vals {
			if awsutil.DeepEqual(val, a.Expected) {
				result = true
				break
			}
		}
	case PathListWaiterMatch:
		// ignored matcher
	case StatusWaiterMatch:
		s := a.Expected.(int)
		result = s == req.HTTPResponse.StatusCode
	case ErrorWaiterMatch:
		if aerr, ok := err.(awserr.Error); ok {
			result = aerr.Code() == a.Expected.(string)
		}
	default:
		waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s",
			name, a.Matcher)
	}

	if !result {
		// If there was no matching result found there is nothing more to do
		// for this response, retry the request.
		return false, nil
	}

	switch a.State {
	case SuccessWaiterState:
		// waiter completed
		return true, nil
	case FailureWaiterState:
		// Waiter failure state triggered
		return true, awserr.New(WaiterResourceNotReadyErrorCode,
			"failed waiting for successful resource state", err)
	case RetryWaiterState:
		// clear the error and retry the operation
		return false, nil
	default:
		waiterLogf(l, "WARNING: Waiter %s encountered unexpected state: %s",
			name, a.State)
		return false, nil
	}
}

func waiterLogf(logger aws.Logger, msg string, args ...interface{}) {
	if logger != nil {
		logger.Log(fmt.Sprintf(msg, args...))
	}
}
<commit_msg>Update waiter.go (#1246)<commit_after>package request

import (
	"fmt"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/awsutil"
)

// WaiterResourceNotReadyErrorCode is the error code returned by a waiter when
// the waiter's max attempts have been exhausted.
const WaiterResourceNotReadyErrorCode = "ResourceNotReady"

// A WaiterOption is a function that will update the Waiter value's fields to
// configure the waiter.
type WaiterOption func(*Waiter)

// WithWaiterMaxAttempts returns the maximum number of times the waiter should
// attempt to check the resource for the target state.
func WithWaiterMaxAttempts(max int) WaiterOption {
	return func(w *Waiter) {
		w.MaxAttempts = max
	}
}

// WaiterDelay will return a delay the waiter should pause between attempts to
// check the resource state. The passed in attempt is the number of times the
// Waiter has checked the resource state.
//
// Attempt is the number of attempts the Waiter has made checking the resource
// state.
type WaiterDelay func(attempt int) time.Duration

// ConstantWaiterDelay returns a WaiterDelay that will always return a constant
// delay the waiter should use between attempts. It ignores the number of
// attempts made.
func ConstantWaiterDelay(delay time.Duration) WaiterDelay {
	return func(attempt int) time.Duration {
		return delay
	}
}

// WithWaiterDelay will set the Waiter to use the WaiterDelay passed in.
func WithWaiterDelay(delayer WaiterDelay) WaiterOption {
	return func(w *Waiter) {
		w.Delay = delayer
	}
}

// WithWaiterLogger returns a waiter option to set the logger a waiter
// should use to log warnings and errors to.
func WithWaiterLogger(logger aws.Logger) WaiterOption {
	return func(w *Waiter) {
		w.Logger = logger
	}
}

// WithWaiterRequestOptions returns a waiter option setting the request
// options for each request the waiter makes. Appends to waiter's request
// options already set.
func WithWaiterRequestOptions(opts ...Option) WaiterOption {
	return func(w *Waiter) {
		w.RequestOptions = append(w.RequestOptions, opts...)
	}
}

// A Waiter provides the functionality to perform a blocking call which will
// wait for a resource state to be satisfied by a service.
//
// This type should not be used directly. The API operations provided in the
// service packages prefixed with "WaitUntil" should be used instead.
type Waiter struct {
	Name      string
	Acceptors []WaiterAcceptor
	Logger    aws.Logger

	MaxAttempts int
	Delay       WaiterDelay

	RequestOptions []Option
	NewRequest     func([]Option) (*Request, error)
}

// ApplyOptions updates the waiter with the list of waiter options provided.
func (w *Waiter) ApplyOptions(opts ...WaiterOption) {
	for _, fn := range opts {
		fn(w)
	}
}

// WaiterState are states the waiter uses based on WaiterAcceptor definitions
// to identify if the resource state the waiter is waiting on has occurred.
type WaiterState int

// String returns the string representation of the waiter state.
func (s WaiterState) String() string {
	switch s {
	case SuccessWaiterState:
		return "success"
	case FailureWaiterState:
		return "failure"
	case RetryWaiterState:
		return "retry"
	default:
		return "unknown waiter state"
	}
}

// States the waiter acceptors will use to identify target resource states.
const (
	SuccessWaiterState WaiterState = iota // waiter successful
	FailureWaiterState                    // waiter failed
	RetryWaiterState                      // waiter needs to be retried
)

// WaiterMatchMode is the mode that the waiter will use to match the WaiterAcceptor
// definition's Expected attribute.
type WaiterMatchMode int

// Modes the waiter will use when inspecting API response to identify target
// resource states.
const (
	PathAllWaiterMatch  WaiterMatchMode = iota // match on all paths
	PathWaiterMatch                            // match on specific path
	PathAnyWaiterMatch                         // match on any path
	PathListWaiterMatch                        // match on list of paths
	StatusWaiterMatch                          // match on status code
	ErrorWaiterMatch                           // match on error
)

// String returns the string representation of the waiter match mode.
func (m WaiterMatchMode) String() string {
	switch m {
	case PathAllWaiterMatch:
		return "pathAll"
	case PathWaiterMatch:
		return "path"
	case PathAnyWaiterMatch:
		return "pathAny"
	case PathListWaiterMatch:
		return "pathList"
	case StatusWaiterMatch:
		return "status"
	case ErrorWaiterMatch:
		return "error"
	default:
		return "unknown waiter match mode"
	}
}

// WaitWithContext will make requests for the API operation using NewRequest to
// build API requests. The request's response will be compared against the
// Waiter's Acceptors to determine the successful state of the resource the
// waiter is inspecting.
//
// The passed in context must not be nil. If it is nil a panic will occur. The
// Context will be used to cancel the waiter's pending requests and retry delays.
// Use aws.BackgroundContext if no context is available.
//
// The waiter will continue until the target state defined by the Acceptors,
// or the max attempts expires.
//
// Will return the WaiterResourceNotReadyErrorCode error code if the waiter's
// retryer ShouldRetry returns false. This normally will happen when the max
// wait attempts expires.
func (w Waiter) WaitWithContext(ctx aws.Context) error {

	for attempt := 1; ; attempt++ {
		req, err := w.NewRequest(w.RequestOptions)
		if err != nil {
			waiterLogf(w.Logger, "unable to create request %v", err)
			return err
		}
		req.Handlers.Build.PushBack(MakeAddToUserAgentFreeFormHandler("Waiter"))
		err = req.Send()

		// See if any of the acceptors match the request's response, or error
		for _, a := range w.Acceptors {
			if matched, matchErr := a.match(w.Name, w.Logger, req, err); matched {
				return matchErr
			}
		}

		// The Waiter should only check the resource state MaxAttempts times
		// This is here instead of in the for loop above to prevent delaying
		// unnecessary when the waiter will not retry.
		if attempt == w.MaxAttempts {
			break
		}

		// Delay to wait before inspecting the resource again
		delay := w.Delay(attempt)
		if sleepFn := req.Config.SleepDelay; sleepFn != nil {
			// Support SleepDelay for backwards compatibility and testing
			sleepFn(delay)
		} else if err := aws.SleepWithContext(ctx, delay); err != nil {
			return awserr.New(CanceledErrorCode, "waiter context canceled", err)
		}
	}

	return awserr.New(WaiterResourceNotReadyErrorCode, "exceeded wait attempts", nil)
}

// A WaiterAcceptor provides the information needed to wait for an API operation
// to complete.
type WaiterAcceptor struct {
	State    WaiterState
	Matcher  WaiterMatchMode
	Argument string
	Expected interface{}
}

// match returns if the acceptor found a match with the passed in request
// or error. True is returned if the acceptor made a match, error is returned
// if there was an error attempting to perform the match.
func (a *WaiterAcceptor) match(name string, l aws.Logger, req *Request, err error) (bool, error) {
	result := false
	var vals []interface{}

	switch a.Matcher {
	case PathAllWaiterMatch, PathWaiterMatch:
		// Require all matches to be equal for result to match
		vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
		if len(vals) == 0 {
			break
		}
		result = true
		for _, val := range vals {
			if !awsutil.DeepEqual(val, a.Expected) {
				result = false
				break
			}
		}
	case PathAnyWaiterMatch:
		// Only a single match needs to equal for the result to match
		vals, _ = awsutil.ValuesAtPath(req.Data, a.Argument)
		for _, val := range vals {
			if awsutil.DeepEqual(val, a.Expected) {
				result = true
				break
			}
		}
	case PathListWaiterMatch:
		// ignored matcher
	case StatusWaiterMatch:
		s := a.Expected.(int)
		result = s == req.HTTPResponse.StatusCode
	case ErrorWaiterMatch:
		if aerr, ok := err.(awserr.Error); ok {
			result = aerr.Code() == a.Expected.(string)
		}
	default:
		waiterLogf(l, "WARNING: Waiter %s encountered unexpected matcher: %s",
			name, a.Matcher)
	}

	if !result {
		// If there was no matching result found there is nothing more to do
		// for this response, retry the request.
		return false, nil
	}

	switch a.State {
	case SuccessWaiterState:
		// waiter completed
		return true, nil
	case FailureWaiterState:
		// Waiter failure state triggered
		return true, awserr.New(WaiterResourceNotReadyErrorCode,
			"failed waiting for successful resource state", err)
	case RetryWaiterState:
		// clear the error and retry the operation
		return false, nil
	default:
		waiterLogf(l, "WARNING: Waiter %s encountered unexpected state: %s",
			name, a.State)
		return false, nil
	}
}

func waiterLogf(logger aws.Logger, msg string, args ...interface{}) {
	if logger != nil {
		logger.Log(fmt.Sprintf(msg, args...))
	}
}
<|endoftext|>


<commit_before>package bos

import (
	"strconv"
	"testing"
	"time"

	"baidubce/test"
)

var bosClient = DefaultClient

func TestGetBucketLocation(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-get-bucket-location-"
	method := "GetBucketLocation"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		expected := "bj"
		location, _ := bosClient.GetBucketLocation(bucketName, nil)

		if location.LocationConstraint != expected {
			t.Error(test.Format(method, location.LocationConstraint, expected))
		}
	})
}

func TestListBuckets(t *testing.T) {
	_, err := bosClient.ListBuckets(nil)

	if err != nil {
		t.Error(test.Format("ListBuckets", err.Error(), "nil"))
	}
}

func TestCreateBucket(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-create-bucket-"
	method := "CreateBucket"

	around(t, method, bucketNamePrefix, "", nil)
}

func TestDoesBucketExist(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-does-bucket-exist-"
	method := "DoesBucketExist"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		expected := true
		exists, err := bosClient.DoesBucketExist(bucketName, nil)

		if err != nil {
			t.Error(test.Format(method, err.Error(), strconv.FormatBool(expected)))
		} else if exists != expected {
			t.Error(test.Format(method, strconv.FormatBool(exists), strconv.FormatBool(expected)))
		}
	})

}

func TestDeleteBucket(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-delete-bucket-"
	method := "DeleteBucket"

	around(t, method, bucketNamePrefix, "", nil)
}

func TestSetBucketPrivate(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-private-"
	method := "SetBucketPrivate"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPrivate(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketPublicRead(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-public-read-"
	method := "SetBucketPublicRead"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPublicRead(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketPublicReadWrite(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-public-rw-"
	method := "SetBucketPublicReadWrite"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPublicReadWrite(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestGetBucketAcl(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-get-bucket-acl-"
	method := "GetBucketAcl"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		_, err := bosClient.GetBucketAcl(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketAcl(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-acl-"
	method := "SetBucketAcl"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		bucketAcl := BucketAcl{
			AccessControlList: []Grant{
				Grant{
					Grantee: []BucketGrantee{
						BucketGrantee{Id: "ef5a4b19192f4931adcf0e12f82795e2"},
					},
					Permission: []string{"FULL_CONTROL"},
				},
			},
		}
		if err := bosClient.SetBucketAcl(bucketName, bucketAcl, nil); err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestPubObject(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-put-object-"
	method := "PutObject"
	objectKey := "put-object-from-string.txt"
	str := "Hello World 你好"

	around(t, method, bucketNamePrefix, objectKey, func(bucketName string) {
		_, err := bosClient.PutObject(bucketName, objectKey, str, nil, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestDeleteObject(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-delete-object-"
	method := "DeleteObject"
	objectKey := "put-object-from-string.txt"
	str := "Hello World 你好"

	around(t, method, bucketNamePrefix, objectKey, func(bucketName string) {
		_, err := bosClient.PutObject(bucketName, objectKey, str, nil, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func around(t *testing.T, method, bucketNamePrefix, objectKey string, f func(string)) {
	bucketName := bucketNamePrefix + strconv.Itoa(int(time.Now().Unix()))
	err := bosClient.CreateBucket(bucketName, nil)

	if err != nil {
		t.Error(test.Format(method+" at creating bucket", err.Error(), "nil"))
	} else {
		if f != nil {
			f(bucketName)

			if objectKey != "" {
				err = bosClient.DeleteObject(bucketName, objectKey, nil)

				if err != nil {
					t.Error(test.Format(method+" at deleting object", err.Error(), "nil"))
				}
			}
		}

		err = bosClient.DeleteBucket(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method+" at deleting bucket", err.Error(), "nil"))
		}
	}
}
<commit_msg>add unit test for ListObjects api<commit_after>package bos

import (
	"strconv"
	"testing"
	"time"

	"baidubce/test"
)

var bosClient = DefaultClient

func TestGetBucketLocation(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-get-bucket-location-"
	method := "GetBucketLocation"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		expected := "bj"
		location, _ := bosClient.GetBucketLocation(bucketName, nil)

		if location.LocationConstraint != expected {
			t.Error(test.Format(method, location.LocationConstraint, expected))
		}
	})
}

func TestListBuckets(t *testing.T) {
	_, err := bosClient.ListBuckets(nil)

	if err != nil {
		t.Error(test.Format("ListBuckets", err.Error(), "nil"))
	}
}

func TestCreateBucket(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-create-bucket-"
	method := "CreateBucket"

	around(t, method, bucketNamePrefix, "", nil)
}

func TestDoesBucketExist(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-does-bucket-exist-"
	method := "DoesBucketExist"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		expected := true
		exists, err := bosClient.DoesBucketExist(bucketName, nil)

		if err != nil {
			t.Error(test.Format(method, err.Error(), strconv.FormatBool(expected)))
		} else if exists != expected {
			t.Error(test.Format(method, strconv.FormatBool(exists), strconv.FormatBool(expected)))
		}
	})

}

func TestDeleteBucket(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-delete-bucket-"
	method := "DeleteBucket"

	around(t, method, bucketNamePrefix, "", nil)
}

func TestSetBucketPrivate(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-private-"
	method := "SetBucketPrivate"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPrivate(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketPublicRead(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-public-read-"
	method := "SetBucketPublicRead"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPublicRead(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketPublicReadWrite(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-public-rw-"
	method := "SetBucketPublicReadWrite"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		err := bosClient.SetBucketPublicReadWrite(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestGetBucketAcl(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-get-bucket-acl-"
	method := "GetBucketAcl"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		_, err := bosClient.GetBucketAcl(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestSetBucketAcl(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-set-bucket-acl-"
	method := "SetBucketAcl"

	around(t, method, bucketNamePrefix, "", func(bucketName string) {
		bucketAcl := BucketAcl{
			AccessControlList: []Grant{
				Grant{
					Grantee: []BucketGrantee{
						BucketGrantee{Id: "ef5a4b19192f4931adcf0e12f82795e2"},
					},
					Permission: []string{"FULL_CONTROL"},
				},
			},
		}
		if err := bosClient.SetBucketAcl(bucketName, bucketAcl, nil); err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestPubObject(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-put-object-"
	method := "PutObject"
	objectKey := "put-object-from-string.txt"
	str := "Hello World 你好"

	around(t, method, bucketNamePrefix, objectKey, func(bucketName string) {
		_, err := bosClient.PutObject(bucketName, objectKey, str, nil, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestDeleteObject(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-delete-object-"
	method := "DeleteObject"
	objectKey := "put-object-from-string.txt"
	str := "Hello World 你好"

	around(t, method, bucketNamePrefix, objectKey, func(bucketName string) {
		_, err := bosClient.PutObject(bucketName, objectKey, str, nil, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		}
	})
}

func TestListObjects(t *testing.T) {
	bucketNamePrefix := "baidubce-sdk-go-test-for-list-objects-"
	method := "ListObjects"
	objectKey := "put-object-from-string.txt"
	str := "Hello World 你好"

	around(t, method, bucketNamePrefix, objectKey, func(bucketName string) {
		_, err := bosClient.PutObject(bucketName, objectKey, str, nil, nil)
		if err != nil {
			t.Error(test.Format(method, err.Error(), "nil"))
		} else {
			listObjectResponse, err := bosClient.ListObjects(bucketName, nil)
			if err != nil {
				t.Error(test.Format(method, err.Error(), "nil"))
			} else if length := len(listObjectResponse.Contents); length != 1 {
				t.Error(test.Format(method, strconv.Itoa(length), "1"))
			}
		}
	})

}

func around(t *testing.T, method, bucketNamePrefix, objectKey string, f func(string)) {
	bucketName := bucketNamePrefix + strconv.Itoa(int(time.Now().Unix()))
	err := bosClient.CreateBucket(bucketName, nil)

	if err != nil {
		t.Error(test.Format(method+" at creating bucket", err.Error(), "nil"))
	} else {
		if f != nil {
			f(bucketName)

			if objectKey != "" {
				err = bosClient.DeleteObject(bucketName, objectKey, nil)

				if err != nil {
					t.Error(test.Format(method+" at deleting object", err.Error(), "nil"))
				}
			}
		}

		err = bosClient.DeleteBucket(bucketName, nil)
		if err != nil {
			t.Error(test.Format(method+" at deleting bucket", err.Error(), "nil"))
		}
	}
}
<|endoftext|>


<commit_before>package debug

import (
	"fmt"

	"github.com/davecgh/go-spew/spew"
	"github.com/jsimonetti/ldapserv/ldap"
	log "gopkg.in/inconshreveable/log15.v2"
)

type DebugBackend struct {
	Log log.Logger
}

func (DebugBackend) Dump(obj interface{}) {
	spew.Dump(obj)
}

func (d *DebugBackend) Add(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetAddRequest()
	fmt.Printf("ADD %#v\n", r)
	d.Dump(m)
	res := ldap.NewAddResponse(ldap.LDAPResultOperationsError)
	w.Write(res)
}

func (d *DebugBackend) Bind(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetBindRequest()
	fmt.Printf("BIND %#v\n", r)
	d.Dump(m)
	res := ldap.NewBindResponse(ldap.LDAPResultUnwillingToPerform)
	w.Write(res)
}

func (d *DebugBackend) Delete(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetDeleteRequest()
	fmt.Printf("DELETE %#v\n", r)
	d.Dump(m)
	res := ldap.NewDeleteResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) ExtendedRequest(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetExtendedRequest()
	fmt.Printf("EXTENDED %#v\n", r)
	d.Dump(m)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Modify(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetModifyRequest()
	fmt.Printf("MODIFY dn=%s\n", r.Object())
	d.Dump(m)
	res := ldap.NewModifyResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) ModifyDN(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Printf("MODIFYDN %#v\n", m)
	d.Dump(m)
	res := ldap.NewModifyResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) PasswordModify(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Printf("PASSWORD MODIFY %#v\n", m)
	d.Dump(m)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Search(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Printf("SEARCH %#v\n", m)
	d.Dump(m)
	res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Whoami(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Println("WHOAMI")
	d.Dump(m)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Abandon(w ldap.ResponseWriter, m *ldap.Message) {
	var req = m.GetAbandonRequest()
	fmt.Println("ABANDON %#v\n", m)
	d.Dump(m)
	// retreive the request to abandon, and send a abort signal to it
	if requestToAbandon, ok := m.Client.GetMessageByID(int(req)); ok {
		requestToAbandon.Abandon()
	}
}

func (d *DebugBackend) Compare(w ldap.ResponseWriter, m *ldap.Message) {
	//r := m.GetCompareRequest()
	fmt.Println("COMPARE %#v\n", m)
	d.Dump(m)
	res := ldap.NewCompareResponse(ldap.LDAPResultCompareTrue)
	w.Write(res)
}

func (d *DebugBackend) Extended(w ldap.ResponseWriter, m *ldap.Message) {
	//r := m.GetExtendedRequest()
	fmt.Println("EXTENDED %#v\n", m)
	d.Dump(m)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) NotFound(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Println("NotFound %#v\n", m)
	d.Dump(m)
	switch m.ProtocolOpType() {
	case ldap.ApplicationBindRequest:
		res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
		res.SetDiagnosticMessage("Default binding behavior set to return Success")

		w.Write(res)

	default:
		res := ldap.NewResponse(ldap.LDAPResultUnwillingToPerform)
		res.SetDiagnosticMessage("Operation not implemented by server")
		w.Write(res)
	}
}
<commit_msg>Add better dumping<commit_after>package debug

import (
	"fmt"

	"github.com/davecgh/go-spew/spew"
	"github.com/jsimonetti/ldapserv/ldap"
	log "gopkg.in/inconshreveable/log15.v2"
)

type DebugBackend struct {
	Log log.Logger
}

func (d *DebugBackend) Add(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetAddRequest()
	spew.Dump(r)
	res := ldap.NewAddResponse(ldap.LDAPResultOperationsError)
	w.Write(res)
}

func (d *DebugBackend) Bind(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetBindRequest()
	spew.Dump(r)
	res := ldap.NewBindResponse(ldap.LDAPResultUnwillingToPerform)
	w.Write(res)
}

func (d *DebugBackend) Delete(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetDeleteRequest()
	spew.Dump(r)
	res := ldap.NewDeleteResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) ExtendedRequest(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetExtendedRequest()
	spew.Dump(r)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Modify(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetModifyRequest()
	spew.Dump(r)
	res := ldap.NewModifyResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) ModifyDN(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetModifyRequest()
	fmt.Printf("MODIFYDN %#v\n", m)
	spew.Dump(r)
	res := ldap.NewModifyResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) PasswordModify(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetModifyRequest()
	fmt.Printf("PASSWORD MODIFY %#v\n", m)
	spew.Dump(r)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Search(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetSearchRequest()
	spew.Dump(r)
	res := ldap.NewSearchResultDoneResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Whoami(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetExtendedRequest()
	spew.Dump(r)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) Abandon(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetAbandonRequest()
	spew.Dump(m)
	// retreive the request to abandon, and send a abort signal to it
	if requestToAbandon, ok := m.Client.GetMessageByID(int(r)); ok {
		requestToAbandon.Abandon()
	}
}

func (d *DebugBackend) Compare(w ldap.ResponseWriter, m *ldap.Message) {
	//r := m.GetCompareRequest()
	fmt.Println("COMPARE %#v\n", m)
	spew.Dump(m)
	res := ldap.NewCompareResponse(ldap.LDAPResultCompareTrue)
	w.Write(res)
}

func (d *DebugBackend) Extended(w ldap.ResponseWriter, m *ldap.Message) {
	r := m.GetExtendedRequest()
	spew.Dump(r)
	res := ldap.NewExtendedResponse(ldap.LDAPResultSuccess)
	w.Write(res)
}

func (d *DebugBackend) NotFound(w ldap.ResponseWriter, m *ldap.Message) {
	fmt.Println("NotFound %#v\n", m)
	spew.Dump(m)
	switch m.ProtocolOpType() {
	case ldap.ApplicationBindRequest:
		res := ldap.NewBindResponse(ldap.LDAPResultSuccess)
		res.SetDiagnosticMessage("Default binding behavior set to return Success")

		w.Write(res)

	default:
		res := ldap.NewResponse(ldap.LDAPResultUnwillingToPerform)
		res.SetDiagnosticMessage("Operation not implemented by server")
		w.Write(res)
	}
}
<|endoftext|>


<commit_before>package configload

import (
	"fmt"

	"github.com/hashicorp/terraform/configs"
	"github.com/hashicorp/terraform/registry"
	"github.com/hashicorp/terraform/svchost/disco"
	"github.com/spf13/afero"
)

// A Loader instance is the main entry-point for loading configurations via
// this package.
//
// It extends the general config-loading functionality in the parent package
// "configs" to support installation of modules from remote sources and
// loading full configurations using modules that were previously installed.
type Loader struct {
	// parser is used to read configuration
	parser *configs.Parser

	// modules is used to install and locate descendent modules that are
	// referenced (directly or indirectly) from the root module.
	modules moduleMgr
}

// Config is used with NewLoader to specify configuration arguments for the
// loader.
type Config struct {
	// ModulesDir is a path to a directory where descendent modules are
	// (or should be) installed. (This is usually the
	// .terraform/modules directory, in the common case where this package
	// is being loaded from the main Terraform CLI package.)
	ModulesDir string

	// Services is the service discovery client to use when locating remote
	// module registry endpoints. If this is nil then registry sources are
	// not supported, which should be true only in specialized circumstances
	// such as in tests.
	Services *disco.Disco
}

// NewLoader creates and returns a loader that reads configuration from the
// real OS filesystem.
//
// The loader has some internal state about the modules that are currently
// installed, which is read from disk as part of this function. If that
// manifest cannot be read then an error will be returned.
func NewLoader(config *Config) (*Loader, error) {
	fs := afero.NewOsFs()
	parser := configs.NewParser(fs)
	reg := registry.NewClient(config.Services, nil)

	ret := &Loader{
		parser: parser,
		modules: moduleMgr{
			FS:         afero.Afero{Fs: fs},
			CanInstall: true,
			Dir:        config.ModulesDir,
			Services:   config.Services,
			Registry:   reg,
		},
	}

	err := ret.modules.readModuleManifestSnapshot()
	if err != nil {
		return nil, fmt.Errorf("failed to read module manifest: %s", err)
	}

	return ret, nil
}

// Parser returns the underlying parser for this loader.
//
// This is useful for loading other sorts of files than the module directories
// that a loader deals with, since then they will share the source code cache
// for this loader and can thus be shown as snippets in diagnostic messages.
func (l *Loader) Parser() *configs.Parser {
	return l.parser
}

// Sources returns the source code cache for the underlying parser of this
// loader. This is a shorthand for l.Parser().Sources().
func (l *Loader) Sources() map[string][]byte {
	return l.parser.Sources()
}
<commit_msg>configs/configload: Helper for recognizing a config dir (or not)<commit_after>package configload

import (
	"fmt"

	"github.com/hashicorp/terraform/configs"
	"github.com/hashicorp/terraform/registry"
	"github.com/hashicorp/terraform/svchost/disco"
	"github.com/spf13/afero"
)

// A Loader instance is the main entry-point for loading configurations via
// this package.
//
// It extends the general config-loading functionality in the parent package
// "configs" to support installation of modules from remote sources and
// loading full configurations using modules that were previously installed.
type Loader struct {
	// parser is used to read configuration
	parser *configs.Parser

	// modules is used to install and locate descendent modules that are
	// referenced (directly or indirectly) from the root module.
	modules moduleMgr
}

// Config is used with NewLoader to specify configuration arguments for the
// loader.
type Config struct {
	// ModulesDir is a path to a directory where descendent modules are
	// (or should be) installed. (This is usually the
	// .terraform/modules directory, in the common case where this package
	// is being loaded from the main Terraform CLI package.)
	ModulesDir string

	// Services is the service discovery client to use when locating remote
	// module registry endpoints. If this is nil then registry sources are
	// not supported, which should be true only in specialized circumstances
	// such as in tests.
	Services *disco.Disco
}

// NewLoader creates and returns a loader that reads configuration from the
// real OS filesystem.
//
// The loader has some internal state about the modules that are currently
// installed, which is read from disk as part of this function. If that
// manifest cannot be read then an error will be returned.
func NewLoader(config *Config) (*Loader, error) {
	fs := afero.NewOsFs()
	parser := configs.NewParser(fs)
	reg := registry.NewClient(config.Services, nil)

	ret := &Loader{
		parser: parser,
		modules: moduleMgr{
			FS:         afero.Afero{Fs: fs},
			CanInstall: true,
			Dir:        config.ModulesDir,
			Services:   config.Services,
			Registry:   reg,
		},
	}

	err := ret.modules.readModuleManifestSnapshot()
	if err != nil {
		return nil, fmt.Errorf("failed to read module manifest: %s", err)
	}

	return ret, nil
}

// Parser returns the underlying parser for this loader.
//
// This is useful for loading other sorts of files than the module directories
// that a loader deals with, since then they will share the source code cache
// for this loader and can thus be shown as snippets in diagnostic messages.
func (l *Loader) Parser() *configs.Parser {
	return l.parser
}

// Sources returns the source code cache for the underlying parser of this
// loader. This is a shorthand for l.Parser().Sources().
func (l *Loader) Sources() map[string][]byte {
	return l.parser.Sources()
}

// IsConfigDir returns true if and only if the given directory contains at
// least one Terraform configuration file. This is a wrapper around calling
// the same method name on the loader's parser.
func (l *Loader) IsConfigDir(path string) bool {
	return l.parser.IsConfigDir(path)
}
<|endoftext|>


<commit_before>package bdconfig

import (
	"fmt"
	"io"

	"jrubin.io/blamedns/bdconfig/bdtype"

	log "github.com/Sirupsen/logrus"
)

type LogConfig struct {
	File  bdtype.LogFile  `cli:",set log filename (stderr, stdout or any file name)"`
	Level bdtype.LogLevel `cli:",set log level (DEBUG, INFO, WARN, ERR)"`
}

func defaultLogConfig() LogConfig {
	return LogConfig{
		File:  bdtype.DefaultLogFile(),
		Level: bdtype.DefaultLogLevel(),
	}
}

func (cfg LogConfig) Write(w io.Writer) (int, error) {
	n, err := fmt.Fprintf(w, "[log]\n")
	if err != nil {
		return n, err
	}

	var o int
	l := log.Level(cfg.Level).String()
	if l != "unknown" {
		o, err = fmt.Fprintf(w, "level = \"%s\"\n", l)
		n += o
		if err != nil {
			return n, err
		}
	}

	if len(cfg.File.Name) > 0 {
		o, err = fmt.Fprintf(w, "file  = \"%s\"\n", cfg.File.Name)
		n += o
		if err != nil {
			return n, err
		}
	}

	return n, nil
}

func (cfg LogConfig) Init() {
	log.WithField("name", cfg.File.Name).Info("log location")
	log.SetOutput(cfg.File)

	if cfg.File.IsFile {
		log.SetFormatter(&log.TextFormatter{
			DisableColors: true,
		})
	}

	l := log.Level(cfg.Level)
	log.SetLevel(l)
	log.WithField("level", l).Debug("log level set")
}
<commit_msg>update log level usage<commit_after>package bdconfig

import (
	"fmt"
	"io"

	"jrubin.io/blamedns/bdconfig/bdtype"

	log "github.com/Sirupsen/logrus"
)

type LogConfig struct {
	File  bdtype.LogFile  `cli:",set log filename (stderr, stdout or any file name)"`
	Level bdtype.LogLevel `cli:",set log level (debug, info, warning, error)"`
}

func defaultLogConfig() LogConfig {
	return LogConfig{
		File:  bdtype.DefaultLogFile(),
		Level: bdtype.DefaultLogLevel(),
	}
}

func (cfg LogConfig) Write(w io.Writer) (int, error) {
	n, err := fmt.Fprintf(w, "[log]\n")
	if err != nil {
		return n, err
	}

	var o int
	l := log.Level(cfg.Level).String()
	if l != "unknown" {
		o, err = fmt.Fprintf(w, "level = \"%s\"\n", l)
		n += o
		if err != nil {
			return n, err
		}
	}

	if len(cfg.File.Name) > 0 {
		o, err = fmt.Fprintf(w, "file  = \"%s\"\n", cfg.File.Name)
		n += o
		if err != nil {
			return n, err
		}
	}

	return n, nil
}

func (cfg LogConfig) Init() {
	log.WithField("name", cfg.File.Name).Info("log location")
	log.SetOutput(cfg.File)

	if cfg.File.IsFile {
		log.SetFormatter(&log.TextFormatter{
			DisableColors: true,
		})
	}

	l := log.Level(cfg.Level)
	log.SetLevel(l)
	log.WithField("level", l).Debug("log level set")
}
<|endoftext|>


<commit_before>/*
 *    Copyright (C) 2014      Daniel 'grindhold' Brendle
 *                  2014-2017 Christian Muehlhaeuser
 *
 *	  This program is free software: you can redistribute it and/or modify
 *	  it under the terms of the GNU Affero General Public License as published
 *	  by the Free Software Foundation, either version 3 of the License, or
 *	  (at your option) any later version.
 *
 *	  This program is distributed in the hope that it will be useful,
 *	  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 *	  GNU Affero General Public License for more details.
 *
 *	  You should have received a copy of the GNU Affero General Public License
 *	  along with this program.	If not, see <http://www.gnu.org/licenses/>.
 *
 *	  Authors:
 *		Daniel 'grindhold' Brendle <grindhold@skarphed.org>
 *      Christian Muehlhaeuser <muesli@gmail.com>
 */

// Package rssbee is a Bee for handling RSS feeds.
package rssbee

import (
	"time"

	rss "github.com/jteeuwen/go-pkg-rss"

	"github.com/muesli/beehive/bees"
)

// RSSBee is a Bee for handling RSS feeds.
type RSSBee struct {
	bees.Bee

	url string
	// decides whether the next fetch should be skipped
	skipNextFetch bool

	eventChan chan bees.Event
}

func (mod *RSSBee) chanHandler(feed *rss.Feed, newchannels []*rss.Channel) {
	//fmt.Printf("%d new channel(s) in %s\n", len(newchannels), feed.Url)
}

func (mod *RSSBee) itemHandler(feed *rss.Feed, ch *rss.Channel, newitems []*rss.Item) {
	if mod.skipNextFetch == true {
		mod.skipNextFetch = false
		return
	}
	for i := range newitems {
		var links []string
		var categories []string
		var enclosures []string

		for j := range newitems[i].Links {
			links = append(links, newitems[i].Links[j].Href)
		}

		for j := range newitems[i].Categories {
			categories = append(categories, newitems[i].Categories[j].Text)
		}

		for j := range newitems[i].Enclosures {
			enclosures = append(enclosures, newitems[i].Enclosures[j].Url)
		}

		newitemEvent := bees.Event{
			Bee:  mod.Name(),
			Name: "new_item",
			Options: []bees.Placeholder{
				{
					Name:  "title",
					Type:  "string",
					Value: newitems[i].Title,
				},
				{
					Name:  "links",
					Type:  "[]string",
					Value: links,
				},
				{
					Name:  "description",
					Type:  "string",
					Value: newitems[i].Description,
				},
				{
					Name:  "author",
					Type:  "string",
					Value: newitems[i].Author.Name,
				},
				{
					Name:  "categories",
					Type:  "[]string",
					Value: categories,
				},
				{
					Name:  "comments",
					Type:  "string",
					Value: newitems[i].Comments,
				},
				{
					Name:  "enclosures",
					Type:  "[]string",
					Value: enclosures,
				},
				{
					Name:  "guid",
					Type:  "string",
					Value: newitems[i].Guid,
				},
				{
					Name:  "pubdate",
					Type:  "string",
					Value: newitems[i].PubDate,
				},
			},
		}
		if newitems[i].Source != nil {
			ph := bees.Placeholder{
				Name:  "source",
				Type:  "string",
				Value: newitems[i].Source.Url,
			}

			newitemEvent.Options = append(newitemEvent.Options, ph)
		}

		mod.eventChan <- newitemEvent
	}
	mod.Logf("%d new item(s) in %s", len(newitems), feed.Url)
}

func (mod *RSSBee) pollFeed(uri string, timeout int) {
	feed := rss.New(timeout, true, mod.chanHandler, mod.itemHandler)

	wait := time.Duration(0)
	for {
		select {
		case <-mod.SigChan:
			return

		case <-time.After(wait):
			if err := feed.Fetch(uri, nil); err != nil {
				mod.LogErrorf("%s: %s", uri, err)
			}
		}

		wait = time.Duration(feed.SecondsTillUpdate() * 1e9)
	}
}

// Run executes the Bee's event loop.
func (mod *RSSBee) Run(cin chan bees.Event) {
	mod.eventChan = cin

	time.Sleep(10 * time.Second)
	mod.pollFeed(mod.url, 5)
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *RSSBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	options.Bind("skip_first", &mod.skipNextFetch)
	options.Bind("url", &mod.url)
}
<commit_msg>github.com/jteeuwen/go-pkg-rss is dead, changed import to github.com/muesli/go-pkg-rss<commit_after>/*
 *    Copyright (C) 2014      Daniel 'grindhold' Brendle
 *                  2014-2017 Christian Muehlhaeuser
 *
 *	  This program is free software: you can redistribute it and/or modify
 *	  it under the terms of the GNU Affero General Public License as published
 *	  by the Free Software Foundation, either version 3 of the License, or
 *	  (at your option) any later version.
 *
 *	  This program is distributed in the hope that it will be useful,
 *	  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 *	  GNU Affero General Public License for more details.
 *
 *	  You should have received a copy of the GNU Affero General Public License
 *	  along with this program.	If not, see <http://www.gnu.org/licenses/>.
 *
 *	  Authors:
 *		Daniel 'grindhold' Brendle <grindhold@skarphed.org>
 *      Christian Muehlhaeuser <muesli@gmail.com>
 */

// Package rssbee is a Bee for handling RSS feeds.
package rssbee

import (
	"time"

	rss "github.com/muesli/go-pkg-rss"

	"github.com/muesli/beehive/bees"
)

// RSSBee is a Bee for handling RSS feeds.
type RSSBee struct {
	bees.Bee

	url string
	// decides whether the next fetch should be skipped
	skipNextFetch bool

	eventChan chan bees.Event
}

func (mod *RSSBee) chanHandler(feed *rss.Feed, newchannels []*rss.Channel) {
	//fmt.Printf("%d new channel(s) in %s\n", len(newchannels), feed.Url)
}

func (mod *RSSBee) itemHandler(feed *rss.Feed, ch *rss.Channel, newitems []*rss.Item) {
	if mod.skipNextFetch == true {
		mod.skipNextFetch = false
		return
	}
	for i := range newitems {
		var links []string
		var categories []string
		var enclosures []string

		for j := range newitems[i].Links {
			links = append(links, newitems[i].Links[j].Href)
		}

		for j := range newitems[i].Categories {
			categories = append(categories, newitems[i].Categories[j].Text)
		}

		for j := range newitems[i].Enclosures {
			enclosures = append(enclosures, newitems[i].Enclosures[j].Url)
		}

		newitemEvent := bees.Event{
			Bee:  mod.Name(),
			Name: "new_item",
			Options: []bees.Placeholder{
				{
					Name:  "title",
					Type:  "string",
					Value: newitems[i].Title,
				},
				{
					Name:  "links",
					Type:  "[]string",
					Value: links,
				},
				{
					Name:  "description",
					Type:  "string",
					Value: newitems[i].Description,
				},
				{
					Name:  "author",
					Type:  "string",
					Value: newitems[i].Author.Name,
				},
				{
					Name:  "categories",
					Type:  "[]string",
					Value: categories,
				},
				{
					Name:  "comments",
					Type:  "string",
					Value: newitems[i].Comments,
				},
				{
					Name:  "enclosures",
					Type:  "[]string",
					Value: enclosures,
				},
				{
					Name:  "guid",
					Type:  "string",
					Value: newitems[i].Guid,
				},
				{
					Name:  "pubdate",
					Type:  "string",
					Value: newitems[i].PubDate,
				},
			},
		}
		if newitems[i].Source != nil {
			ph := bees.Placeholder{
				Name:  "source",
				Type:  "string",
				Value: newitems[i].Source.Url,
			}

			newitemEvent.Options = append(newitemEvent.Options, ph)
		}

		mod.eventChan <- newitemEvent
	}
	mod.Logf("%d new item(s) in %s", len(newitems), feed.Url)
}

func (mod *RSSBee) pollFeed(uri string, timeout int) {
	feed := rss.New(timeout, true, mod.chanHandler, mod.itemHandler)

	wait := time.Duration(0)
	for {
		select {
		case <-mod.SigChan:
			return

		case <-time.After(wait):
			if err := feed.Fetch(uri, nil); err != nil {
				mod.LogErrorf("%s: %s", uri, err)
			}
		}

		wait = time.Duration(feed.SecondsTillUpdate() * 1e9)
	}
}

// Run executes the Bee's event loop.
func (mod *RSSBee) Run(cin chan bees.Event) {
	mod.eventChan = cin

	time.Sleep(10 * time.Second)
	mod.pollFeed(mod.url, 5)
}

// ReloadOptions parses the config options and initializes the Bee.
func (mod *RSSBee) ReloadOptions(options bees.BeeOptions) {
	mod.SetOptions(options)

	options.Bind("skip_first", &mod.skipNextFetch)
	options.Bind("url", &mod.url)
}
<|endoftext|>


<commit_before>package navitia

import (
	"context"
	"testing"

	"github.com/aabizri/navitia/types"
)

func Test_JourneyRequest_toUrl(t *testing.T) {
	// First an empty struct
	req, err := JourneyRequest{}.toURL()
	if err != nil {
		t.Errorf("failure: toURL returned error: %v", err)
	}
	if len(req) != 0 {
		t.Errorf("failure: toURL created fields for non-specified parameters")
	}
	t.Logf("Result: %v", req)
}

func Test_Journeys(t *testing.T) {
	if *apiKey == "" {
		t.Skip(skipNoKey)
	}

	ctx := context.Background()

	params := JourneyRequest{}
	coords := types.Coordinates{Latitude: 48.847002, Longitude: 2.377310}
	params.From = coords.ID()

	res, err := testSession.Journeys(ctx, params)
	t.Logf("Got results: \n%s", res.String())
	if err != nil {
		t.Fatalf("Got error in Journey(): %v\n\tParameters: %#v", err, params)
	}
}

func Test_Journeys_Paging(t *testing.T) {
	if *apiKey == "" {
		t.Skip(skipNoKey)
	}

	ctx := context.Background()

	params := JourneyRequest{
		From: types.Coordinates{Latitude: 48.842716, Longitude: 2.384471}.ID(), // 110 Avenue Daumesnil (Paris)
		To:   types.Coordinates{Latitude: 48.867305, Longitude: 2.352005}.ID(), // 10 Rue du Caire (Paris)
	}

	res, err := testSession.Journeys(ctx, params)
	t.Logf("Got results: \n%s", res.String())
	t.Logf("Paging: %#v", res.Paging)
	if err != nil {
		t.Fatalf("Got error in Journey(): %v\n\tParameters: %#v", err, params)
	}

	for i := 0; res.Paging.Next != nil && i < 6; i++ {
		p := JourneyResults{}
		err = res.Paging.Next(ctx, testSession, &p)
		t.Logf("Next (#%d) results:\n%s", i, p.String())
		if err != nil {
			t.Fatalf("Got error in Paging.Next (pass %d): %v", i, err)
		}
		res = &p
	}
}
<commit_msg>Fix tests for last commit<commit_after>package navitia

import (
	"context"
	"testing"

	"github.com/aabizri/navitia/types"
)

func Test_JourneyRequest_toUrl(t *testing.T) {
	// First an empty struct
	req, err := JourneyRequest{}.toURL()
	if err != nil {
		t.Errorf("failure: toURL returned error: %v", err)
	}
	if len(req) != 0 {
		t.Errorf("failure: toURL created fields for non-specified parameters")
	}
	t.Logf("Result: %v", req)
}

func Test_Journeys(t *testing.T) {
	if *apiKey == "" {
		t.Skip(skipNoKey)
	}

	ctx := context.Background()

	params := JourneyRequest{}
	coords := types.Coordinates{Latitude: 48.847002, Longitude: 2.377310}
	params.From = coords.ID()

	res, err := testSession.Journeys(ctx, params)
	t.Logf("Got results: \n%#v", res)
	if err != nil {
		t.Fatalf("Got error in Journey(): %v\n\tParameters: %#v", err, params)
	}
}

func Test_Journeys_Paging(t *testing.T) {
	if *apiKey == "" {
		t.Skip(skipNoKey)
	}

	ctx := context.Background()

	params := JourneyRequest{
		From: types.Coordinates{Latitude: 48.842716, Longitude: 2.384471}.ID(), // 110 Avenue Daumesnil (Paris)
		To:   types.Coordinates{Latitude: 48.867305, Longitude: 2.352005}.ID(), // 10 Rue du Caire (Paris)
	}

	res, err := testSession.Journeys(ctx, params)
	t.Logf("Got results: \n%#v", res)
	t.Logf("Paging: %#v", res.Paging)
	if err != nil {
		t.Fatalf("Got error in Journey(): %v\n\tParameters: %#v", err, params)
	}

	for i := 0; res.Paging.Next != nil && i < 6; i++ {
		p := JourneyResults{}
		err = res.Paging.Next(ctx, testSession, &p)
		t.Logf("Next (#%d) results:\n%#v", i, p)
		if err != nil {
			t.Fatalf("Got error in Paging.Next (pass %d): %v", i, err)
		}
		res = &p
	}
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Marty Schoch
//  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 gouchstore

import "errors"

// Interface for writing bulk data into couchstore.
// Migrated to facilitate Seriesly, re-evaluate overall API

type BulkWriter interface {
	// Set a document.
	Set(*DocumentInfo, *Document)
	// Delete a document.
	Delete(*DocumentInfo)
	// Commit the current batch.
	Commit() error
	// Shut down this bulk interface.
	Close() error
}

type instr struct {
	di  *DocumentInfo
	doc *Document
}

type bulkWriter struct {
	update chan instr
	quit   chan bool
	commit chan chan error
}

func (b *bulkWriter) Close() error {
	close(b.quit)
	return nil
}

var errClosed = errors.New("db is closed")

func (b *bulkWriter) Commit() error {
	ch := make(chan error)
	select {
	case b.commit <- ch:
		return <-ch
	case <-b.quit:
		return errClosed
	}
}

func (b *bulkWriter) Set(di *DocumentInfo, doc *Document) {
	b.update <- instr{di, doc}
}

func (b *bulkWriter) Delete(di *DocumentInfo) {
	di.Deleted = true
	b.update <- instr{di, nil}
}

func (db *Gouchstore) commitBulk(batch []instr) error {

	docs := make([]*Document, len(batch))
	docInfos := make([]*DocumentInfo, len(batch))
	for i := range batch {
		docs[i] = batch[i].doc
		docInfos[i] = batch[i].di
	}

	err := db.SaveDocuments(docs, docInfos)
	if err != nil {
		return err
	}

	return db.Commit()
}

// Get a bulk writer.
//
// You must call Close() on the bulk writer when you're done bulk
// writing.
func (db *Gouchstore) Bulk() BulkWriter {
	rv := &bulkWriter{
		make(chan instr),
		make(chan bool),
		make(chan chan error),
	}

	go func() {
		ever := true
		batch := make([]instr, 0, 100)
		for ever {
			select {
			case <-rv.quit:
				ever = false
			case req := <-rv.commit:
				req <- db.commitBulk(batch)
				batch = batch[:0]
			case i := <-rv.update:
				batch = append(batch, i)
			}
		}
	}()

	return rv
}
<commit_msg>if the batch contains no changes, don't call SaveDocuments<commit_after>//  Copyright (c) 2014 Marty Schoch
//  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 gouchstore

import (
	"errors"
)

// Interface for writing bulk data into couchstore.
// Migrated to facilitate Seriesly, re-evaluate overall API

type BulkWriter interface {
	// Set a document.
	Set(*DocumentInfo, *Document)
	// Delete a document.
	Delete(*DocumentInfo)
	// Commit the current batch.
	Commit() error
	// Shut down this bulk interface.
	Close() error
}

type instr struct {
	di  *DocumentInfo
	doc *Document
}

type bulkWriter struct {
	update chan instr
	quit   chan bool
	commit chan chan error
}

func (b *bulkWriter) Close() error {
	close(b.quit)
	return nil
}

var errClosed = errors.New("db is closed")

func (b *bulkWriter) Commit() error {
	ch := make(chan error)
	select {
	case b.commit <- ch:
		return <-ch
	case <-b.quit:
		return errClosed
	}
}

func (b *bulkWriter) Set(di *DocumentInfo, doc *Document) {
	b.update <- instr{di, doc}
}

func (b *bulkWriter) Delete(di *DocumentInfo) {
	di.Deleted = true
	b.update <- instr{di, nil}
}

func (db *Gouchstore) commitBulk(batch []instr) error {

	docs := make([]*Document, len(batch))
	docInfos := make([]*DocumentInfo, len(batch))
	for i := range batch {
		docs[i] = batch[i].doc
		docInfos[i] = batch[i].di
	}

	if len(docs) > 0 {
		err := db.SaveDocuments(docs, docInfos)
		if err != nil {
			return err
		}
	}

	return db.Commit()
}

// Get a bulk writer.
//
// You must call Close() on the bulk writer when you're done bulk
// writing.
func (db *Gouchstore) Bulk() BulkWriter {
	rv := &bulkWriter{
		make(chan instr),
		make(chan bool),
		make(chan chan error),
	}

	go func() {
		ever := true
		batch := make([]instr, 0, 100)
		for ever {

			select {
			case <-rv.quit:
				ever = false
			case req := <-rv.commit:
				req <- db.commitBulk(batch)
				batch = batch[:0]
			case i := <-rv.update:
				batch = append(batch, i)
			}
		}
	}()

	return rv
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/epeli/hooktftp/config"
	"github.com/epeli/hooktftp/hooks"
	"github.com/epeli/hooktftp/tftp"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"syscall"
	"time"
)

var HOOKS []hooks.Hook
var CONFIG_PATH string = "/etc/hooktftp.yml"

func handleRRQ(res *tftp.RRQresponse) {

	started := time.Now()

	path := res.Request.Path

	fmt.Println(
		"GET", path,
		"blocksize", res.Request.Blocksize,
		"from", *res.Request.Addr,
	)

	if err := res.WriteOACK(); err != nil {
		fmt.Println("Failed to write OACK", err)
		return
	}

	var reader io.ReadCloser
	for _, hook := range HOOKS {
		var err error
		reader, err = hook(res.Request.Path)
		if err == hooks.NO_MATCH {
			continue
		} else if err != nil {

			if err, ok := err.(*os.PathError); ok {
				res.WriteError(tftp.NOT_FOUND, err.Error())
				return
			}

			fmt.Printf("Failed to execute hook for '%v' error: %v", res.Request.Path, err)
			res.WriteError(tftp.UNKNOWN_ERROR, "Hook failed: "+err.Error())
			return
		}
		defer func() {
			err := reader.Close()
			if err != nil {
				fmt.Println("Failed to close reader for", res.Request.Path, err)
			}
		}()
		break
	}

	if reader == nil {
		res.WriteError(tftp.NOT_FOUND, "No hook matches")
		return
	}

	b := make([]byte, res.Request.Blocksize)

	totalBytes := 0

	for {
		bytesRead, err := reader.Read(b)
		totalBytes += bytesRead

		if err == io.EOF {
			if _, err := res.Write(b[:bytesRead]); err != nil {
				fmt.Println("Failed to write last bytes of the reader", err)
				return
			}
			res.End()
			break
		} else if err != nil {
			fmt.Println("Error while reading", reader, err)
			res.WriteError(tftp.UNKNOWN_ERROR, err.Error())
			return
		}

		if _, err := res.Write(b[:bytesRead]); err != nil {
			fmt.Println("Failed to write bytes for", path, err)
			return
		}
	}

	took := time.Since(started)

	speed := float64(totalBytes) / took.Seconds() / 1024 / 1024

	fmt.Printf("Sent %v bytes in %v %f MB/s\n", totalBytes, took, speed)
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "\nUsage: %s [config]\n", os.Args[0])
		fmt.Println("\n    See https://github.com/epeli/hooktftp\n")
	}
	flag.Parse()

	if len(flag.Args()) > 0 {
		CONFIG_PATH = flag.Args()[0]
	}

	fmt.Println("Reading hooks from", CONFIG_PATH)

	configData, err := ioutil.ReadFile(CONFIG_PATH)

	if err != nil {
		fmt.Println("Failed to read config", err)
		return
	}

	conf, err := config.ParseYaml(configData)
	if err != nil {
		fmt.Println("Failed to parse config", err)
		return
	}

	for _, hookDef := range conf.HookDefs {
		fmt.Println("Compiling hook", hookDef)

		// Create new hookDef variable for the hookDef pointer for each loop
		// iteration. Go reuses the hookDef variable and if we pass pointer to
		// that terrible things happen.
		newPointer := hookDef
		hook, err := hooks.CompileHook(&newPointer)
		if err != nil {
			fmt.Println("Failed to compile hook", hookDef, err)
			return
		}
		HOOKS = append(HOOKS, hook)
	}

	addr, err := net.ResolveUDPAddr("udp", ":"+conf.Port)
	if err != nil {
		fmt.Println("Failed to resolve address", err)
		return
	}

	server, err := tftp.NewTFTPServer(addr)
	if err != nil {
		fmt.Println("Failed to listen", err)
		return
	}

	fmt.Println("Listening on", conf.Port)

	if conf.User != "" {
		err := DropPrivileges(conf.User)
		if err != nil {
			fmt.Printf("Failed to drop privileges to '%s' error: %v", conf.User, err)
			return
		}
		currentUser, _ := user.Current()
		fmt.Println("Dropped privileges to", currentUser)
	}

	if conf.User == "" && syscall.Getuid() == 0 {
		fmt.Println("!!!!!!!!!")
		fmt.Println("WARNING: Running as root and 'user' is not set in", CONFIG_PATH)
		fmt.Println("!!!!!!!!!")
	}

	for {
		res, err := server.Accept()
		if err != nil {
			fmt.Println("Bad tftp request", err)
			continue
		}

		go handleRRQ(res)
	}

}
<commit_msg>Use default port if not overridden by config.<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/epeli/hooktftp/config"
	"github.com/epeli/hooktftp/hooks"
	"github.com/epeli/hooktftp/tftp"
	"io"
	"io/ioutil"
	"net"
	"os"
	"os/user"
	"syscall"
	"time"
)

var HOOKS []hooks.Hook
var CONFIG_PATH string = "/etc/hooktftp.yml"

func handleRRQ(res *tftp.RRQresponse) {

	started := time.Now()

	path := res.Request.Path

	fmt.Println(
		"GET", path,
		"blocksize", res.Request.Blocksize,
		"from", *res.Request.Addr,
	)

	if err := res.WriteOACK(); err != nil {
		fmt.Println("Failed to write OACK", err)
		return
	}

	var reader io.ReadCloser
	for _, hook := range HOOKS {
		var err error
		reader, err = hook(res.Request.Path)
		if err == hooks.NO_MATCH {
			continue
		} else if err != nil {

			if err, ok := err.(*os.PathError); ok {
				res.WriteError(tftp.NOT_FOUND, err.Error())
				return
			}

			fmt.Printf("Failed to execute hook for '%v' error: %v", res.Request.Path, err)
			res.WriteError(tftp.UNKNOWN_ERROR, "Hook failed: "+err.Error())
			return
		}
		defer func() {
			err := reader.Close()
			if err != nil {
				fmt.Println("Failed to close reader for", res.Request.Path, err)
			}
		}()
		break
	}

	if reader == nil {
		res.WriteError(tftp.NOT_FOUND, "No hook matches")
		return
	}

	b := make([]byte, res.Request.Blocksize)

	totalBytes := 0

	for {
		bytesRead, err := reader.Read(b)
		totalBytes += bytesRead

		if err == io.EOF {
			if _, err := res.Write(b[:bytesRead]); err != nil {
				fmt.Println("Failed to write last bytes of the reader", err)
				return
			}
			res.End()
			break
		} else if err != nil {
			fmt.Println("Error while reading", reader, err)
			res.WriteError(tftp.UNKNOWN_ERROR, err.Error())
			return
		}

		if _, err := res.Write(b[:bytesRead]); err != nil {
			fmt.Println("Failed to write bytes for", path, err)
			return
		}
	}

	took := time.Since(started)

	speed := float64(totalBytes) / took.Seconds() / 1024 / 1024

	fmt.Printf("Sent %v bytes in %v %f MB/s\n", totalBytes, took, speed)
}

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "\nUsage: %s [config]\n", os.Args[0])
		fmt.Println("\n    See https://github.com/epeli/hooktftp\n")
	}
	flag.Parse()

	if len(flag.Args()) > 0 {
		CONFIG_PATH = flag.Args()[0]
	}

	fmt.Println("Reading hooks from", CONFIG_PATH)

	configData, err := ioutil.ReadFile(CONFIG_PATH)

	if err != nil {
		fmt.Println("Failed to read config", err)
		return
	}

	conf, err := config.ParseYaml(configData)
	if err != nil {
		fmt.Println("Failed to parse config", err)
		return
	}

	for _, hookDef := range conf.HookDefs {
		fmt.Println("Compiling hook", hookDef)

		// Create new hookDef variable for the hookDef pointer for each loop
		// iteration. Go reuses the hookDef variable and if we pass pointer to
		// that terrible things happen.
		newPointer := hookDef
		hook, err := hooks.CompileHook(&newPointer)
		if err != nil {
			fmt.Println("Failed to compile hook", hookDef, err)
			return
		}
		HOOKS = append(HOOKS, hook)
	}

	if conf.Port == "" {
		conf.Port = "69"
	}

	addr, err := net.ResolveUDPAddr("udp", ":"+conf.Port)
	if err != nil {
		fmt.Println("Failed to resolve address", err)
		return
	}

	server, err := tftp.NewTFTPServer(addr)
	if err != nil {
		fmt.Println("Failed to listen", err)
		return
	}

	fmt.Println("Listening on", conf.Port)

	if conf.User != "" {
		err := DropPrivileges(conf.User)
		if err != nil {
			fmt.Printf("Failed to drop privileges to '%s' error: %v", conf.User, err)
			return
		}
		currentUser, _ := user.Current()
		fmt.Println("Dropped privileges to", currentUser)
	}

	if conf.User == "" && syscall.Getuid() == 0 {
		fmt.Println("!!!!!!!!!")
		fmt.Println("WARNING: Running as root and 'user' is not set in", CONFIG_PATH)
		fmt.Println("!!!!!!!!!")
	}

	for {
		res, err := server.Accept()
		if err != nil {
			fmt.Println("Bad tftp request", err)
			continue
		}

		go handleRRQ(res)
	}

}
<|endoftext|>


<commit_before>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings (identity meaning no encoding), or return 406 Not
// Acceptable status if it can do neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
//
// Using the optimized gzip compressor by Klaus Post
//
// By default, httpgzip uses the standard library gzip implementation
// to minimize dependencies. However, there is an excellent optimized
// gzip implementation written by Klaus Post that can increase
// throughput. To use it instead, first install his compress library:
//
//   go get github.com/klauspost/compress
//
// and then install httpgzip using the "klauspost" build tag:
//
//   go install -tags klauspost xi2.org/x/httpgzip
//
// Credit is also due to Klaus for his blog post which inspired the
// creation of this package and is recommended reading:
//
//   https://blog.klauspost.com/gzip-performance-for-go-webservers/
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"xi2.org/x/httpgzip/internal/gzip"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import "compress/gzip".
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil && err == nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<commit_msg>Small change to documentation heading<commit_after>/*
   Copyright 2015 The Httpgzip Authors. See the AUTHORS file at the
   top-level directory of this distribution and at
   <https://xi2.org/x/httpgzip/m/AUTHORS>.

   This file is part of Httpgzip.

   Httpgzip is free software: you can redistribute it and/or modify it
   under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   Httpgzip is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Httpgzip.  If not, see <https://www.gnu.org/licenses/>.
*/

// Package httpgzip implements an http.Handler wrapper adding gzip
// compression for appropriate requests.
//
// It attempts to properly parse the request's Accept-Encoding header
// according to RFC 2616 and does not do a simple string search for
// "gzip" (which will fail to do the correct thing for values such as
// "*" or "identity,gzip;q=0"). It will serve either gzip or identity
// content codings (identity meaning no encoding), or return 406 Not
// Acceptable status if it can do neither.
//
// It works correctly with handlers which honour Range request headers
// (such as http.FileServer) by removing the Range header for requests
// which prefer gzip encoding. This is necessary since Range requests
// apply to the gzipped content but the wrapped handler is not aware
// of the compression when it writes byte ranges. The Accept-Ranges
// header is also stripped from corresponding responses.
//
// For requests which prefer gzip encoding a Content-Type header is
// set using http.DetectContentType if it is not set by the wrapped
// handler.
//
// Using an optimized gzip compressor
//
// By default, httpgzip uses the standard library gzip implementation
// to minimize dependencies. However, there is an excellent optimized
// gzip implementation written by Klaus Post that can increase
// throughput. To use it instead, first install his compress library:
//
//   go get github.com/klauspost/compress
//
// and then install httpgzip using the "klauspost" build tag:
//
//   go install -tags klauspost xi2.org/x/httpgzip
//
// Credit is also due to Klaus for his blog post which inspired the
// creation of this package and is recommended reading:
//
//   https://blog.klauspost.com/gzip-performance-for-go-webservers/
package httpgzip // import "xi2.org/x/httpgzip"

import (
	"bytes"
	"fmt"
	"mime"
	"net/http"
	"strconv"
	"strings"
	"sync"

	"xi2.org/x/httpgzip/internal/gzip"
)

// These constants are copied from the gzip package, so that code that
// imports this package does not also have to import "compress/gzip".
const (
	NoCompression      = gzip.NoCompression
	BestSpeed          = gzip.BestSpeed
	BestCompression    = gzip.BestCompression
	DefaultCompression = gzip.DefaultCompression
)

// DefaultContentTypes is the default list of content types for which
// a Handler considers gzip compression. This list originates from the
// file compression.conf within the Apache configuration found at
// https://html5boilerplate.com/.
var DefaultContentTypes = []string{
	"application/atom+xml",
	"application/javascript",
	"application/json",
	"application/ld+json",
	"application/manifest+json",
	"application/rdf+xml",
	"application/rss+xml",
	"application/schema+json",
	"application/vnd.geo+json",
	"application/vnd.ms-fontobject",
	"application/x-font-ttf",
	"application/x-javascript",
	"application/x-web-app-manifest+json",
	"application/xhtml+xml",
	"application/xml",
	"font/eot",
	"font/opentype",
	"image/bmp",
	"image/svg+xml",
	"image/vnd.microsoft.icon",
	"image/x-icon",
	"text/cache-manifest",
	"text/css",
	"text/html",
	"text/javascript",
	"text/plain",
	"text/vcard",
	"text/vnd.rim.location.xloc",
	"text/vtt",
	"text/x-component",
	"text/x-cross-domain-policy",
	"text/xml",
}

var gzipWriterPools = map[int]*sync.Pool{}

func init() {
	levels := map[int]struct{}{
		DefaultCompression: struct{}{},
		NoCompression:      struct{}{},
	}
	for i := BestSpeed; i <= BestCompression; i++ {
		levels[i] = struct{}{}
	}
	for k := range levels {
		level := k // create new variable for closure
		gzipWriterPools[level] = &sync.Pool{
			New: func() interface{} {
				w, _ := gzip.NewWriterLevel(nil, level)
				return w
			},
		}
	}
}

var gzipBufPool = sync.Pool{
	New: func() interface{} { return new(bytes.Buffer) },
}

// A gzipResponseWriter is a modified http.ResponseWriter. It adds
// gzip compression to certain responses, and there are two cases
// where this is done. Case 1 is when encs only allows gzip encoding
// and forbids identity. Case 2 is when encs prefers gzip encoding,
// the response is at least 512 bytes and the response's content type
// is in ctMap.
//
// A gzipResponseWriter sets the Content-Encoding and Content-Type
// headers when appropriate. It is important to call the Close method
// when writing is finished in order to flush and close the
// gzipResponseWriter. The slice encs must contain only encodings from
// {encGzip,encIdentity} and contain at least one encoding.
//
// If a gzip.Writer is used in order to write a response it will use a
// compression level of level.
type gzipResponseWriter struct {
	http.ResponseWriter
	httpStatus int
	ctMap      map[string]struct{}
	encs       []encoding
	level      int
	gw         *gzip.Writer
	buf        *bytes.Buffer
}

func newGzipResponseWriter(w http.ResponseWriter, ctMap map[string]struct{}, encs []encoding, level int) *gzipResponseWriter {
	buf := gzipBufPool.Get().(*bytes.Buffer)
	buf.Reset()
	return &gzipResponseWriter{
		ResponseWriter: w,
		httpStatus:     http.StatusOK,
		ctMap:          ctMap,
		encs:           encs,
		level:          level,
		buf:            buf}
}

// init gets called by Write once at least 512 bytes have been written
// to the temporary buffer buf, or by Close if it has not yet been
// called. Firstly it determines the content type, either from the
// Content-Type header, or by calling http.DetectContentType on
// buf. Then, if needed, a gzip.Writer is initialized. Lastly,
// appropriate headers are set and the ResponseWriter's WriteHeader
// method is called.
func (w *gzipResponseWriter) init() {
	cth := w.Header().Get("Content-Type")
	var ct string
	if cth != "" {
		ct = cth
	} else {
		ct = http.DetectContentType(w.buf.Bytes())
	}
	var gzipContentType bool
	if mt, _, err := mime.ParseMediaType(ct); err == nil {
		if _, ok := w.ctMap[mt]; ok {
			gzipContentType = true
		}
	}
	var useGzip bool
	if w.Header().Get("Content-Encoding") == "" && w.encs[0] == encGzip {
		if gzipContentType && w.buf.Len() >= 512 || len(w.encs) == 1 {
			useGzip = true
		}
	}
	if useGzip {
		w.gw = gzipWriterPools[w.level].Get().(*gzip.Writer)
		w.gw.Reset(w.ResponseWriter)
		w.Header().Del("Content-Length")
		w.Header().Set("Content-Encoding", "gzip")
	}
	w.Header().Del("Accept-Ranges")
	if cth == "" {
		w.Header().Set("Content-Type", ct)
	}
	w.ResponseWriter.WriteHeader(w.httpStatus)
}

func (w *gzipResponseWriter) Write(p []byte) (int, error) {
	var n, written int
	var err error
	if w.buf != nil {
		written = w.buf.Len()
		_, _ = w.buf.Write(p)
		if w.buf.Len() < 512 {
			return len(p), nil
		}
		w.init()
		p = w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
	}
	switch {
	case w.gw != nil:
		n, err = w.gw.Write(p)
	default:
		n, err = w.ResponseWriter.Write(p)
	}
	n -= written
	if n < 0 {
		n = 0
	}
	return n, err
}

func (w *gzipResponseWriter) WriteHeader(httpStatus int) {
	// postpone WriteHeader call until end of init method
	w.httpStatus = httpStatus
}

func (w *gzipResponseWriter) Close() (err error) {
	if w.buf != nil {
		w.init()
		p := w.buf.Bytes()
		defer func() {
			gzipBufPool.Put(w.buf)
			w.buf = nil
		}()
		switch {
		case w.gw != nil:
			_, err = w.gw.Write(p)
		default:
			_, err = w.ResponseWriter.Write(p)
		}
	}
	if w.gw != nil {
		e := w.gw.Close()
		if e != nil && err == nil {
			err = e
		}
		gzipWriterPools[w.level].Put(w.gw)
		w.gw = nil
	}
	return err
}

// An encoding is a supported content coding.
type encoding int

const (
	encIdentity encoding = iota
	encGzip
)

// acceptedEncodings returns the supported content codings that are
// accepted by the request r. It returns a slice of encodings in
// client preference order.
//
// If the Sec-WebSocket-Key header is present then compressed content
// encodings are not considered.
//
// ref: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
func acceptedEncodings(r *http.Request) []encoding {
	h := r.Header.Get("Accept-Encoding")
	swk := r.Header.Get("Sec-WebSocket-Key")
	if h == "" {
		return []encoding{encIdentity}
	}
	gzip := float64(-1)    // -1 means not accepted, 0 -> 1 means value of q
	identity := float64(0) // -1 means not accepted, 0 -> 1 means value of q
	for _, s := range strings.Split(h, ",") {
		f := strings.Split(s, ";")
		f0 := strings.ToLower(strings.Trim(f[0], " "))
		q := float64(1.0)
		if len(f) > 1 {
			f1 := strings.ToLower(strings.Trim(f[1], " "))
			if strings.HasPrefix(f1, "q=") {
				if flt, err := strconv.ParseFloat(f1[2:], 32); err == nil {
					if flt >= 0 && flt <= 1 {
						q = flt
					}
				}
			}
		}
		if (f0 == "gzip" || f0 == "*") && q > gzip && swk == "" {
			gzip = q
		}
		if (f0 == "gzip" || f0 == "*") && q == 0 {
			gzip = -1
		}
		if (f0 == "identity" || f0 == "*") && q > identity {
			identity = q
		}
		if (f0 == "identity" || f0 == "*") && q == 0 {
			identity = -1
		}
	}
	switch {
	case gzip == -1 && identity == -1:
		return []encoding{}
	case gzip == -1:
		return []encoding{encIdentity}
	case identity == -1:
		return []encoding{encGzip}
	case identity > gzip:
		return []encoding{encIdentity, encGzip}
	default:
		return []encoding{encGzip, encIdentity}
	}
}

// NewHandler returns a new http.Handler which wraps a handler h
// adding gzip compression to certain responses. There are two cases
// where gzip compression is done. Case 1 is responses whose requests
// only allow gzip encoding and forbid identity encoding (identity
// encoding meaning no encoding). Case 2 is responses whose requests
// prefer gzip encoding, whose size is at least 512 bytes and whose
// content types are in contentTypes. If contentTypes is nil then
// DefaultContentTypes is considered instead.
//
// The new http.Handler sets the Content-Encoding, Vary and
// Content-Type headers in its responses as appropriate. If a request
// expresses a preference for gzip encoding then any Range headers are
// removed from the request before it is passed through to h and
// Accept-Ranges headers are stripped from corresponding
// responses. This happens regardless of whether gzip encoding is
// eventually used in the response or not.
func NewHandler(h http.Handler, contentTypes []string) http.Handler {
	gzh, _ := NewHandlerLevel(h, contentTypes, DefaultCompression)
	return gzh
}

// NewHandlerLevel is like NewHandler but allows one to specify the
// gzip compression level instead of assuming DefaultCompression.
//
// The compression level can be DefaultCompression, NoCompression, or
// any integer value between BestSpeed and BestCompression
// inclusive. The error returned will be nil if the level is valid.
func NewHandlerLevel(h http.Handler, contentTypes []string, level int) (http.Handler, error) {
	switch {
	case level == DefaultCompression || level == NoCompression:
		// no action needed
	case level < BestSpeed || level > BestCompression:
		return nil, fmt.Errorf(
			"httpgzip: invalid compression level: %d", level)
	}
	if contentTypes == nil {
		contentTypes = DefaultContentTypes
	}
	ctMap := map[string]struct{}{}
	for _, ct := range contentTypes {
		ctMap[ct] = struct{}{}
	}
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// add Vary header
		w.Header().Add("Vary", "Accept-Encoding")
		// check client's accepted encodings
		encs := acceptedEncodings(r)
		// return if no acceptable encodings
		if len(encs) == 0 {
			w.WriteHeader(http.StatusNotAcceptable)
			return
		}
		if encs[0] == encGzip {
			// cannot accept Range requests for possibly gzipped
			// responses
			r.Header.Del("Range")
			// create new ResponseWriter
			w = newGzipResponseWriter(w, ctMap, encs, level)
			defer w.(*gzipResponseWriter).Close()
		}
		// call original handler's ServeHTTP
		h.ServeHTTP(w, r)
	}), nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Travis Keep. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or
// at http://opensource.org/licenses/BSD-3-Clause.

package gohue_test

import (
  "github.com/keep94/gohue"
  "testing"
)

func TestColorBlend(t *testing.T) {
  c1 := gohue.NewColor(0.3, 0.2)
  c2 := gohue.NewColor(0.2, 0.6)
  expected := gohue.NewColor(0.23, 0.48)
  if actual := c1.Blend(c2, 0.7); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
  expected = gohue.NewColor(0.2, 0.6)
  if actual := c1.Blend(c2, 1.0); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
  expected = gohue.NewColor(0.3, 0.2)
  if actual := c1.Blend(c2, 0.0); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
}

func TestColorXY(t *testing.T) {
  c := gohue.NewColor(0.0, 1.0)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
  c = gohue.NewColor(1.0, 0.0)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
  c = gohue.NewColor(7.0, 0.2)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
}

func TestMaybeColor(t *testing.T) {
  var m, c gohue.MaybeColor
  v := gohue.NewColor(0.4, 0.6)
  m.Set(v)
  if m != gohue.NewMaybeColor(v) {
    t.Error("MaybeColor Set broken.")
  }
  verifyString(t, "Just (0.4000, 0.6000)", m.String())
  m.Clear()
  if m != c {
    t.Error("MaybeColor Clear broken.")
  }
  verifyString(t, "Nothing", m.String())
}

func verifyString(t *testing.T, expected, actual string) {
  if expected != actual {
    t.Errorf("Expected %s, got %s", expected, actual);
  }
}
<commit_msg>Remove trailing semicolon<commit_after>// Copyright 2013 Travis Keep. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or
// at http://opensource.org/licenses/BSD-3-Clause.

package gohue_test

import (
  "github.com/keep94/gohue"
  "testing"
)

func TestColorBlend(t *testing.T) {
  c1 := gohue.NewColor(0.3, 0.2)
  c2 := gohue.NewColor(0.2, 0.6)
  expected := gohue.NewColor(0.23, 0.48)
  if actual := c1.Blend(c2, 0.7); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
  expected = gohue.NewColor(0.2, 0.6)
  if actual := c1.Blend(c2, 1.0); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
  expected = gohue.NewColor(0.3, 0.2)
  if actual := c1.Blend(c2, 0.0); actual != expected {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
}

func TestColorXY(t *testing.T) {
  c := gohue.NewColor(0.0, 1.0)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
  c = gohue.NewColor(1.0, 0.0)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
  c = gohue.NewColor(7.0, 0.2)
  if c != gohue.NewColor(c.X(), c.Y()) {
    t.Error("Round trip of X and Y failed.")
  }
}

func TestMaybeColor(t *testing.T) {
  var m, c gohue.MaybeColor
  v := gohue.NewColor(0.4, 0.6)
  m.Set(v)
  if m != gohue.NewMaybeColor(v) {
    t.Error("MaybeColor Set broken.")
  }
  verifyString(t, "Just (0.4000, 0.6000)", m.String())
  m.Clear()
  if m != c {
    t.Error("MaybeColor Clear broken.")
  }
  verifyString(t, "Nothing", m.String())
}

func verifyString(t *testing.T, expected, actual string) {
  if expected != actual {
    t.Errorf("Expected %s, got %s", expected, actual)
  }
}
<|endoftext|>