<commit_before>package envh

import (
	"fmt"
	"strings"

	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPopulateStructWithNoPointersGiven(t *testing.T) {
	type POPULATESTRUCT struct{}

	tree, err := NewEnvTree("test", "_")

	assert.NoError(t, err, "Must return no errors")

	err = populateStructFromEnvTree(POPULATESTRUCT{}, &tree, false)

	assert.EqualError(t, err, `Type "struct" is not supported : you must provide "pointer to struct"`)

	err = populateStructFromEnvTree(8, &tree, false)

	assert.EqualError(t, err, `Type "int" is not supported : you must provide "pointer to struct"`)
}

func TestPopulateStructWithInnerPointer(t *testing.T) {
	type TEST7 struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type TEST4 struct {
		TEST5 *string
		TEST6 TEST7
	}

	type POPULATESTRUCT struct {
		TEST2 string
		TEST3 TEST4
	}

	actual := POPULATESTRUCT{}

	tree, err := NewEnvTree("test", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	restoreEnvs()

	assert.EqualError(t, err, `Type "ptr" is not supported : you must provide "int32, float32, string, boolean or struct"`)
}

func TestPopulateStructWithTypeErrors(t *testing.T) {
	type TEST5 struct {
		TEST6 int
	}

	type POPULATESTRUCT struct {
		TEST1 float32
		TEST2 int
		TEST3 bool
		TEST4 TEST5
	}

	type g struct {
		init       func()
		checkError func(err error)
		teardown   func()
	}

	tests := []g{
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST1", "value1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value1" can't be converted to type "float"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST2", "value2")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value2" can't be converted to type "int"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST3", "value3")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value3" can't be converted to type "bool"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST4_TEST6", "value4")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value4" can't be converted to type "int"`)
			},
		},
	}

	for _, s := range tests {
		actual := POPULATESTRUCT{}

		s.init()

		tree, err := NewEnvTree("POPULATESTRUCT", "_")

		assert.NoError(t, err)

		err = populateStructFromEnvTree(&actual, &tree, false)
		s.checkError(err)
		restoreEnvs()
	}
}

func TestPopulateStructWithStrictCheckDisabled(t *testing.T) {
	type TEST7 struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type TEST4 struct {
		TEST5 string
		TEST6 TEST7
	}

	type POPULATESTRUCT struct {
		TEST2 string
		TEST3 TEST4
	}

	actual := POPULATESTRUCT{}

	setEnv("POPULATESTRUCT_TEST2", "value1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST8", "1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST9", "1.1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST10", "value test 10")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST11", "true")

	tree, err := NewEnvTree("POPULATESTRUCT", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	restoreEnvs()

	expected := POPULATESTRUCT{
		"value1",
		TEST4{
			"",
			TEST7{
				1,
				1.1,
				"value test 10",
				true,
			},
		},
	}

	assert.NoError(t, err)
	assert.Equal(t, expected, actual, "Must popuplate struct with value given by environment variables")
}

func TestPopulateStructWithStrictCheckEnabled(t *testing.T) {
	type POPULATESTRUCT struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type g struct {
		init       func()
		checkError func(err error)
		teardown   func()
	}

	tests := []g{
		{
			init: func() {
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
				setEnv("POPULATESTRUCT_TEST9", "1.1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
				setEnv("POPULATESTRUCT_TEST9", "1.1")
				setEnv("POPULATESTRUCT_TEST10", "test")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
	}

	for _, s := range tests {
		actual := POPULATESTRUCT{}

		s.init()

		tree, err := NewEnvTree("POPULATESTRUCT", "_")

		assert.NoError(t, err)

		err = populateStructFromEnvTree(&actual, &tree, true)
		s.checkError(err)
		restoreEnvs()
	}
}

type SUM struct {
	LEFTOPERAND  int
	RIGHTOPERAND int
	RESULT       int
}

func (s *SUM) Walk(tree *EnvTree, keyChain []string) (bool, error) {
	if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){
		"SUM_LEFTOPERAND": s.validateLeftOperand,
		"SUM_RESULT":      s.setResult,
	}[strings.Join(keyChain, "_")]; ok {
		return iterator(tree, keyChain)
	}

	return false, nil

}

func (s *SUM) setResult(tree *EnvTree, keyChain []string) (bool, error) {
	left, err := tree.FindInt("SUM", "LEFTOPERAND")

	if err != nil {
		return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`)
	}

	right, err := tree.FindInt("SUM", "RIGHTOPERAND")

	if err != nil {
		return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`)
	}

	s.RESULT = left + right

	return true, nil
}

func (s *SUM) validateLeftOperand(tree *EnvTree, keyChain []string) (bool, error) {
	val, err := tree.FindInt(keyChain...)

	if err != nil {
		return false, fmt.Errorf(`"SUM_LEFTOPERAND" can't be found`)
	}

	if val <= 0 {
		return false, fmt.Errorf(`"LEFTOPERAND" must be greater than 0`)
	}

	return false, nil
}

func TestPopulateStructWithCustomSet(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "1")
	setEnv("SUM_RIGHTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.NoError(t, err)

	expected := SUM{LEFTOPERAND: 1, RIGHTOPERAND: 2, RESULT: 3}

	assert.Equal(t, expected, actual, "Must set result field")

	restoreEnvs()
}

func TestPopulateStructWithCustomSetTriggeringAnError(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.EqualError(t, err, `Can't find "SUM_LEFTOPERAND"`, "Must bubble up an error from Populate function")

	restoreEnvs()
}

func TestPopulateStructWithCustomValidationTriggeringAnError(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "0")
	setEnv("SUM_RIGHTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.EqualError(t, err, `"LEFTOPERAND" must be greater than 0`, "Must validate data")

	restoreEnvs()
}

type FIXREFERENCEMESSUP struct {
	TEST1 struct {
		TEST2 struct {
			TEST3 struct {
				TEST4 string
				TEST5 string
				TEST6 string
			}
			TEST7 map[string]string
		}
		TEST8 map[string]map[string]string
	}
}

func (s *FIXREFERENCEMESSUP) Walk(tree *EnvTree, keyChain []string) (bool, error) {
	if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){
		"FIXREFERENCEMESSUP_TEST1_TEST2_TEST7": s.fillMap,
		"FIXREFERENCEMESSUP_TEST1_TEST8":       func(*EnvTree, []string) (bool, error) { return true, nil },
	}[strings.Join(keyChain, "_")]; ok {
		return iterator(tree, keyChain)
	}

	return false, nil
}

func (s *FIXREFERENCEMESSUP) fillMap(tree *EnvTree, keyChain []string) (bool, error) {
	s.TEST1.TEST2.TEST7 = map[string]string{}
	s.TEST1.TEST2.TEST7["hello"] = "world!"
	return true, nil
}

func TestFixReferenceMessUp(t *testing.T) {

	actual := FIXREFERENCEMESSUP{}

	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST4", "4")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST5", "5")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST6", "6")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST7_HELLO", "world!")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST8_TEST", "test")

	tree, err := NewEnvTree("FIXREFERENCEMESSUP", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.NoError(t, err)

	expected := FIXREFERENCEMESSUP{}
	expected.TEST1.TEST2.TEST3.TEST4 = "4"
	expected.TEST1.TEST2.TEST3.TEST5 = "5"
	expected.TEST1.TEST2.TEST3.TEST6 = "6"
	expected.TEST1.TEST2.TEST7 = map[string]string{}
	expected.TEST1.TEST2.TEST7["hello"] = "world!"

	assert.Equal(t, expected, actual)
}
<commit_msg>Remove unused fields<commit_after>package envh

import (
	"fmt"
	"strings"

	"github.com/stretchr/testify/assert"
	"testing"
)

func TestPopulateStructWithNoPointersGiven(t *testing.T) {
	type POPULATESTRUCT struct{}

	tree, err := NewEnvTree("test", "_")

	assert.NoError(t, err, "Must return no errors")

	err = populateStructFromEnvTree(POPULATESTRUCT{}, &tree, false)

	assert.EqualError(t, err, `Type "struct" is not supported : you must provide "pointer to struct"`)

	err = populateStructFromEnvTree(8, &tree, false)

	assert.EqualError(t, err, `Type "int" is not supported : you must provide "pointer to struct"`)
}

func TestPopulateStructWithInnerPointer(t *testing.T) {
	type TEST7 struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type TEST4 struct {
		TEST5 *string
		TEST6 TEST7
	}

	type POPULATESTRUCT struct {
		TEST2 string
		TEST3 TEST4
	}

	actual := POPULATESTRUCT{}

	tree, err := NewEnvTree("test", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	restoreEnvs()

	assert.EqualError(t, err, `Type "ptr" is not supported : you must provide "int32, float32, string, boolean or struct"`)
}

func TestPopulateStructWithTypeErrors(t *testing.T) {
	type TEST5 struct {
		TEST6 int
	}

	type POPULATESTRUCT struct {
		TEST1 float32
		TEST2 int
		TEST3 bool
		TEST4 TEST5
	}

	type g struct {
		init       func()
		checkError func(err error)
	}

	tests := []g{
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST1", "value1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value1" can't be converted to type "float"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST2", "value2")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value2" can't be converted to type "int"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST3", "value3")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value3" can't be converted to type "bool"`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST4_TEST6", "value4")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Value "value4" can't be converted to type "int"`)
			},
		},
	}

	for _, s := range tests {
		actual := POPULATESTRUCT{}

		s.init()

		tree, err := NewEnvTree("POPULATESTRUCT", "_")

		assert.NoError(t, err)

		err = populateStructFromEnvTree(&actual, &tree, false)
		s.checkError(err)
		restoreEnvs()
	}
}

func TestPopulateStructWithStrictCheckDisabled(t *testing.T) {
	type TEST7 struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type TEST4 struct {
		TEST5 string
		TEST6 TEST7
	}

	type POPULATESTRUCT struct {
		TEST2 string
		TEST3 TEST4
	}

	actual := POPULATESTRUCT{}

	setEnv("POPULATESTRUCT_TEST2", "value1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST8", "1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST9", "1.1")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST10", "value test 10")
	setEnv("POPULATESTRUCT_TEST3_TEST6_TEST11", "true")

	tree, err := NewEnvTree("POPULATESTRUCT", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	restoreEnvs()

	expected := POPULATESTRUCT{
		"value1",
		TEST4{
			"",
			TEST7{
				1,
				1.1,
				"value test 10",
				true,
			},
		},
	}

	assert.NoError(t, err)
	assert.Equal(t, expected, actual, "Must popuplate struct with value given by environment variables")
}

func TestPopulateStructWithStrictCheckEnabled(t *testing.T) {
	type POPULATESTRUCT struct {
		TEST8  int
		TEST9  float32
		TEST10 string
		TEST11 bool
	}

	type g struct {
		init       func()
		checkError func(err error)
	}

	tests := []g{
		{
			init: func() {
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
				setEnv("POPULATESTRUCT_TEST9", "1.1")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
		{
			init: func() {
				setEnv("POPULATESTRUCT_TEST8", "1")
				setEnv("POPULATESTRUCT_TEST9", "1.1")
				setEnv("POPULATESTRUCT_TEST10", "test")
			},
			checkError: func(err error) {
				assert.EqualError(t, err, `Variable not found`)
			},
		},
	}

	for _, s := range tests {
		actual := POPULATESTRUCT{}

		s.init()

		tree, err := NewEnvTree("POPULATESTRUCT", "_")

		assert.NoError(t, err)

		err = populateStructFromEnvTree(&actual, &tree, true)
		s.checkError(err)
		restoreEnvs()
	}
}

type SUM struct {
	LEFTOPERAND  int
	RIGHTOPERAND int
	RESULT       int
}

func (s *SUM) Walk(tree *EnvTree, keyChain []string) (bool, error) {
	if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){
		"SUM_LEFTOPERAND": s.validateLeftOperand,
		"SUM_RESULT":      s.setResult,
	}[strings.Join(keyChain, "_")]; ok {
		return iterator(tree, keyChain)
	}

	return false, nil

}

func (s *SUM) setResult(tree *EnvTree, keyChain []string) (bool, error) {
	left, err := tree.FindInt("SUM", "LEFTOPERAND")

	if err != nil {
		return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`)
	}

	right, err := tree.FindInt("SUM", "RIGHTOPERAND")

	if err != nil {
		return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`)
	}

	s.RESULT = left + right

	return true, nil
}

func (s *SUM) validateLeftOperand(tree *EnvTree, keyChain []string) (bool, error) {
	val, err := tree.FindInt(keyChain...)

	if err != nil {
		return false, fmt.Errorf(`"SUM_LEFTOPERAND" can't be found`)
	}

	if val <= 0 {
		return false, fmt.Errorf(`"LEFTOPERAND" must be greater than 0`)
	}

	return false, nil
}

func TestPopulateStructWithCustomSet(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "1")
	setEnv("SUM_RIGHTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.NoError(t, err)

	expected := SUM{LEFTOPERAND: 1, RIGHTOPERAND: 2, RESULT: 3}

	assert.Equal(t, expected, actual, "Must set result field")

	restoreEnvs()
}

func TestPopulateStructWithCustomSetTriggeringAnError(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.EqualError(t, err, `Can't find "SUM_LEFTOPERAND"`, "Must bubble up an error from Populate function")

	restoreEnvs()
}

func TestPopulateStructWithCustomValidationTriggeringAnError(t *testing.T) {
	setEnv("SUM_LEFTOPERAND", "0")
	setEnv("SUM_RIGHTOPERAND", "2")

	actual := SUM{}

	tree, err := NewEnvTree("SUM", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.EqualError(t, err, `"LEFTOPERAND" must be greater than 0`, "Must validate data")

	restoreEnvs()
}

type FIXREFERENCEMESSUP struct {
	TEST1 struct {
		TEST2 struct {
			TEST3 struct {
				TEST4 string
				TEST5 string
				TEST6 string
			}
			TEST7 map[string]string
		}
		TEST8 map[string]map[string]string
	}
}

func (s *FIXREFERENCEMESSUP) Walk(tree *EnvTree, keyChain []string) (bool, error) {
	if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){
		"FIXREFERENCEMESSUP_TEST1_TEST2_TEST7": s.fillMap,
		"FIXREFERENCEMESSUP_TEST1_TEST8":       func(*EnvTree, []string) (bool, error) { return true, nil },
	}[strings.Join(keyChain, "_")]; ok {
		return iterator(tree, keyChain)
	}

	return false, nil
}

func (s *FIXREFERENCEMESSUP) fillMap(tree *EnvTree, keyChain []string) (bool, error) {
	s.TEST1.TEST2.TEST7 = map[string]string{}
	s.TEST1.TEST2.TEST7["hello"] = "world!"
	return true, nil
}

func TestFixReferenceMessUp(t *testing.T) {

	actual := FIXREFERENCEMESSUP{}

	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST4", "4")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST5", "5")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST6", "6")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST7_HELLO", "world!")
	setEnv("FIXREFERENCEMESSUP_TEST1_TEST8_TEST", "test")

	tree, err := NewEnvTree("FIXREFERENCEMESSUP", "_")

	assert.NoError(t, err)

	err = populateStructFromEnvTree(&actual, &tree, false)

	assert.NoError(t, err)

	expected := FIXREFERENCEMESSUP{}
	expected.TEST1.TEST2.TEST3.TEST4 = "4"
	expected.TEST1.TEST2.TEST3.TEST5 = "5"
	expected.TEST1.TEST2.TEST3.TEST6 = "6"
	expected.TEST1.TEST2.TEST7 = map[string]string{}
	expected.TEST1.TEST2.TEST7["hello"] = "world!"

	assert.Equal(t, expected, actual)
}
<|endoftext|>


<commit_before>package retrying

import (
	"fmt"
	"math/rand"
	"reflect"
	"runtime"
	"time"

	"github.com/hashicorp/go-multierror"
)

// errors
var (
	ErrTimeout             = fmt.Errorf("timeout error")
	ErrNoFunctionSpecified = fmt.Errorf("no function is specified")
)

const (
	defaultStackSize       = 4096
	defaultMaxAttemptTimes = 1
)

var errorInterface = reflect.TypeOf((*error)(nil)).Elem()

// can be mocked out for test
var sleep = time.Sleep

// Retryable model consisting of retry options
type Retryable struct {
	stackSize     int
	allGoroutines bool

	maxAttemptTimes int
	maxDelay        time.Duration

	waitFixed                    time.Duration
	waitRandomMin, waitRandomMax time.Duration

	f func() error

	errors []error
}

// New create new retry
func New() *Retryable {
	return &Retryable{
		stackSize:       defaultStackSize,
		maxAttemptTimes: defaultMaxAttemptTimes,
		f:               func() error { return ErrNoFunctionSpecified },
	}
}

// Stack set stack parameters used in runtime.Stack
func (r *Retryable) Stack(n int, all bool) *Retryable {
	if n <= 0 {
		r.errors = append(r.errors, fmt.Errorf("stack size must be positive integer"))
	}
	r.stackSize = n
	r.allGoroutines = all
	return r
}

// MaxAttemptTimes set max attempt times
func (r *Retryable) MaxAttemptTimes(n int) *Retryable {
	if n <= 0 {
		r.errors = append(r.errors, fmt.Errorf("max attempt times must be positive integer"))
	}
	r.maxAttemptTimes = n
	return r
}

// MaxDelay set max delay duration
func (r *Retryable) MaxDelay(d time.Duration) *Retryable {
	if d <= 0 {
		r.errors = append(r.errors, fmt.Errorf("max delay must be positive duration"))
	}
	r.maxDelay = d
	return r
}

// WaitFixed set fixed wait duration
func (r *Retryable) WaitFixed(d time.Duration) *Retryable {
	if d <= 0 {
		r.errors = append(r.errors, fmt.Errorf("wait fixed must be positive duration"))
	}
	r.waitFixed = d
	return r
}

// WaitRandom set min/max random
func (r *Retryable) WaitRandom(min, max time.Duration) *Retryable {
	if min < 0 || max < 0 {
		r.errors = append(r.errors, fmt.Errorf("wait random min/max must be positive duration"))
	}
	if min >= max {
		r.errors = append(r.errors, fmt.Errorf("wait random min must be smaller than max"))
	}
	r.waitRandomMin, r.waitRandomMax = min, max
	return r
}

// Function set function
// i should be a function with no output or last output should be an error
func (r *Retryable) Function(i interface{}) *Retryable {
	typ := reflect.TypeOf(i)
	if kind := typ.Kind(); kind != reflect.Func {
		r.errors = append(r.errors, fmt.Errorf("expected type %v but get %v", reflect.Func, kind))
		return r
	}
	if n := typ.NumIn(); n != 0 {
		r.errors = append(r.errors, fmt.Errorf("expected 0 inputs but get %v", n))
	}
	if n := typ.NumOut(); n > 0 && !typ.Out(n-1).Implements(errorInterface) {
		r.errors = append(r.errors, fmt.Errorf("expected 0 output or last output implements error interface"))
	}

	val := reflect.ValueOf(i)
	switch typ.NumOut() {
	case 0:
		r.f = r.wrapRecoverFunc(func() error {
			val.Call(nil)
			return nil
		})
	default:
		r.f = r.wrapRecoverFunc(func() error {
			outputs := val.Call(nil)
			lastOutput := outputs[len(outputs)-1]
			if lastOutput.IsNil() {
				return nil
			}
			return lastOutput.Interface().(error)
		})
	}

	return r
}

// Try call the wrap function with retry options
func (r *Retryable) Try() error {
	errors := multierror.Append(nil, r.errors...)

	// stop if errors occur in initialization
	if err := errors.ErrorOrNil(); err != nil {
		return err
	}

	// try with or without timeout
	if r.maxDelay > 0 {
		return r.tryWithTimeout()
	}
	return r.tryWithoutTimeout()
}

// helpers
//
func (r *Retryable) wrapRecoverFunc(f func() error) func() error {
	return func() (err error) {
		defer func() {
			if e := recover(); e != nil {
				buf := make([]byte, r.stackSize)
				runtime.Stack(buf, r.allGoroutines)
				err = fmt.Errorf("%v\n%s\n", e, buf)
			}
		}()

		return f()
	}
}

func (r *Retryable) wait() {
	duration := r.waitFixed
	if duration <= 0 && r.waitRandomMax > r.waitRandomMin {
		duration = r.waitRandomMin + time.Duration(rand.Int63n(int64(r.waitRandomMax-r.waitRandomMin)))
	}
	sleep(duration)
}

func (r *Retryable) tryWithTimeout() error {
	errors := &multierror.Error{}
	errChan := make(chan error)
	timer := time.NewTimer(r.maxDelay)
	count := r.maxAttemptTimes

	go func() {
		for ; count > 0; count-- {
			errChan <- r.f()
			r.wait()
		}
	}()

	for {
		select {
		case err := <-errChan:
			errors = multierror.Append(errors, err)

			if err == nil {
				return nil
			}

			if count <= 0 {
				return errors.ErrorOrNil()
			}
		case <-timer.C:
			return ErrTimeout
		}
	}
}

func (r *Retryable) tryWithoutTimeout() error {
	errors := &multierror.Error{}

	for count := r.maxAttemptTimes; count > 0; count-- {
		err := r.f()
		errors = multierror.Append(errors, err)

		if err == nil {
			return nil
		}

		r.wait()
	}

	return errors.ErrorOrNil()
}
<commit_msg>Use buffer channel to avoid bugs<commit_after>package retrying

import (
	"fmt"
	"math/rand"
	"reflect"
	"runtime"
	"time"

	"github.com/hashicorp/go-multierror"
)

// errors
var (
	ErrTimeout             = fmt.Errorf("timeout error")
	ErrNoFunctionSpecified = fmt.Errorf("no function is specified")
)

const (
	defaultStackSize       = 4096
	defaultMaxAttemptTimes = 1
)

var errorInterface = reflect.TypeOf((*error)(nil)).Elem()

// can be mocked out for test
var sleep = time.Sleep

// Retryable model consisting of retry options
type Retryable struct {
	stackSize     int
	allGoroutines bool

	maxAttemptTimes int
	maxDelay        time.Duration

	waitFixed                    time.Duration
	waitRandomMin, waitRandomMax time.Duration

	f func() error

	errors []error
}

// New create new retry
func New() *Retryable {
	return &Retryable{
		stackSize:       defaultStackSize,
		maxAttemptTimes: defaultMaxAttemptTimes,
		f:               func() error { return ErrNoFunctionSpecified },
	}
}

// Stack set stack parameters used in runtime.Stack
func (r *Retryable) Stack(n int, all bool) *Retryable {
	if n <= 0 {
		r.errors = append(r.errors, fmt.Errorf("stack size must be positive integer"))
	}
	r.stackSize = n
	r.allGoroutines = all
	return r
}

// MaxAttemptTimes set max attempt times
func (r *Retryable) MaxAttemptTimes(n int) *Retryable {
	if n <= 0 {
		r.errors = append(r.errors, fmt.Errorf("max attempt times must be positive integer"))
	}
	r.maxAttemptTimes = n
	return r
}

// MaxDelay set max delay duration
func (r *Retryable) MaxDelay(d time.Duration) *Retryable {
	if d <= 0 {
		r.errors = append(r.errors, fmt.Errorf("max delay must be positive duration"))
	}
	r.maxDelay = d
	return r
}

// WaitFixed set fixed wait duration
func (r *Retryable) WaitFixed(d time.Duration) *Retryable {
	if d <= 0 {
		r.errors = append(r.errors, fmt.Errorf("wait fixed must be positive duration"))
	}
	r.waitFixed = d
	return r
}

// WaitRandom set min/max random
func (r *Retryable) WaitRandom(min, max time.Duration) *Retryable {
	if min < 0 || max < 0 {
		r.errors = append(r.errors, fmt.Errorf("wait random min/max must be positive duration"))
	}
	if min >= max {
		r.errors = append(r.errors, fmt.Errorf("wait random min must be smaller than max"))
	}
	r.waitRandomMin, r.waitRandomMax = min, max
	return r
}

// Function set function
// i should be a function with no output or last output should be an error
func (r *Retryable) Function(i interface{}) *Retryable {
	typ := reflect.TypeOf(i)
	if kind := typ.Kind(); kind != reflect.Func {
		r.errors = append(r.errors, fmt.Errorf("expected type %v but get %v", reflect.Func, kind))
		return r
	}
	if n := typ.NumIn(); n != 0 {
		r.errors = append(r.errors, fmt.Errorf("expected 0 inputs but get %v", n))
	}
	if n := typ.NumOut(); n > 0 && !typ.Out(n-1).Implements(errorInterface) {
		r.errors = append(r.errors, fmt.Errorf("expected 0 output or last output implements error interface"))
	}

	val := reflect.ValueOf(i)
	switch typ.NumOut() {
	case 0:
		r.f = r.wrapRecoverFunc(func() error {
			val.Call(nil)
			return nil
		})
	default:
		r.f = r.wrapRecoverFunc(func() error {
			outputs := val.Call(nil)
			lastOutput := outputs[len(outputs)-1]
			if lastOutput.IsNil() {
				return nil
			}
			return lastOutput.Interface().(error)
		})
	}

	return r
}

// Try call the wrap function with retry options
func (r *Retryable) Try() error {
	errors := multierror.Append(nil, r.errors...)

	// stop if errors occur in initialization
	if err := errors.ErrorOrNil(); err != nil {
		return err
	}

	// try with or without timeout
	if r.maxDelay > 0 {
		return r.tryWithTimeout()
	}
	return r.tryWithoutTimeout()
}

// helpers
//
func (r *Retryable) wrapRecoverFunc(f func() error) func() error {
	return func() (err error) {
		defer func() {
			if e := recover(); e != nil {
				buf := make([]byte, r.stackSize)
				runtime.Stack(buf, r.allGoroutines)
				err = fmt.Errorf("%v\n%s\n", e, buf)
			}
		}()

		return f()
	}
}

func (r *Retryable) wait() {
	duration := r.waitFixed
	if duration <= 0 && r.waitRandomMax > r.waitRandomMin {
		duration = r.waitRandomMin + time.Duration(rand.Int63n(int64(r.waitRandomMax-r.waitRandomMin)))
	}
	sleep(duration)
}

func (r *Retryable) tryWithTimeout() error {
	errors := &multierror.Error{}
	errChan := make(chan error, r.maxAttemptTimes)
	timer := time.NewTimer(r.maxDelay)
	count := r.maxAttemptTimes

	go func() {
		for ; count > 0; count-- {
			errChan <- r.f()
			r.wait()
		}
	}()

	for {
		select {
		case err := <-errChan:
			errors = multierror.Append(errors, err)

			if err == nil {
				return nil
			}

			if count <= 0 {
				return errors.ErrorOrNil()
			}
		case <-timer.C:
			return ErrTimeout
		}
	}
}

func (r *Retryable) tryWithoutTimeout() error {
	errors := &multierror.Error{}

	for count := r.maxAttemptTimes; count > 0; count-- {
		err := r.f()
		errors = multierror.Append(errors, err)

		if err == nil {
			return nil
		}

		r.wait()
	}

	return errors.ErrorOrNil()
}
<|endoftext|>


<commit_before>package yaesu

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"sync"
	"time"

	serial "github.com/tarm/serial"

	"github.com/dh1tw/remoteRotator/rotator"
)

// Yaesu is the implementation of the Yaesu GS232A/B rotator protocol
type Yaesu struct {
	sync.RWMutex
	name            string
	azimuthMin      int
	azimuthMax      int
	azimuthStop     int
	azimuthOverlap  bool
	elevationMin    int
	elevationMax    int
	azimuth         int
	azPreset        int
	elevation       int
	elPreset        int
	hasAzimuth      bool
	hasElevation    bool
	azInitialized   bool
	elInitialized   bool
	pollingInterval time.Duration
	pollingTicker   *time.Ticker
	eventHandler    func(rotator.Rotator, rotator.Heading)
	sp              io.ReadWriteCloser
	spPortName      string
	spBaudrate      int
	closeCh         chan struct{}
	errorCh         chan struct{}
	starter         sync.Once
	closer          sync.Once
	headingPattern  *regexp.Regexp
	watchdogTs      time.Time
}

// New creates a new Yaesu object which satisfies implicitly the
// rotator.Rotator interface. Configuration settings can be set through
// functional options.
// Default settings are:
// hasAzimuth: true,
// portname: /dev/ttyACM0,
// pollingInterval: 5sec,
// baudrate: 9600.
func New(opts ...func(*Yaesu)) (*Yaesu, error) {

	// regex Pattern [0-9]{4} -> 0310..etc
	headingPattern, err := regexp.Compile("[\\d]{4}")
	if err != nil {
		return nil, err
	}

	r := &Yaesu{
		hasAzimuth:      true,
		pollingInterval: time.Second * 5,
		spPortName:      "/dev/ttyACM0",
		spBaudrate:      9600,
		headingPattern:  headingPattern,
		azimuthMax:      450,
		elevationMax:    180,
		closeCh:         make(chan struct{}),
	}

	for _, opt := range opts {
		opt(r)
	}

	config := &serial.Config{
		Name:        r.spPortName,
		Baud:        r.spBaudrate,
		ReadTimeout: time.Millisecond * 100,
		Parity:      serial.ParityNone,
		Size:        8,
		StopBits:    1,
	}

	sp, err := serial.OpenPort(config)
	if err != nil {
		return nil, err
	}

	r.sp = sp

	go r.start()

	return r, nil
}

// Close shuts down the object
func (r *Yaesu) Close() {
	r.Lock()
	defer r.Unlock()
	if r.pollingTicker != nil {
		r.pollingTicker.Stop()
	}
	// makes sure that the serial port and the event loop just gets closed once
	r.closer.Do(func() {
		close(r.closeCh)
		r.sp.Close()
	})
}

// resetWatchdog resets the watchdog. This means that a packet has been
// received from the Yaesu rotator
func (r *Yaesu) resetWatchdog() {
	r.Lock()
	defer r.Unlock()
	r.watchdogTs = time.Now()
}

// checkWatchdog compares the watchdog timestamp with the current time
// and returns true if this value is greater than 5x updateInterval.
func (r *Yaesu) checkWatchdog() bool {
	r.Lock()
	defer r.Unlock()
	if time.Since(r.watchdogTs) > 5*r.pollingInterval {
		return true
	}
	return false
}

// Start the main event loop for the serial port.
// It will query the Yaesu rotator for the current heading (azimuth + elevation)
// with the pollingrate defined during initialization.
// A watchdog detects if the Yaesu rotator does not respond anymore.
// If an error occures, the errorCh will be closed.
// Consequently the communication will be shut down and the object
// prepared for garbage collection.
func (r *Yaesu) start() {
	defer r.Close()

	r.Lock()
	r.pollingTicker = time.NewTicker(r.pollingInterval)
	r.watchdogTs = time.Now()
	r.Unlock()

	for {
		select {
		case <-r.pollingTicker.C:
			if err := r.query(); err != nil {
				fmt.Println("serial port write error:", err)
				close(r.errorCh)
				return
			}
			if r.checkWatchdog() {
				fmt.Println("communication lost with Yaesu rotator")
				close(r.errorCh)
				return
			}
		// when closing has been signaled, stop polling and
		// reading from the serial port by exiting this function
		case <-r.closeCh:
			return
		default:
			// pass
		}

		msg, err := r.read()
		if err != nil {
			// serialport read is expected to timeout after 100ms
			// to unblock this routine
			if err == io.EOF {
				continue
			}
			fmt.Printf("serial port read error (%s on %s): %s\n",
				r.name, r.spPortName, err)
			close(r.errorCh)
			return // exit
		}
		r.resetWatchdog()
		r.parseMsg(msg)
	}
}

// read from the Yaesu rotator through this wrapper function
func (r *Yaesu) read() (string, error) {
	r.Lock()
	defer r.Unlock()
	return bufio.NewReader(r.sp).ReadString('\n')
}

// request Azimuth + Elevation from Yaesu rotator
func (r *Yaesu) query() error {
	//query azimuth + elevation
	r.Lock()
	defer r.Unlock()
	_, err := r.write([]byte("C2\r\n"))
	return err
}

// all functions write to the Yaesu rotator / serial port through this wrapper function
func (r *Yaesu) write(data []byte) (int, error) {
	return r.sp.Write(data)
}

// parseMsg checks the content of the received message from the Yaesu rotator
// and then further stores them and executes the event callback
func (r *Yaesu) parseMsg(msg string) {

	headings := []string{}
	gotNewValue := false

	if r.headingPattern != nil {
		headings = r.headingPattern.FindAllString(msg, -1)
	}

	r.Lock()
	defer r.Unlock()

	if len(headings) > 0 {
		//contains always 4 digits
		az, _ := strconv.Atoi(headings[0][1:]) //discard the first digit, since it's always 0

		if !r.azInitialized {
			r.azPreset = az
			r.azInitialized = true
			gotNewValue = true
		}

		if r.azimuth != az {
			r.azimuth = az
			gotNewValue = true
		}
	}

	if len(headings) == 2 {
		// contains always 4 digits
		el, _ := strconv.Atoi(headings[1][1:])

		if !r.elInitialized {
			r.elPreset = el
			r.elInitialized = true
		}

		if r.elevation != el {
			r.elevation = el
		}

		if r.eventHandler != nil && gotNewValue {
			// cb launched async to avoid deadlock on yaesu.*()
			go r.eventHandler(r, r.serialize().Heading)
		}
	}
}

// Name returns the name of the rotator
func (r *Yaesu) Name() string {
	r.RLock()
	defer r.RUnlock()
	return r.name
}

// Azimuth returns the current horizontal heading of the rotator in degrees
func (r *Yaesu) Azimuth() int {
	r.RLock()
	defer r.RUnlock()
	return r.azimuth
}

// AzPreset returns the horizontal heading (preset) to which the rotator
// shall turn to
func (r *Yaesu) AzPreset() int {
	r.RLock()
	defer r.RUnlock()
	return r.azPreset
}

// HasAzimuth returns a boolean value indicating if this rotator supports
// horizontal rotation
func (r *Yaesu) HasAzimuth() bool {
	r.RLock()
	defer r.RUnlock()
	return r.hasAzimuth
}

// SetAzimuth sets to value of the horizontal heading to which the
// rotator shall turn to. Allowed values are 0 ... 450. Values outside
// of this range will be clipped.
func (r *Yaesu) SetAzimuth(az int) error {
	r.Lock()
	defer r.Unlock()

	if !r.hasAzimuth {
		return nil
	}

	if az > 450 {
		az = 450
	}

	if az < 0 {
		az = 0
	}

	r.azPreset = az

	if _, err := r.write([]byte(fmt.Sprintf("M%.3d\r\n", az))); err != nil {
		return err
	}

	return nil
}

// Elevation returns the current vertical elevation of the rotator in degrees
func (r *Yaesu) Elevation() int {
	r.RLock()
	defer r.RUnlock()
	return r.elevation
}

// ElPreset returns the vertical elevation (preset) to which the rotator
// shall turn to
func (r *Yaesu) ElPreset() int {
	r.RLock()
	defer r.RUnlock()
	return r.elPreset
}

// HasElevation returns a boolean value indicating if this rotator supports
// vertical rotation
func (r *Yaesu) HasElevation() bool {
	r.RLock()
	defer r.RUnlock()
	return r.hasElevation
}

// SetElevation sets to value of the vertical elevation to which the
// rotator shall turn to. Allowed values are 0 ... 180. Values outside
// of this range will be clipped.
func (r *Yaesu) SetElevation(el int) error {
	r.RLock()
	defer r.RUnlock()

	if !r.hasElevation {
		return nil
	}

	if el > 180 {
		el = 180
	}

	if el < 0 {
		el = 0
	}

	r.elPreset = el

	if _, err := r.write([]byte(fmt.Sprintf("N%.3d\r\n",
		r.elPreset))); err != nil {
		return err
	}

	return nil
}

// Stop stops all rotator movement
func (r *Yaesu) Stop() error {
	r.Lock()
	defer r.Unlock()

	r.azPreset = r.azimuth
	r.elPreset = r.elevation

	if _, err := r.write([]byte("S\r\n")); err != nil {
		return err
	}

	return nil
}

// StopAzimuth stops horizontal rotator movement
func (r *Yaesu) StopAzimuth() error {
	r.Lock()
	defer r.Unlock()

	r.azPreset = r.azimuth

	if _, err := r.write([]byte("A\r\n")); err != nil {
		return err
	}

	return nil
}

// StopElevation stops vertical rotator movement
func (r *Yaesu) StopElevation() error {
	r.Lock()
	defer r.Unlock()

	r.elPreset = r.elevation

	if _, err := r.write([]byte("E\r\n")); err != nil {
		return err
	}

	return nil
}

// Serialize the data of the rotator
func (r *Yaesu) Serialize() rotator.Object {
	r.RLock()
	defer r.RUnlock()

	return r.serialize()
}

func (r *Yaesu) serialize() rotator.Object {

	obj := rotator.Object{
		Name: r.name,
		Heading: rotator.Heading{
			Azimuth:   r.azimuth,
			AzPreset:  r.azPreset,
			Elevation: r.elevation,
			ElPreset:  r.elPreset,
		},
		Config: rotator.Config{
			HasAzimuth:   r.hasAzimuth,
			AzimuthMax:   r.azimuthMax,
			AzimuthMin:   r.azimuthMin,
			AzimuthStop:  r.azimuthStop,
			HasElevation: r.hasElevation,
			ElevationMax: r.elevationMax,
			ElevationMin: r.elevationMin,
		},
	}

	return obj
}
<commit_msg>separated polling into a dedicated go routine<commit_after>package yaesu

import (
	"bufio"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"sync"
	"time"

	serial "github.com/tarm/serial"

	"github.com/dh1tw/remoteRotator/rotator"
)

// Yaesu is the implementation of the Yaesu GS232A/B rotator protocol
type Yaesu struct {
	sync.RWMutex
	name            string
	azimuthMin      int
	azimuthMax      int
	azimuthStop     int
	azimuthOverlap  bool
	elevationMin    int
	elevationMax    int
	azimuth         int
	azPreset        int
	elevation       int
	elPreset        int
	hasAzimuth      bool
	hasElevation    bool
	azInitialized   bool
	elInitialized   bool
	pollingInterval time.Duration
	pollingTicker   *time.Ticker
	eventHandler    func(rotator.Rotator, rotator.Heading)
	sp              io.ReadWriteCloser
	spRead          sync.Mutex
	spWrite         sync.Mutex
	spPortName      string
	spBaudrate      int
	closeCh         chan struct{}
	errorCh         chan struct{}
	starter         sync.Once
	closer          sync.Once
	headingPattern  *regexp.Regexp
	watchdogTs      time.Time
}

// New creates a new Yaesu object which satisfies implicitly the
// rotator.Rotator interface. Configuration settings can be set through
// functional options.
// Default settings are:
// hasAzimuth: true,
// portname: /dev/ttyACM0,
// pollingInterval: 5sec,
// baudrate: 9600.
func New(opts ...func(*Yaesu)) (*Yaesu, error) {

	// regex Pattern [0-9]{4} -> 0310..etc
	headingPattern, err := regexp.Compile("[\\d]{4}")
	if err != nil {
		return nil, err
	}

	r := &Yaesu{
		hasAzimuth:      true,
		pollingInterval: time.Second * 5,
		spPortName:      "/dev/ttyACM0",
		spBaudrate:      9600,
		headingPattern:  headingPattern,
		azimuthMax:      450,
		elevationMax:    180,
		closeCh:         make(chan struct{}),
	}

	for _, opt := range opts {
		opt(r)
	}

	config := &serial.Config{
		Name:        r.spPortName,
		Baud:        r.spBaudrate,
		ReadTimeout: time.Millisecond * 100,
		Parity:      serial.ParityNone,
		Size:        8,
		StopBits:    1,
	}

	sp, err := serial.OpenPort(config)
	if err != nil {
		return nil, err
	}

	r.sp = sp

	go r.start()

	return r, nil
}

// Close shuts down the object
func (r *Yaesu) Close() {
	r.Lock()
	defer r.Unlock()
	if r.pollingTicker != nil {
		r.pollingTicker.Stop()
	}
	// makes sure that the serial port and the event loop just gets closed once
	r.closer.Do(func() {
		close(r.closeCh)
		r.sp.Close()
	})
}

// resetWatchdog resets the watchdog. This means that a packet has been
// received from the Yaesu rotator
func (r *Yaesu) resetWatchdog() {
	r.Lock()
	defer r.Unlock()
	r.watchdogTs = time.Now()
}

// checkWatchdog compares the watchdog timestamp with the current time
// and returns true if this value is greater than 5x updateInterval.
func (r *Yaesu) checkWatchdog() bool {
	r.Lock()
	defer r.Unlock()
	if time.Since(r.watchdogTs) > 5*r.pollingInterval {
		return true
	}
	return false
}

// Start the main event loop for the serial port.
// It will query the Yaesu rotator for the current heading (azimuth + elevation)
// with the pollingrate defined during initialization.
// A watchdog detects if the Yaesu rotator does not respond anymore.
// If an error occures, the errorCh will be closed.
// Consequently the communication will be shut down and the object
// prepared for garbage collection.
func (r *Yaesu) start() {
	defer r.Close()

	r.Lock()
	r.pollingTicker = time.NewTicker(r.pollingInterval)
	r.watchdogTs = time.Now()
	r.Unlock()

	// start async polling
	go r.poll()

	for {
		select {
		// when closing has been signaled, stop reading
		// from the serial port by exiting this function
		case <-r.closeCh:
			return
		default:
		}

		// this is a blocking function which will run eventually
		// into a timeout if no data is received
		msg, err := r.read()
		if err != nil {
			// serialport read is expected to timeout after 100ms
			// to unblock this routine
			if err == io.EOF {
				continue
			}
			fmt.Printf("serial port read error (%s on %s): %s\n",
				r.name, r.spPortName, err)
			close(r.errorCh)
			return // exit
		}
		r.resetWatchdog()
		r.parseMsg(msg)
	}
}

// poll the Yaesu rotator for the current heading (azimuth + elevation)
func (r *Yaesu) poll() {
	defer r.Close()

	for {
		select {
		case <-r.pollingTicker.C:
			if err := r.query(); err != nil {
				fmt.Println("serial port write error:", err)
				close(r.errorCh)
				return
			}
			if r.checkWatchdog() {
				fmt.Println("communication lost with Yaesu rotator")
				close(r.errorCh)
				return
			}
		// when closing has been signaled, stop polling and return
		case <-r.closeCh:
			return
		}
	}
}

// read from the Yaesu rotator through this wrapper function
func (r *Yaesu) read() (string, error) {
	r.spRead.Lock()
	defer r.spRead.Unlock()
	return bufio.NewReader(r.sp).ReadString('\n')
}

// request Azimuth + Elevation from Yaesu rotator
func (r *Yaesu) query() error {
	//query azimuth + elevation
	_, err := r.write([]byte("C2\r\n"))
	return err
}

// all functions write to the Yaesu rotator / serial port through this wrapper function
func (r *Yaesu) write(data []byte) (int, error) {
	r.spWrite.Lock()
	defer r.spWrite.Unlock()
	return r.sp.Write(data)
}

// parseMsg checks the content of the received message from the Yaesu rotator
// and then further stores them and executes the event callback
func (r *Yaesu) parseMsg(msg string) {

	headings := []string{}
	gotNewValue := false

	if r.headingPattern != nil {
		headings = r.headingPattern.FindAllString(msg, -1)
	}

	r.Lock()
	defer r.Unlock()

	if len(headings) > 0 {
		//contains always 4 digits
		az, _ := strconv.Atoi(headings[0][1:]) //discard the first digit, since it's always 0

		if !r.azInitialized {
			r.azPreset = az
			r.azInitialized = true
			gotNewValue = true
		}

		if r.azimuth != az {
			r.azimuth = az
			gotNewValue = true
		}
	}

	if len(headings) == 2 {
		// contains always 4 digits
		el, _ := strconv.Atoi(headings[1][1:])

		if !r.elInitialized {
			r.elPreset = el
			r.elInitialized = true
		}

		if r.elevation != el {
			r.elevation = el
		}

		if r.eventHandler != nil && gotNewValue {
			// cb launched async to avoid deadlock on yaesu.*()
			go r.eventHandler(r, r.serialize().Heading)
		}
	}
}

// Name returns the name of the rotator
func (r *Yaesu) Name() string {
	r.RLock()
	defer r.RUnlock()
	return r.name
}

// Azimuth returns the current horizontal heading of the rotator in degrees
func (r *Yaesu) Azimuth() int {
	r.RLock()
	defer r.RUnlock()
	return r.azimuth
}

// AzPreset returns the horizontal heading (preset) to which the rotator
// shall turn to
func (r *Yaesu) AzPreset() int {
	r.RLock()
	defer r.RUnlock()
	return r.azPreset
}

// HasAzimuth returns a boolean value indicating if this rotator supports
// horizontal rotation
func (r *Yaesu) HasAzimuth() bool {
	r.RLock()
	defer r.RUnlock()
	return r.hasAzimuth
}

// SetAzimuth sets to value of the horizontal heading to which the
// rotator shall turn to. Allowed values are 0 ... 450. Values outside
// of this range will be clipped.
func (r *Yaesu) SetAzimuth(az int) error {
	r.Lock()
	defer r.Unlock()

	if !r.hasAzimuth {
		return nil
	}

	if az > 450 {
		az = 450
	}

	if az < 0 {
		az = 0
	}

	r.azPreset = az

	if _, err := r.write([]byte(fmt.Sprintf("M%.3d\r\n", az))); err != nil {
		return err
	}

	return nil
}

// Elevation returns the current vertical elevation of the rotator in degrees
func (r *Yaesu) Elevation() int {
	r.RLock()
	defer r.RUnlock()
	return r.elevation
}

// ElPreset returns the vertical elevation (preset) to which the rotator
// shall turn to
func (r *Yaesu) ElPreset() int {
	r.RLock()
	defer r.RUnlock()
	return r.elPreset
}

// HasElevation returns a boolean value indicating if this rotator supports
// vertical rotation
func (r *Yaesu) HasElevation() bool {
	r.RLock()
	defer r.RUnlock()
	return r.hasElevation
}

// SetElevation sets to value of the vertical elevation to which the
// rotator shall turn to. Allowed values are 0 ... 180. Values outside
// of this range will be clipped.
func (r *Yaesu) SetElevation(el int) error {
	r.RLock()
	defer r.RUnlock()

	if !r.hasElevation {
		return nil
	}

	if el > 180 {
		el = 180
	}

	if el < 0 {
		el = 0
	}

	r.elPreset = el

	if _, err := r.write([]byte(fmt.Sprintf("N%.3d\r\n",
		r.elPreset))); err != nil {
		return err
	}

	return nil
}

// Stop stops all rotator movement
func (r *Yaesu) Stop() error {
	r.Lock()
	defer r.Unlock()

	r.azPreset = r.azimuth
	r.elPreset = r.elevation

	if _, err := r.write([]byte("S\r\n")); err != nil {
		return err
	}

	return nil
}

// StopAzimuth stops horizontal rotator movement
func (r *Yaesu) StopAzimuth() error {
	r.Lock()
	defer r.Unlock()

	r.azPreset = r.azimuth

	if _, err := r.write([]byte("A\r\n")); err != nil {
		return err
	}

	return nil
}

// StopElevation stops vertical rotator movement
func (r *Yaesu) StopElevation() error {
	r.Lock()
	defer r.Unlock()

	r.elPreset = r.elevation

	if _, err := r.write([]byte("E\r\n")); err != nil {
		return err
	}

	return nil
}

// Serialize the data of the rotator
func (r *Yaesu) Serialize() rotator.Object {
	r.RLock()
	defer r.RUnlock()

	return r.serialize()
}

func (r *Yaesu) serialize() rotator.Object {

	obj := rotator.Object{
		Name: r.name,
		Heading: rotator.Heading{
			Azimuth:   r.azimuth,
			AzPreset:  r.azPreset,
			Elevation: r.elevation,
			ElPreset:  r.elPreset,
		},
		Config: rotator.Config{
			HasAzimuth:   r.hasAzimuth,
			AzimuthMax:   r.azimuthMax,
			AzimuthMin:   r.azimuthMin,
			AzimuthStop:  r.azimuthStop,
			HasElevation: r.hasElevation,
			ElevationMax: r.elevationMax,
			ElevationMin: r.elevationMin,
		},
	}

	return obj
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcs

import (
	"crypto/md5"
	"fmt"
	"io"
)

// A request to create an object, accepted by Bucket.CreateObject.
type CreateObjectRequest struct {
	// The name with which to create the object. This field must be set.
	//
	// Object names must:
	//
	// *  be non-empty.
	// *  be no longer than 1024 bytes.
	// *  be valid UTF-8.
	// *  not contain the code point U+000A (line feed).
	// *  not contain the code point U+000D (carriage return).
	//
	// See here for authoritative documentation:
	//     https://cloud.google.com/storage/docs/bucket-naming#objectnames
	Name string

	// Optional information with which to create the object. See here for more
	// information:
	//
	//     https://cloud.google.com/storage/docs/json_api/v1/objects#resource
	//
	ContentType     string
	ContentLanguage string
	ContentEncoding string
	CacheControl    string
	Metadata        map[string]string

	// A reader from which to obtain the contents of the object. Must be non-nil.
	Contents io.Reader

	// If non-nil, the object will not be created if the checksum of the received
	// contents does not match the supplied value.
	CRC32C *uint32

	// If non-nil, the object will not be created if the MD5 sum of the received
	// contents does not match the supplied value.
	MD5 *[md5.Size]byte

	// If non-nil, the object will be created/overwritten only if the current
	// generation for the object name is equal to the given value. Zero means the
	// object does not exist.
	GenerationPrecondition *int64

	// If non-nil, the object will be created/overwritten only if the current
	// meta-generation for the object name is equal to the given value. This is
	// only meaningful in conjunction with GenerationPrecondition.
	MetaGenerationPrecondition *int64
}

// A request to copy an object to a new name, preserving all metadata.
type CopyObjectRequest struct {
	SrcName string
	DstName string

	// The generation of the source object to copy, or zero for the latest
	// generation.
	SrcGeneration int64

	// If non-nil, the destination object will be created/overwritten only if the
	// current meta-generation for the source object is equal to the given value.
	//
	// This is probably only meaningful in conjunction with SrcGeneration.
	SrcMetaGenerationPrecondition *int64
}

// The maximum number of sources that a ComposeObjectsRequest may contain.
//
// Cf. https://cloud.google.com/storage/docs/composite-objects#_Compose
const MaxSourcesPerComposeRequest = 32

// The maximum number of components that a composite object may have. The sum
// of the component counts of the sources in a ComposeObjectsRequest must be no
// more than this value.
//
// Cf. https://cloud.google.com/storage/docs/composite-objects#_Count
const MaxComponentCount = 1024

// A request to compose one or more objects into a single composite object.
type ComposeObjectsRequest struct {
	// The name of the destination composite object.
	DstName string

	// If non-nil, the destination object will be created/overwritten only if the
	// current generation for its name is equal to the given value. Zero means
	// the object does not exist.
	DstGenerationPrecondition *int64

	// If non-nil, the destination object will be created/overwritten only if the
	// current meta-generation for its name is equal to the given value.
	//
	// This is only meaningful if DstGenerationPrecondition is also specified.
	DstMetaGenerationPrecondition *int64

	// The source objects from which to compose. This must be non-empty.
	//
	// Make sure to see the notes on MaxSourcesPerComposeRequest and
	// MaxComponentCount.
	Sources []ComposeSource

	// Optional information with which to create the object. See here for more
	// information:
	//
	//     https://cloud.google.com/storage/docs/json_api/v1/objects#resource
	//
	Metadata map[string]string
}

type ComposeSource struct {
	// The name of the source object.
	Name string

	// The generation of the source object to compose from. Zero means the latest
	// generation.
	Generation int64
}

// A [start, limit) range of bytes within an object.
//
// Semantics:
//
//  *  If Limit is less than or equal to Start, the range is treated as empty.
//
//  *  If Limit is greater than the length of the object, the range is
//     implicitly truncated.
//
type ByteRange struct {
	Start uint64
	Limit uint64
}

func (br ByteRange) String() string {
	return fmt.Sprintf("[%d, %d)", br.Start, br.Limit)
}

// A request to read the contents of an object at a particular generation.
type ReadObjectRequest struct {
	// The name of the object to read.
	Name string

	// The generation of the object to read. Zero means the latest generation.
	Generation int64

	// If present, limit the contents returned to a range within the object.
	Range *ByteRange
}

type StatObjectRequest struct {
	// The name of the object in question.
	Name string
}

type ListObjectsRequest struct {
	// List only objects whose names begin with this prefix.
	Prefix string

	// Collapse results based on a delimiter.
	//
	// If non-empty, enable the following behavior. For each run of one or more
	// objects whose names are of the form:
	//
	//     <Prefix><S><Delimiter><...>
	//
	// where <S> is a string that doesn't itself contain Delimiter and <...> is
	// anything, return a single Collaped entry in the listing consisting of
	//
	//     <Prefix><S><Delimiter>
	//
	// instead of one Object record per object. If a collapsed entry consists of
	// a large number of objects, this may be more efficient.
	Delimiter string

	// Used to continue a listing where a previous one left off. See
	// Listing.ContinuationToken for more information.
	ContinuationToken string

	// The maximum number of objects and collapsed runs to return. Fewer than
	// this number may actually be returned. If this is zero, a sensible default
	// is used.
	MaxResults int
}

// A set of objects and delimter-based collapsed runs returned by a call to
// ListObjects. See also ListObjectsRequest.
type Listing struct {
	// Records for objects matching the listing criteria.
	//
	// Guaranteed to be strictly increasing under a lexicographical comparison on
	// (name, generation) pairs.
	Objects []*Object

	// Collapsed entries for runs of names sharing a prefix followed by a
	// delimiter. See notes on ListObjectsRequest.Delimiter.
	//
	// Guaranteed to be strictly increasing.
	CollapsedRuns []string

	// A continuation token, for fetching more results.
	//
	// If non-empty, this listing does not represent the full set of matching
	// objects in the bucket. Call ListObjects again with the request's
	// ContinuationToken field set to this value to continue where you left off.
	//
	// Guarantees, for replies R1 and R2, with R2 continuing from R1:
	//
	//  *  All of R1's object names are strictly less than all object names and
	//     collapsed runs in R2.
	//
	//  *  All of R1's collapsed runs are strictly less than all object names and
	//     prefixes in R2.
	//
	// (Cf. Google-internal bug 19286144)
	//
	// Note that there is no guarantee of atomicity of listings. Objects written
	// and deleted concurrently with a single or multiple listing requests may or
	// may not be returned.
	ContinuationToken string
}

// A request to update the metadata of an object, accepted by
// Bucket.UpdateObject.
type UpdateObjectRequest struct {
	// The name of the object to update. Must be specified.
	Name string

	// The generation of the object to update. Zero means the latest generation.
	Generation int64

	// If non-nil, the request will fail without effect if there is an object
	// with the given name (and optionally generation), and its meta-generation
	// is not equal to this value.
	MetaGenerationPrecondition *int64

	// String fields in the object to update (or not). The semantics are as
	// follows, for a given field F:
	//
	//  *  If F is set to nil, the corresponding GCS object field is untouched.
	//
	//  *  If *F is the empty string, then the corresponding GCS object field is
	//     removed.
	//
	//  *  Otherwise, the corresponding GCS object field is set to *F.
	//
	//  *  There is no facility for setting a GCS object field to the empty
	//     string, since many of the fields do not actually allow that as a legal
	//     value.
	//
	// Note that the GCS object's content type field cannot be removed.
	ContentType     *string
	ContentEncoding *string
	ContentLanguage *string
	CacheControl    *string

	// User-provided metadata updates. Keys that are not mentioned are untouched.
	// Keys whose values are nil are deleted, and others are updated to the
	// supplied string. There is no facility for completely removing user
	// metadata.
	Metadata map[string]*string
}

// A request to delete an object by name. Non-existence is not treated as an
// error.
type DeleteObjectRequest struct {
	// The name of the object to delete. Must be specified.
	Name string

	// The generation of the object to delete. Zero means the latest generation.
	Generation int64

	// If non-nil, the request will fail without effect if there is an object
	// with the given name (and optionally generation), and its meta-generation
	// is not equal to this value.
	MetaGenerationPrecondition *int64
}
<commit_msg>Better define the semantics of ranges with Start > object length.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcs

import (
	"crypto/md5"
	"fmt"
	"io"
)

// A request to create an object, accepted by Bucket.CreateObject.
type CreateObjectRequest struct {
	// The name with which to create the object. This field must be set.
	//
	// Object names must:
	//
	// *  be non-empty.
	// *  be no longer than 1024 bytes.
	// *  be valid UTF-8.
	// *  not contain the code point U+000A (line feed).
	// *  not contain the code point U+000D (carriage return).
	//
	// See here for authoritative documentation:
	//     https://cloud.google.com/storage/docs/bucket-naming#objectnames
	Name string

	// Optional information with which to create the object. See here for more
	// information:
	//
	//     https://cloud.google.com/storage/docs/json_api/v1/objects#resource
	//
	ContentType     string
	ContentLanguage string
	ContentEncoding string
	CacheControl    string
	Metadata        map[string]string

	// A reader from which to obtain the contents of the object. Must be non-nil.
	Contents io.Reader

	// If non-nil, the object will not be created if the checksum of the received
	// contents does not match the supplied value.
	CRC32C *uint32

	// If non-nil, the object will not be created if the MD5 sum of the received
	// contents does not match the supplied value.
	MD5 *[md5.Size]byte

	// If non-nil, the object will be created/overwritten only if the current
	// generation for the object name is equal to the given value. Zero means the
	// object does not exist.
	GenerationPrecondition *int64

	// If non-nil, the object will be created/overwritten only if the current
	// meta-generation for the object name is equal to the given value. This is
	// only meaningful in conjunction with GenerationPrecondition.
	MetaGenerationPrecondition *int64
}

// A request to copy an object to a new name, preserving all metadata.
type CopyObjectRequest struct {
	SrcName string
	DstName string

	// The generation of the source object to copy, or zero for the latest
	// generation.
	SrcGeneration int64

	// If non-nil, the destination object will be created/overwritten only if the
	// current meta-generation for the source object is equal to the given value.
	//
	// This is probably only meaningful in conjunction with SrcGeneration.
	SrcMetaGenerationPrecondition *int64
}

// The maximum number of sources that a ComposeObjectsRequest may contain.
//
// Cf. https://cloud.google.com/storage/docs/composite-objects#_Compose
const MaxSourcesPerComposeRequest = 32

// The maximum number of components that a composite object may have. The sum
// of the component counts of the sources in a ComposeObjectsRequest must be no
// more than this value.
//
// Cf. https://cloud.google.com/storage/docs/composite-objects#_Count
const MaxComponentCount = 1024

// A request to compose one or more objects into a single composite object.
type ComposeObjectsRequest struct {
	// The name of the destination composite object.
	DstName string

	// If non-nil, the destination object will be created/overwritten only if the
	// current generation for its name is equal to the given value. Zero means
	// the object does not exist.
	DstGenerationPrecondition *int64

	// If non-nil, the destination object will be created/overwritten only if the
	// current meta-generation for its name is equal to the given value.
	//
	// This is only meaningful if DstGenerationPrecondition is also specified.
	DstMetaGenerationPrecondition *int64

	// The source objects from which to compose. This must be non-empty.
	//
	// Make sure to see the notes on MaxSourcesPerComposeRequest and
	// MaxComponentCount.
	Sources []ComposeSource

	// Optional information with which to create the object. See here for more
	// information:
	//
	//     https://cloud.google.com/storage/docs/json_api/v1/objects#resource
	//
	Metadata map[string]string
}

type ComposeSource struct {
	// The name of the source object.
	Name string

	// The generation of the source object to compose from. Zero means the latest
	// generation.
	Generation int64
}

// A [start, limit) range of bytes within an object.
//
// Semantics:
//
//  *  If Limit is less than or equal to Start, the range is treated as empty.
//
//  *  The effective range is [start, limit) intersected with [0, L), where L
//     is the length of the object.
//
//     For example, a read for [L-1, L+10) returns the last byte of the object,
//     and [L+2, L+10) is legal but returns nothing.
//
type ByteRange struct {
	Start uint64
	Limit uint64
}

func (br ByteRange) String() string {
	return fmt.Sprintf("[%d, %d)", br.Start, br.Limit)
}

// A request to read the contents of an object at a particular generation.
type ReadObjectRequest struct {
	// The name of the object to read.
	Name string

	// The generation of the object to read. Zero means the latest generation.
	Generation int64

	// If present, limit the contents returned to a range within the object.
	Range *ByteRange
}

type StatObjectRequest struct {
	// The name of the object in question.
	Name string
}

type ListObjectsRequest struct {
	// List only objects whose names begin with this prefix.
	Prefix string

	// Collapse results based on a delimiter.
	//
	// If non-empty, enable the following behavior. For each run of one or more
	// objects whose names are of the form:
	//
	//     <Prefix><S><Delimiter><...>
	//
	// where <S> is a string that doesn't itself contain Delimiter and <...> is
	// anything, return a single Collaped entry in the listing consisting of
	//
	//     <Prefix><S><Delimiter>
	//
	// instead of one Object record per object. If a collapsed entry consists of
	// a large number of objects, this may be more efficient.
	Delimiter string

	// Used to continue a listing where a previous one left off. See
	// Listing.ContinuationToken for more information.
	ContinuationToken string

	// The maximum number of objects and collapsed runs to return. Fewer than
	// this number may actually be returned. If this is zero, a sensible default
	// is used.
	MaxResults int
}

// A set of objects and delimter-based collapsed runs returned by a call to
// ListObjects. See also ListObjectsRequest.
type Listing struct {
	// Records for objects matching the listing criteria.
	//
	// Guaranteed to be strictly increasing under a lexicographical comparison on
	// (name, generation) pairs.
	Objects []*Object

	// Collapsed entries for runs of names sharing a prefix followed by a
	// delimiter. See notes on ListObjectsRequest.Delimiter.
	//
	// Guaranteed to be strictly increasing.
	CollapsedRuns []string

	// A continuation token, for fetching more results.
	//
	// If non-empty, this listing does not represent the full set of matching
	// objects in the bucket. Call ListObjects again with the request's
	// ContinuationToken field set to this value to continue where you left off.
	//
	// Guarantees, for replies R1 and R2, with R2 continuing from R1:
	//
	//  *  All of R1's object names are strictly less than all object names and
	//     collapsed runs in R2.
	//
	//  *  All of R1's collapsed runs are strictly less than all object names and
	//     prefixes in R2.
	//
	// (Cf. Google-internal bug 19286144)
	//
	// Note that there is no guarantee of atomicity of listings. Objects written
	// and deleted concurrently with a single or multiple listing requests may or
	// may not be returned.
	ContinuationToken string
}

// A request to update the metadata of an object, accepted by
// Bucket.UpdateObject.
type UpdateObjectRequest struct {
	// The name of the object to update. Must be specified.
	Name string

	// The generation of the object to update. Zero means the latest generation.
	Generation int64

	// If non-nil, the request will fail without effect if there is an object
	// with the given name (and optionally generation), and its meta-generation
	// is not equal to this value.
	MetaGenerationPrecondition *int64

	// String fields in the object to update (or not). The semantics are as
	// follows, for a given field F:
	//
	//  *  If F is set to nil, the corresponding GCS object field is untouched.
	//
	//  *  If *F is the empty string, then the corresponding GCS object field is
	//     removed.
	//
	//  *  Otherwise, the corresponding GCS object field is set to *F.
	//
	//  *  There is no facility for setting a GCS object field to the empty
	//     string, since many of the fields do not actually allow that as a legal
	//     value.
	//
	// Note that the GCS object's content type field cannot be removed.
	ContentType     *string
	ContentEncoding *string
	ContentLanguage *string
	CacheControl    *string

	// User-provided metadata updates. Keys that are not mentioned are untouched.
	// Keys whose values are nil are deleted, and others are updated to the
	// supplied string. There is no facility for completely removing user
	// metadata.
	Metadata map[string]*string
}

// A request to delete an object by name. Non-existence is not treated as an
// error.
type DeleteObjectRequest struct {
	// The name of the object to delete. Must be specified.
	Name string

	// The generation of the object to delete. Zero means the latest generation.
	Generation int64

	// If non-nil, the request will fail without effect if there is an object
	// with the given name (and optionally generation), and its meta-generation
	// is not equal to this value.
	MetaGenerationPrecondition *int64
}
<|endoftext|>


<commit_before>package suit

import (
	"encoding/json"
	"reflect"
	"unicode"
	"unicode/utf8"
)

type ConfigurationScreen struct {
	Title    string
	Subtitle string
	Sections []Section
	Actions  []Typed
}

func (o *ConfigurationScreen) MarshalJSON() ([]byte, error) {
	return json.Marshal(walk(*o))
}

type Section struct {
	Title    string
	Subtitle string
	Contents []Typed
	Well     bool
}

type InputText struct {
	Title       string
	Subtitle    string
	Before      string
	After       string
	Placeholder string
	Name        string
	Value       interface{}
	InputType   string
	Minimum     *int
	Maximum     *int
}

func (o InputText) getType() string {
	return "inputText"
}

type OptionGroup struct {
	Title          string
	Subtitle       string
	Name           string
	MinimumChoices int
	MaximumChoices int
	Options        []OptionGroupOption
}

func (o OptionGroup) getType() string {
	return "optionGroup"
}

type OptionGroupOption struct {
	Title    string
	Subtitle string
	Value    string
	Selected bool
}

type InputTimeRange struct {
	Title    string
	Subtitle string
	Name     string
	Value    TimeRange
}

func (o InputTimeRange) getType() string {
	return "inputTimeRange"
}

type TimeRange struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type CloseAction struct {
	Label string
}

func (o CloseAction) getType() string {
	return "close"
}

type ReplyAction struct {
	Label        string
	Name         string
	DisplayClass string
	DisplayIcon  string
}

func (o ReplyAction) getType() string {
	return "reply"
}

type Typed interface {
	getType() string
}

func walk(o interface{}) map[string]interface{} {

	m := make(map[string]interface{})

	if t, ok := o.(Typed); ok {
		m["type"] = t.getType()
	}

	val := reflect.ValueOf(o)

	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)

		val := valueField.Interface()

		switch valueField.Kind() {
		case reflect.Struct:
			val = walk(val)
		case reflect.Slice:
			vals := []interface{}{}
			for i := 0; i < valueField.Len(); i++ {
				if valueField.Index(i).Kind() == reflect.Interface || valueField.Index(i).Kind() == reflect.Struct {
					vals = append(vals, walk(valueField.Index(i).Interface()))
				} else {
					vals = append(vals, valueField.Index(i).Interface())
				}
				val = vals
			}
		default:
			if valueField.Interface() == reflect.Zero(valueField.Type()).Interface() {
				val = nil
			}
		}

		if val != nil {
			m[lF(typeField.Name)] = val
		}
	}

	return m
}

func lF(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}
<commit_msg>suit - Handle pointers, add Alert content<commit_after>package suit

import (
	"encoding/json"
	"reflect"
	"unicode"
	"unicode/utf8"
)

type ConfigurationScreen struct {
	Title    string
	Subtitle string
	Sections []Section
	Actions  []Typed
}

func (o *ConfigurationScreen) MarshalJSON() ([]byte, error) {
	return json.Marshal(walk(*o))
}

type Section struct {
	Title    string
	Subtitle string
	Contents []Typed
	Well     bool
}

type InputText struct {
	Title       string
	Subtitle    string
	Before      string
	After       string
	Placeholder string
	Name        string
	Value       interface{}
	InputType   string
	Minimum     *int
	Maximum     *int
}

func (o InputText) getType() string {
	return "inputText"
}

type OptionGroup struct {
	Title          string
	Subtitle       string
	Name           string
	MinimumChoices int
	MaximumChoices int
	Options        []OptionGroupOption
}

func (o OptionGroup) getType() string {
	return "optionGroup"
}

type OptionGroupOption struct {
	Title    string
	Subtitle string
	Value    string
	Selected bool
}

type Alert struct {
	Title        string
	Subtitle     string
	DisplayClass string
}

func (o Alert) getType() string {
	return "alert"
}

type ActionList struct {
	Title           string
	Subtitle        string
	Name            string
	Options         []ActionListOption
	PrimaryAction   Typed
	SecondaryAction Typed
}

func (o ActionList) getType() string {
	return "actionList"
}

type ActionListOption struct {
	Title    string
	Subtitle string
	Value    string
}

type InputTimeRange struct {
	Title    string
	Subtitle string
	Name     string
	Value    TimeRange
}

func (o InputTimeRange) getType() string {
	return "inputTimeRange"
}

type TimeRange struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type InputHidden struct {
	Name  string
	Value string
}

func (o InputHidden) getType() string {
	return "inputHidden"
}

type CloseAction struct {
	Label string
}

func (o CloseAction) getType() string {
	return "close"
}

type ReplyAction struct {
	Label        string
	Name         string
	DisplayClass string
	DisplayIcon  string
}

func (o ReplyAction) getType() string {
	return "reply"
}

type Typed interface {
	getType() string
}

func walk(o interface{}) map[string]interface{} {

	m := make(map[string]interface{})

	if t, ok := o.(Typed); ok {
		m["type"] = t.getType()
	}

	val := reflect.ValueOf(o)

	for i := 0; i < val.NumField(); i++ {
		valueField := val.Field(i)
		typeField := val.Type().Field(i)

		val := valueField.Interface()

		valueField = reflect.ValueOf(val)

		if valueField.Kind() == reflect.Ptr && !isZero(valueField) {
			valueField = valueField.Elem()
			val = valueField.Interface()
		}

		switch valueField.Kind() {
		case reflect.Struct:
			val = walk(val)
		case reflect.Slice:
			vals := []interface{}{}
			for i := 0; i < valueField.Len(); i++ {
				if valueField.Index(i).Kind() == reflect.Interface || valueField.Index(i).Kind() == reflect.Struct {
					vals = append(vals, walk(valueField.Index(i).Interface()))
				} else {
					vals = append(vals, valueField.Index(i).Interface())
				}
				val = vals
			}
		default:
			if isZero(valueField) {
				val = nil
			}
		}

		if val != nil {
			m[lF(typeField.Name)] = val
		}
	}

	return m
}

func isZero(valueField reflect.Value) bool {
	return valueField.Interface() == reflect.Zero(valueField.Type()).Interface()
}

func lF(s string) string {
	if s == "" {
		return ""
	}
	r, n := utf8.DecodeRuneInString(s)
	return string(unicode.ToLower(r)) + s[n:]
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package azure

import (
	"bytes"
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2017-10-01/containerregistry"
	"github.com/Azure/go-autorest/autorest/to"
)

type fakeClient struct {
	results []containerregistry.Registry
}

func (f *fakeClient) List(ctx context.Context) ([]containerregistry.Registry, error) {
	return f.results, nil
}

func Test(t *testing.T) {
	configStr := `
    {
        "aadClientId": "foo",
        "aadClientSecret": "bar"
    }`
	result := []containerregistry.Registry{
		{
			Name: to.StringPtr("foo"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.io"),
			},
		},
		{
			Name: to.StringPtr("bar"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.cn"),
			},
		},
		{
			Name: to.StringPtr("baz"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.de"),
			},
		},
		{
			Name: to.StringPtr("bus"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.us"),
			},
		},
	}
	fakeClient := &fakeClient{
		results: result,
	}

	provider := &acrProvider{
		registryClient: fakeClient,
	}
	provider.loadConfig(bytes.NewBufferString(configStr))

	creds := provider.Provide("")

	if len(creds) != len(result)+1 {
		t.Errorf("Unexpected list: %v, expected length %d", creds, len(result)+1)
	}
	for _, cred := range creds {
		if cred.Username != "" && cred.Username != "foo" {
			t.Errorf("expected 'foo' for username, saw: %v", cred.Username)
		}
		if cred.Password != "" && cred.Password != "bar" {
			t.Errorf("expected 'bar' for password, saw: %v", cred.Username)
		}
	}
	for _, val := range result {
		registryName := getLoginServer(val)
		if _, found := creds[registryName]; !found {
			t.Errorf("Missing expected registry: %s", registryName)
		}
	}
}

func TestParseACRLoginServerFromImage(t *testing.T) {
	tests := []struct {
		image    string
		expected string
	}{
		{
			image:    "invalidImage",
			expected: "",
		},
		{
			image:    "docker.io/library/busybox:latest",
			expected: "",
		},
		{
			image:    "foo.azurecr.io/bar/image:version",
			expected: "foo.azurecr.io",
		},
	}
	for _, test := range tests {
		if loginServer := parseACRLoginServerFromImage(test.image); loginServer != test.expected {
			t.Errorf("function parseACRLoginServerFromImage returns \"%s\" for image %s, expected \"%s\"", loginServer, test.image, test.expected)
		}
	}
}
<commit_msg>Add more test cases<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package azure

import (
	"bytes"
	"context"
	"testing"

	"github.com/Azure/azure-sdk-for-go/services/containerregistry/mgmt/2017-10-01/containerregistry"
	"github.com/Azure/go-autorest/autorest/to"
)

type fakeClient struct {
	results []containerregistry.Registry
}

func (f *fakeClient) List(ctx context.Context) ([]containerregistry.Registry, error) {
	return f.results, nil
}

func Test(t *testing.T) {
	configStr := `
    {
        "aadClientId": "foo",
        "aadClientSecret": "bar"
    }`
	result := []containerregistry.Registry{
		{
			Name: to.StringPtr("foo"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.io"),
			},
		},
		{
			Name: to.StringPtr("bar"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.cn"),
			},
		},
		{
			Name: to.StringPtr("baz"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.de"),
			},
		},
		{
			Name: to.StringPtr("bus"),
			RegistryProperties: &containerregistry.RegistryProperties{
				LoginServer: to.StringPtr("*.azurecr.us"),
			},
		},
	}
	fakeClient := &fakeClient{
		results: result,
	}

	provider := &acrProvider{
		registryClient: fakeClient,
	}
	provider.loadConfig(bytes.NewBufferString(configStr))

	creds := provider.Provide("")

	if len(creds) != len(result)+1 {
		t.Errorf("Unexpected list: %v, expected length %d", creds, len(result)+1)
	}
	for _, cred := range creds {
		if cred.Username != "" && cred.Username != "foo" {
			t.Errorf("expected 'foo' for username, saw: %v", cred.Username)
		}
		if cred.Password != "" && cred.Password != "bar" {
			t.Errorf("expected 'bar' for password, saw: %v", cred.Username)
		}
	}
	for _, val := range result {
		registryName := getLoginServer(val)
		if _, found := creds[registryName]; !found {
			t.Errorf("Missing expected registry: %s", registryName)
		}
	}
}

func TestParseACRLoginServerFromImage(t *testing.T) {
	tests := []struct {
		image    string
		expected string
	}{
		{
			image:    "invalidImage",
			expected: "",
		},
		{
			image:    "docker.io/library/busybox:latest",
			expected: "",
		},
		{
			image:    "foo.azurecr.io/bar/image:version",
			expected: "foo.azurecr.io",
		},
		{
			image:    "foo.azurecr.cn/bar/image:version",
			expected: "foo.azurecr.cn",
		},
		{
			image:    "foo.azurecr.de/bar/image:version",
			expected: "foo.azurecr.de",
		},
		{
			image:    "foo.azurecr.us/bar/image:version",
			expected: "foo.azurecr.us",
		},
	}
	for _, test := range tests {
		if loginServer := parseACRLoginServerFromImage(test.image); loginServer != test.expected {
			t.Errorf("function parseACRLoginServerFromImage returns \"%s\" for image %s, expected \"%s\"", loginServer, test.image, test.expected)
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"bytes"
	"errors"
	"log"
	"net/http"
	"io/ioutil"
	"os"
	"strings"

	"code.google.com/p/vitess/go/cache"
)

const cacheCapacity = 246*1024*1024 // 256MB
const template = `<!doctype html>

    <meta charset="utf-8">
    <title>{{NAME}} :: viewdocs.io</title>
    <link rel="stylesheet" type="text/css" href="//cloud.typography.com/678416/735422/css/fonts.css" />
    <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,600' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="http://static.gist.io/css/screen.css">

{{NAME}} :: index

            {{CONTENT}}

`

type CacheValue struct {
	Value string
}

func (cv *CacheValue) Size() int {
	return len(cv.Value)
}

func parseRequest(r *http.Request) (user, repo, doc string, err error) {
	hostname := strings.Split(r.Host, ".")
	if len(hostname) < 2 {
		return "", "", "", errors.New("Bad hostname")
	}
	user = hostname[0]
	path := strings.Split(r.RequestURI, "/")
	repo = path[1]
	if len(path) < 3 {
		doc = "index"
	} else {
		doc = strings.Join(path[2:], "/")
	}
	return
}

func fetchAndRenderDoc(user, repo, doc string) (string, error) {
	resp, err := http.Get("https://raw.github.com/"+user+"/"+repo+"/master/docs/"+doc+".md")
	if err != nil {
		return "", err
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	payload, err := json.Marshal(map[string]string{"text": string(body)})
	if err != nil {
		return "", err
	}
	resp, err = http.Post("https://api.github.com/markdown?access_token="+os.Getenv("ACCESS_TOKEN"), "application/json", bytes.NewBuffer(payload))
	if err != nil {
		return "", err
	}
	body, err = ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	output := strings.Replace(template, "{{CONTENT}}", string(body), 1)
	output = strings.Replace(output, "{{NAME}}", repo, -1)
	output = strings.Replace(output, "{{USER}}", user, -1)
	return output, nil
}

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8888"
	}

	lru := cache.NewLRUCache(cacheCapacity)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.RequestURI == "/" {
			http.Redirect(w, r, "http://progrium.viewdocs.io/viewdocs", 301)
			return
		}
		if r.RequestURI == "/favicon.ico" {
			return
		}
		switch r.Method {
		case "GET":
			user, repo, doc, err := parseRequest(r)
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			key := user + ":" + repo + ":" + doc
			value, ok := lru.Get(key)
			var output string
			if !ok {
				output, err = fetchAndRenderDoc(user, repo, doc)
				if err != nil {
					w.WriteHeader(http.StatusInternalServerError)
					w.Write([]byte(err.Error()))
					return
				}
				lru.Set(key, &CacheValue{output})
				log.Println("CACHE MISS:", key, lru.StatsJSON())
			} else {
				output = value.(*CacheValue).Value
			}
			w.Write([]byte(output))
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	})
	log.Println("Listening on port "+port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}
<commit_msg>tracking code<commit_after>package main

import (
	"encoding/json"
	"bytes"
	"errors"
	"log"
	"net/http"
	"io/ioutil"
	"os"
	"strings"

	"code.google.com/p/vitess/go/cache"
)

const cacheCapacity = 246*1024*1024 // 256MB
const template = `<!doctype html>

    <meta charset="utf-8">
    <title>{{NAME}} :: viewdocs.io</title>
    <link rel="stylesheet" type="text/css" href="//cloud.typography.com/678416/735422/css/fonts.css" />
    <link href='http://fonts.googleapis.com/css?family=Source+Code+Pro:300,600' rel='stylesheet' type='text/css'>
    <link rel="stylesheet" href="http://static.gist.io/css/screen.css">

{{NAME}} :: index

            {{CONTENT}}

`

type CacheValue struct {
	Value string
}

func (cv *CacheValue) Size() int {
	return len(cv.Value)
}

func parseRequest(r *http.Request) (user, repo, doc string, err error) {
	hostname := strings.Split(r.Host, ".")
	if len(hostname) < 2 {
		return "", "", "", errors.New("Bad hostname")
	}
	user = hostname[0]
	path := strings.Split(r.RequestURI, "/")
	repo = path[1]
	if len(path) < 3 {
		doc = "index"
	} else {
		doc = strings.Join(path[2:], "/")
	}
	return
}

func fetchAndRenderDoc(user, repo, doc string) (string, error) {
	resp, err := http.Get("https://raw.github.com/"+user+"/"+repo+"/master/docs/"+doc+".md")
	if err != nil {
		return "", err
	}
	body, err := ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	payload, err := json.Marshal(map[string]string{"text": string(body)})
	if err != nil {
		return "", err
	}
	resp, err = http.Post("https://api.github.com/markdown?access_token="+os.Getenv("ACCESS_TOKEN"), "application/json", bytes.NewBuffer(payload))
	if err != nil {
		return "", err
	}
	body, err = ioutil.ReadAll(resp.Body)
	resp.Body.Close()
	if err != nil {
		return "", err
	}
	output := strings.Replace(template, "{{CONTENT}}", string(body), 1)
	output = strings.Replace(output, "{{NAME}}", repo, -1)
	output = strings.Replace(output, "{{USER}}", user, -1)
	return output, nil
}

func main() {
	port := os.Getenv("PORT")
	if port == "" {
		port = "8888"
	}

	lru := cache.NewLRUCache(cacheCapacity)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.RequestURI == "/" {
			http.Redirect(w, r, "http://progrium.viewdocs.io/viewdocs", 301)
			return
		}
		if r.RequestURI == "/favicon.ico" {
			return
		}
		switch r.Method {
		case "GET":
			user, repo, doc, err := parseRequest(r)
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				w.Write([]byte(err.Error()))
				return
			}
			key := user + ":" + repo + ":" + doc
			value, ok := lru.Get(key)
			var output string
			if !ok {
				output, err = fetchAndRenderDoc(user, repo, doc)
				if err != nil {
					w.WriteHeader(http.StatusInternalServerError)
					w.Write([]byte(err.Error()))
					return
				}
				lru.Set(key, &CacheValue{output})
				log.Println("CACHE MISS:", key, lru.StatsJSON())
			} else {
				output = value.(*CacheValue).Value
			}
			w.Write([]byte(output))
		default:
			w.WriteHeader(http.StatusMethodNotAllowed)
		}
	})
	log.Println("Listening on port "+port)
	log.Fatal(http.ListenAndServe(":"+port, nil))
}
<|endoftext|>


<commit_before>// Package vlog contains functions for optionally printing if a verbose flag
// is set and for coalescing multiple duplicate print calls.
package vlog

import (
	"log"
	"time"

	"github.com/fastly/go-utils/suppress"
)

// Verbose determines whether calls to V* functions actually log or not.
var Verbose bool
var suppressDur time.Duration

// SetSuppressDuration sets the time for repeated log calls to be
// suppressed and coalesced.
func SetSuppressDuration(duration time.Duration) {
	suppressDur = duration
}

// Vlogf calls log.Printf if Verbose is true.
func VLogf(format string, v ...interface{}) {
	if Verbose {
		log.Printf(format, v...)
	}
}

// LogfQuiet coalesces multiple calls from the same location into one log.Printf
// call at the end of the suppress duration. If called multiple times, the output
// will be prepended with "[#x] ", where # is the number of duplicate suppressed calls.
func LogfQuiet(id, format string, v ...interface{}) {
	// this is level 3, logfQuietN is level 2, WrapFor level 1, runtime.Caller 0.
	logfQuietN(3, id, format, v...)
}

// VlogFQuiet calls LogfQuiet if Verbose is true.
func VLogfQuiet(id, format string, v ...interface{}) {
	if Verbose {
		logfQuietN(3, id, format, v...)
	}
}

func logfQuietN(depth int, id, format string, v ...interface{}) {
	suppress.WrapFor(depth, suppressDur, id, func(n int, id string) {
		if n <= 1 {
			log.Printf(format, v...)
		} else {
			log.Printf("[%dx] "+format, append([]interface{}{n}, v...)...) // ...
		}
	})
}
<commit_msg>Make default suppression duration non-zero<commit_after>// Package vlog contains functions for optionally printing if a verbose flag
// is set and for coalescing multiple duplicate print calls.
package vlog

import (
	"log"
	"time"

	"github.com/fastly/go-utils/suppress"
)

// Verbose determines whether calls to V* functions actually log or not.
var Verbose bool
var suppressDur = time.Second

// SetSuppressDuration sets the time for repeated log calls to be
// suppressed and coalesced.
func SetSuppressDuration(duration time.Duration) {
	suppressDur = duration
}

// Vlogf calls log.Printf if Verbose is true.
func VLogf(format string, v ...interface{}) {
	if Verbose {
		log.Printf(format, v...)
	}
}

// LogfQuiet coalesces multiple calls from the same location into one log.Printf
// call at the end of the suppress duration. If called multiple times, the output
// will be prepended with "[#x] ", where # is the number of duplicate suppressed calls.
func LogfQuiet(id, format string, v ...interface{}) {
	// this is level 3, logfQuietN is level 2, WrapFor level 1, runtime.Caller 0.
	logfQuietN(3, id, format, v...)
}

// VlogFQuiet calls LogfQuiet if Verbose is true.
func VLogfQuiet(id, format string, v ...interface{}) {
	if Verbose {
		logfQuietN(3, id, format, v...)
	}
}

func logfQuietN(depth int, id, format string, v ...interface{}) {
	suppress.WrapFor(depth, suppressDur, id, func(n int, id string) {
		if n <= 1 {
			log.Printf(format, v...)
		} else {
			log.Printf("[%dx] "+format, append([]interface{}{n}, v...)...) // ...
		}
	})
}
<|endoftext|>


<commit_before>package s3api

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer2"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/gorilla/mux"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"time"
)

const (
	maxObjectListSizeLimit = 1000 // Limit number of objects in a listObjectsResponse.
)

func (s3a *S3ApiServer) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {

	// https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html

	// collect parameters
	vars := mux.Vars(r)
	bucket := vars["bucket"]

	originalPrefix, marker, startAfter, delimiter, _, maxKeys := getListObjectsV2Args(r.URL.Query())

	if maxKeys < 0 {
		writeErrorResponse(w, ErrInvalidMaxKeys, r.URL)
		return
	}
	if delimiter != "" && delimiter != "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	if marker == "" {
		marker = startAfter
	}

	response, err := s3a.listFilerEntries(bucket, originalPrefix, maxKeys, marker)

	if err != nil {
		writeErrorResponse(w, ErrInternalError, r.URL)
		return
	}

	writeSuccessResponseXML(w, encodeResponse(response))
}

func (s3a *S3ApiServer) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {

	// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html

	// collect parameters
	vars := mux.Vars(r)
	bucket := vars["bucket"]

	originalPrefix, marker, delimiter, maxKeys := getListObjectsV1Args(r.URL.Query())

	if maxKeys < 0 {
		writeErrorResponse(w, ErrInvalidMaxKeys, r.URL)
		return
	}
	if delimiter != "" && delimiter != "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	response, err := s3a.listFilerEntries(bucket, originalPrefix, maxKeys, marker)

	if err != nil {
		writeErrorResponse(w, ErrInternalError, r.URL)
		return
	}

	writeSuccessResponseXML(w, encodeResponse(response))
}

func (s3a *S3ApiServer) listFilerEntries(bucket, originalPrefix string, maxKeys int, marker string) (response ListBucketResponse, err error) {

	// convert full path prefix into directory name and prefix for entry name
	dir, prefix := filepath.Split(originalPrefix)

	// check filer
	err = s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		request := &filer_pb.ListEntriesRequest{
			Directory:          fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, dir),
			Prefix:             prefix,
			Limit:              uint32(maxKeys + 1),
			StartFromFileName:  marker,
			InclusiveStartFrom: false,
		}

		glog.V(4).Infof("read directory: %v", request)
		resp, err := client.ListEntries(context.Background(), request)
		if err != nil {
			return fmt.Errorf("list buckets: %v", err)
		}

		var contents []ListEntry
		var commonPrefixes []PrefixEntry
		var counter int
		var lastEntryName string
		var isTruncated bool
		for _, entry := range resp.Entries {
			counter++
			if counter > maxKeys {
				isTruncated = true
				break
			}
			lastEntryName = entry.Name
			if entry.IsDirectory {
				commonPrefixes = append(commonPrefixes, PrefixEntry{
					Prefix: fmt.Sprintf("%s%s/", dir, entry.Name),
				})
			} else {
				contents = append(contents, ListEntry{
					Key:          fmt.Sprintf("%s%s", dir, entry.Name),
					LastModified: time.Unix(entry.Attributes.Mtime, 0),
					ETag:         "", // TODO add etag
					Size:         int64(filer2.TotalSize(entry.Chunks)),
					Owner: CanonicalUser{
						ID: fmt.Sprintf("%d", entry.Attributes.Uid),
					},
					StorageClass: StorageClass("STANDARD"),
				})
			}
		}

		response = ListBucketResponse{
			ListBucketResponse: ListBucketResult{
				Name:           bucket,
				Prefix:         originalPrefix,
				Marker:         marker,
				NextMarker:     lastEntryName,
				MaxKeys:        maxKeys,
				Delimiter:      "/",
				IsTruncated:    isTruncated,
				Contents:       contents,
				CommonPrefixes: commonPrefixes,
			},
		}

		return nil
	})

	return
}

func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, fetchOwner bool, maxkeys int) {
	prefix = values.Get("prefix")
	token = values.Get("continuation-token")
	startAfter = values.Get("start-after")
	delimiter = values.Get("delimiter")
	if values.Get("max-keys") != "" {
		maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
	} else {
		maxkeys = maxObjectListSizeLimit
	}
	fetchOwner = values.Get("fetch-owner") == "true"
	return
}

func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string, maxkeys int) {
	prefix = values.Get("prefix")
	marker = values.Get("marker")
	delimiter = values.Get("delimiter")
	if values.Get("max-keys") != "" {
		maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
	} else {
		maxkeys = maxObjectListSizeLimit
	}
	return
}
<commit_msg>s3API working with listing objects<commit_after>package s3api

import (
	"context"
	"fmt"
	"github.com/chrislusf/seaweedfs/weed/filer2"
	"github.com/chrislusf/seaweedfs/weed/glog"
	"github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
	"github.com/gorilla/mux"
	"net/http"
	"net/url"
	"path/filepath"
	"strconv"
	"time"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/aws"
)

const (
	maxObjectListSizeLimit = 1000 // Limit number of objects in a listObjectsResponse.
)

func (s3a *S3ApiServer) ListObjectsV2Handler(w http.ResponseWriter, r *http.Request) {

	// https://docs.aws.amazon.com/AmazonS3/latest/API/v2-RESTBucketGET.html

	// collect parameters
	vars := mux.Vars(r)
	bucket := vars["bucket"]

	glog.V(4).Infof("read v2: %v", vars)

	originalPrefix, marker, startAfter, delimiter, _, maxKeys := getListObjectsV2Args(r.URL.Query())

	if maxKeys < 0 {
		writeErrorResponse(w, ErrInvalidMaxKeys, r.URL)
		return
	}
	if delimiter != "" && delimiter != "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	if marker == "" {
		marker = startAfter
	}

	response, err := s3a.listFilerEntries(bucket, originalPrefix, maxKeys, marker)

	if err != nil {
		writeErrorResponse(w, ErrInternalError, r.URL)
		return
	}

	writeSuccessResponseXML(w, encodeResponse(response))
}

func (s3a *S3ApiServer) ListObjectsV1Handler(w http.ResponseWriter, r *http.Request) {

	// https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketGET.html

	// collect parameters
	vars := mux.Vars(r)
	bucket := vars["bucket"]

	originalPrefix, marker, delimiter, maxKeys := getListObjectsV1Args(r.URL.Query())

	if maxKeys < 0 {
		writeErrorResponse(w, ErrInvalidMaxKeys, r.URL)
		return
	}
	if delimiter != "" && delimiter != "/" {
		writeErrorResponse(w, ErrNotImplemented, r.URL)
		return
	}

	response, err := s3a.listFilerEntries(bucket, originalPrefix, maxKeys, marker)

	if err != nil {
		writeErrorResponse(w, ErrInternalError, r.URL)
		return
	}

	writeSuccessResponseXML(w, encodeResponse(response))
}

func (s3a *S3ApiServer) listFilerEntries(bucket, originalPrefix string, maxKeys int, marker string) (response *s3.ListObjectsOutput, err error) {

	// convert full path prefix into directory name and prefix for entry name
	dir, prefix := filepath.Split(originalPrefix)

	// check filer
	err = s3a.withFilerClient(func(client filer_pb.SeaweedFilerClient) error {

		request := &filer_pb.ListEntriesRequest{
			Directory:          fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, dir),
			Prefix:             prefix,
			Limit:              uint32(maxKeys + 1),
			StartFromFileName:  marker,
			InclusiveStartFrom: false,
		}

		resp, err := client.ListEntries(context.Background(), request)
		if err != nil {
			return fmt.Errorf("list buckets: %v", err)
		}

		var contents []*s3.Object
		var commonPrefixes []*s3.CommonPrefix
		var counter int
		var lastEntryName string
		var isTruncated bool
		for _, entry := range resp.Entries {
			counter++
			if counter > maxKeys {
				isTruncated = true
				break
			}
			lastEntryName = entry.Name
			if entry.IsDirectory {
				commonPrefixes = append(commonPrefixes, &s3.CommonPrefix{
					Prefix: aws.String(fmt.Sprintf("%s%s/", dir, entry.Name)),
				})
			} else {
				contents = append(contents, &s3.Object{
					Key:          aws.String(fmt.Sprintf("%s%s", dir, entry.Name)),
					LastModified: aws.Time(time.Unix(entry.Attributes.Mtime, 0)),
					ETag:         aws.String("\"2345sgfwetrewrt\""), // TODO add etag
					Size:         aws.Int64(int64(filer2.TotalSize(entry.Chunks))),
					Owner: &s3.Owner{
						ID:          aws.String("bcaf161ca5fb16fd081034f"),
						DisplayName: aws.String("webfile"),
					},
					StorageClass: aws.String("STANDARD"),
				})
			}
		}

		response = &s3.ListObjectsOutput{
			Name:           aws.String(bucket),
			Prefix:         aws.String(originalPrefix),
			Marker:         aws.String(marker),
			NextMarker:     aws.String(lastEntryName),
			MaxKeys:        aws.Int64(int64(maxKeys)),
			Delimiter:      aws.String("/"),
			IsTruncated:    aws.Bool(isTruncated),
			Contents:       contents,
			CommonPrefixes: commonPrefixes,
		}

		glog.V(4).Infof("read directory: %v, found: %v", request, counter)

		return nil
	})

	return
}

func getListObjectsV2Args(values url.Values) (prefix, token, startAfter, delimiter string, fetchOwner bool, maxkeys int) {
	prefix = values.Get("prefix")
	token = values.Get("continuation-token")
	startAfter = values.Get("start-after")
	delimiter = values.Get("delimiter")
	if values.Get("max-keys") != "" {
		maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
	} else {
		maxkeys = maxObjectListSizeLimit
	}
	fetchOwner = values.Get("fetch-owner") == "true"
	return
}

func getListObjectsV1Args(values url.Values) (prefix, marker, delimiter string, maxkeys int) {
	prefix = values.Get("prefix")
	marker = values.Get("marker")
	delimiter = values.Get("delimiter")
	if values.Get("max-keys") != "" {
		maxkeys, _ = strconv.Atoi(values.Get("max-keys"))
	} else {
		maxkeys = maxObjectListSizeLimit
	}
	return
}
<|endoftext|>