<commit_before>package cli

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSetupEnv(t *testing.T) {
	cases := []struct {
		args     []string
		env      map[string]string
		expected string
	}{
		{nil, nil, ""},
		{[]string{"--foobar", "bang!"}, nil, "bang!"},
		// make sure reset is good
		{nil, nil, ""},
		// test both variants of the prefix
		{nil, map[string]string{"DEMO_FOOBAR": "good"}, "good"},
		{nil, map[string]string{"DEMOFOOBAR": "silly"}, "silly"},
		// and that cli overrides env...
		{[]string{"--foobar", "important"},
			map[string]string{"DEMO_FOOBAR": "ignored"}, "important"},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		var foo string
		demo := &cobra.Command{
			Use: "demo",
			RunE: func(cmd *cobra.Command, args []string) error {
				foo = viper.GetString("foobar")
				return nil
			},
		}
		demo.Flags().String("foobar", "", "Some test value from config")
		cmd := PrepareBaseCmd(demo, "DEMO", "/qwerty/asdfgh") // some missing dir..
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, foo, i)
	}
}

func TestSetupConfig(t *testing.T) {
	// we pre-create two config files we can refer to in the rest of
	// the test cases.
	cval1, cval2 := "fubble", "wubble"
	conf1, err := WriteDemoConfig(map[string]string{"boo": cval1})
	require.Nil(t, err)
	// make sure it handles dashed-words in the config, and ignores random info
	conf2, err := WriteDemoConfig(map[string]string{"boo": cval2, "foo": "bar", "two-words": "WORD"})
	require.Nil(t, err)

	cases := []struct {
		args        []string
		env         map[string]string
		expected    string
		expectedTwo string
	}{
		{nil, nil, "", ""},
		// setting on the command line
		{[]string{"--boo", "haha"}, nil, "haha", ""},
		{[]string{"--two-words", "rocks"}, nil, "", "rocks"},
		{[]string{"--home", conf1}, nil, cval1, ""},
		// test both variants of the prefix
		//{nil, map[string]string{"RD_BOO": "bang"}, "bang", ""},
		//{nil, map[string]string{"RD_TWO_WORDS": "fly"}, "", "fly"},
		//{nil, map[string]string{"RDTWO_WORDS": "fly"}, "", "fly"},
		//{nil, map[string]string{"RD_ROOT": conf1}, cval1, ""},
		//{nil, map[string]string{"RDROOT": conf2}, cval2, "WORD"},
		//{nil, map[string]string{"RDHOME": conf1}, cval1, ""},
		// and when both are set??? HOME wins every time!
		{[]string{"--home", conf1}, map[string]string{"RDHOME": conf2}, cval2, "WORD"},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		var foo, two string
		boo := &cobra.Command{
			Use: "reader",
			RunE: func(cmd *cobra.Command, args []string) error {
				foo = viper.GetString("boo")
				two = viper.GetString("two-words")
				return nil
			},
		}
		boo.Flags().String("boo", "", "Some test value from config")
		boo.Flags().String("two-words", "", "Check out env handling -")
		cmd := PrepareBaseCmd(boo, "RD", "/qwerty/asdfgh") // some missing dir...
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, foo, i)
		assert.Equal(t, tc.expectedTwo, two, i)
	}
}

type DemoConfig struct {
	Name   string `mapstructure:"name"`
	Age    int    `mapstructure:"age"`
	Unused int    `mapstructure:"unused"`
}

func TestSetupUnmarshal(t *testing.T) {
	// we pre-create two config files we can refer to in the rest of
	// the test cases.
	cval1, cval2 := "someone", "else"
	conf1, err := WriteDemoConfig(map[string]string{"name": cval1})
	require.Nil(t, err)
	// even with some ignored fields, should be no problem
	conf2, err := WriteDemoConfig(map[string]string{"name": cval2, "foo": "bar"})
	require.Nil(t, err)

	// unused is not declared on a flag and remains from base
	base := DemoConfig{
		Name:   "default",
		Age:    42,
		Unused: -7,
	}
	c := func(name string, age int) DemoConfig {
		r := base
		// anything set on the flags as a default is used over
		// the default config object
		r.Name = "from-flag"
		if name != "" {
			r.Name = name
		}
		if age != 0 {
			r.Age = age
		}
		return r
	}

	cases := []struct {
		args     []string
		env      map[string]string
		expected DemoConfig
	}{
		{nil, nil, c("", 0)},
		// setting on the command line
		{[]string{"--name", "haha"}, nil, c("haha", 0)},
		{[]string{"--home", conf1}, nil, c(cval1, 0)},
		// test both variants of the prefix
		{nil, map[string]string{"MR_AGE": "56"}, c("", 56)},
		//{nil, map[string]string{"MR_ROOT": conf1}, c(cval1, 0)},
		{[]string{"--age", "17"}, map[string]string{"MRHOME": conf2}, c(cval2, 17)},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		cfg := base
		marsh := &cobra.Command{
			Use: "marsh",
			RunE: func(cmd *cobra.Command, args []string) error {
				return viper.Unmarshal(&cfg)
			},
		}
		marsh.Flags().String("name", "from-flag", "Some test value from config")
		// if we want a flag to use the proper default, then copy it
		// from the default config here
		marsh.Flags().Int("age", base.Age, "Some test value from config")
		cmd := PrepareBaseCmd(marsh, "MR", "/qwerty/asdfgh") // some missing dir...
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, cfg, i)
	}
}

func TestSetupTrace(t *testing.T) {
	cases := []struct {
		args     []string
		env      map[string]string
		long     bool
		expected string
	}{
		{nil, nil, false, "Trace flag = false"},
		{[]string{"--trace"}, nil, true, "Trace flag = true"},
		{[]string{"--no-such-flag"}, nil, false, "unknown flag: --no-such-flag"},
		{nil, map[string]string{"DBG_TRACE": "true"}, true, "Trace flag = true"},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		trace := &cobra.Command{
			Use: "trace",
			RunE: func(cmd *cobra.Command, args []string) error {
				return errors.Errorf("Trace flag = %t", viper.GetBool(TraceFlag))
			},
		}
		cmd := PrepareBaseCmd(trace, "DBG", "/qwerty/asdfgh") // some missing dir..
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		stdout, stderr, err := RunCaptureWithArgs(cmd, args, tc.env)
		require.NotNil(t, err, i)
		require.Equal(t, "", stdout, i)
		require.NotEqual(t, "", stderr, i)
		msg := strings.Split(stderr, "\n")
		desired := fmt.Sprintf("ERROR: %s", tc.expected)
		assert.Equal(t, desired, msg[0], i)
		if tc.long && assert.True(t, len(msg) > 2, i) {
			// the next line starts the stack trace...
			assert.Contains(t, msg[1], "TestSetupTrace", i)
			assert.Contains(t, msg[2], "setup_test.go", i)
		}
	}
}
<commit_msg>uncomment tests<commit_after>package cli

import (
	"fmt"
	"strconv"
	"strings"
	"testing"

	"github.com/pkg/errors"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestSetupEnv(t *testing.T) {
	cases := []struct {
		args     []string
		env      map[string]string
		expected string
	}{
		{nil, nil, ""},
		{[]string{"--foobar", "bang!"}, nil, "bang!"},
		// make sure reset is good
		{nil, nil, ""},
		// test both variants of the prefix
		{nil, map[string]string{"DEMO_FOOBAR": "good"}, "good"},
		{nil, map[string]string{"DEMOFOOBAR": "silly"}, "silly"},
		// and that cli overrides env...
		{[]string{"--foobar", "important"},
			map[string]string{"DEMO_FOOBAR": "ignored"}, "important"},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		var foo string
		demo := &cobra.Command{
			Use: "demo",
			RunE: func(cmd *cobra.Command, args []string) error {
				foo = viper.GetString("foobar")
				return nil
			},
		}
		demo.Flags().String("foobar", "", "Some test value from config")
		cmd := PrepareBaseCmd(demo, "DEMO", "/qwerty/asdfgh") // some missing dir..
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, foo, i)
	}
}

func TestSetupConfig(t *testing.T) {
	// we pre-create two config files we can refer to in the rest of
	// the test cases.
	cval1 := "fubble"
	conf1, err := WriteDemoConfig(map[string]string{"boo": cval1})
	require.Nil(t, err)

	cases := []struct {
		args        []string
		env         map[string]string
		expected    string
		expectedTwo string
	}{
		{nil, nil, "", ""},
		// setting on the command line
		{[]string{"--boo", "haha"}, nil, "haha", ""},
		{[]string{"--two-words", "rocks"}, nil, "", "rocks"},
		{[]string{"--home", conf1}, nil, cval1, ""},
		// test both variants of the prefix
		{nil, map[string]string{"RD_BOO": "bang"}, "bang", ""},
		{nil, map[string]string{"RD_TWO_WORDS": "fly"}, "", "fly"},
		{nil, map[string]string{"RDTWO_WORDS": "fly"}, "", "fly"},
		{nil, map[string]string{"RD_HOME": conf1}, cval1, ""},
		{nil, map[string]string{"RDHOME": conf1}, cval1, ""},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		var foo, two string
		boo := &cobra.Command{
			Use: "reader",
			RunE: func(cmd *cobra.Command, args []string) error {
				foo = viper.GetString("boo")
				two = viper.GetString("two-words")
				return nil
			},
		}
		boo.Flags().String("boo", "", "Some test value from config")
		boo.Flags().String("two-words", "", "Check out env handling -")
		cmd := PrepareBaseCmd(boo, "RD", "/qwerty/asdfgh") // some missing dir...
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, foo, i)
		assert.Equal(t, tc.expectedTwo, two, i)
	}
}

type DemoConfig struct {
	Name   string `mapstructure:"name"`
	Age    int    `mapstructure:"age"`
	Unused int    `mapstructure:"unused"`
}

func TestSetupUnmarshal(t *testing.T) {
	// we pre-create two config files we can refer to in the rest of
	// the test cases.
	cval1, cval2 := "someone", "else"
	conf1, err := WriteDemoConfig(map[string]string{"name": cval1})
	require.Nil(t, err)
	// even with some ignored fields, should be no problem
	conf2, err := WriteDemoConfig(map[string]string{"name": cval2, "foo": "bar"})
	require.Nil(t, err)

	// unused is not declared on a flag and remains from base
	base := DemoConfig{
		Name:   "default",
		Age:    42,
		Unused: -7,
	}
	c := func(name string, age int) DemoConfig {
		r := base
		// anything set on the flags as a default is used over
		// the default config object
		r.Name = "from-flag"
		if name != "" {
			r.Name = name
		}
		if age != 0 {
			r.Age = age
		}
		return r
	}

	cases := []struct {
		args     []string
		env      map[string]string
		expected DemoConfig
	}{
		{nil, nil, c("", 0)},
		// setting on the command line
		{[]string{"--name", "haha"}, nil, c("haha", 0)},
		{[]string{"--home", conf1}, nil, c(cval1, 0)},
		// test both variants of the prefix
		{nil, map[string]string{"MR_AGE": "56"}, c("", 56)},
		{nil, map[string]string{"MR_HOME": conf1}, c(cval1, 0)},
		{[]string{"--age", "17"}, map[string]string{"MRHOME": conf2}, c(cval2, 17)},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		cfg := base
		marsh := &cobra.Command{
			Use: "marsh",
			RunE: func(cmd *cobra.Command, args []string) error {
				return viper.Unmarshal(&cfg)
			},
		}
		marsh.Flags().String("name", "from-flag", "Some test value from config")
		// if we want a flag to use the proper default, then copy it
		// from the default config here
		marsh.Flags().Int("age", base.Age, "Some test value from config")
		cmd := PrepareBaseCmd(marsh, "MR", "/qwerty/asdfgh") // some missing dir...
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		err := RunWithArgs(cmd, args, tc.env)
		require.Nil(t, err, i)
		assert.Equal(t, tc.expected, cfg, i)
	}
}

func TestSetupTrace(t *testing.T) {
	cases := []struct {
		args     []string
		env      map[string]string
		long     bool
		expected string
	}{
		{nil, nil, false, "Trace flag = false"},
		{[]string{"--trace"}, nil, true, "Trace flag = true"},
		{[]string{"--no-such-flag"}, nil, false, "unknown flag: --no-such-flag"},
		{nil, map[string]string{"DBG_TRACE": "true"}, true, "Trace flag = true"},
	}

	for idx, tc := range cases {
		i := strconv.Itoa(idx)
		// test command that store value of foobar in local variable
		trace := &cobra.Command{
			Use: "trace",
			RunE: func(cmd *cobra.Command, args []string) error {
				return errors.Errorf("Trace flag = %t", viper.GetBool(TraceFlag))
			},
		}
		cmd := PrepareBaseCmd(trace, "DBG", "/qwerty/asdfgh") // some missing dir..
		cmd.Exit = func(int) {}

		viper.Reset()
		args := append([]string{cmd.Use}, tc.args...)
		stdout, stderr, err := RunCaptureWithArgs(cmd, args, tc.env)
		require.NotNil(t, err, i)
		require.Equal(t, "", stdout, i)
		require.NotEqual(t, "", stderr, i)
		msg := strings.Split(stderr, "\n")
		desired := fmt.Sprintf("ERROR: %s", tc.expected)
		assert.Equal(t, desired, msg[0], i)
		if tc.long && assert.True(t, len(msg) > 2, i) {
			// the next line starts the stack trace...
			assert.Contains(t, msg[1], "TestSetupTrace", i)
			assert.Contains(t, msg[2], "setup_test.go", i)
		}
	}
}
<|endoftext|>


<commit_before>package internal

import (
	"bytes"
	"os"
	"strconv"
	"sync"
	"time"
)

const (
	RuntimeGolang = "golang"

	CategoryCPU    = "cpu"
	CategoryMemory = "memory"
	CategoryTime   = "time"

	TypeCPUUsage         = "cpu-usage"
	TypeMemoryAllocation = "memory-allocations"
	TypeBlockingCalls    = "blocking-calls"

	UnitSample      = "sample"
	UnitMillisecond = "millisecond"
	UnitMicrosecond = "microsecond"
	UnitNanosecond  = "nanosecond"
	UnitByte        = "byte"
	UnitKilobyte    = "kilobyte"
	UnitPercent     = "percent"
)

type CallSite struct {
	MethodName  string
	FileName    string
	FileLine    int64
	Metadata    map[string]string
	measurement float64
	numSamples  int64
	children    map[string]*CallSite
	updateLock  *sync.RWMutex
}

type Profile struct {
	ID        string
	ProcessID string
	Runtime   string
	Category  string
	Type      string
	Unit      string
	Roots     []*CallSite
	Duration  int64
	Timespan  int64
	Timestamp int64
}

func NewProfile(category string, typ string, unit string, roots []*CallSite, duration int64, timespan int64) *Profile {
	p := &Profile{
		ProcessID: strconv.Itoa(os.Getpid()),
		ID:        GenerateUUID(),
		Runtime:   RuntimeGolang,
		Category:  category,
		Type:      typ,
		Unit:      unit,
		Roots:     roots,
		Duration:  duration / int64(time.Millisecond),
		Timespan:  timespan * 1000,
		Timestamp: time.Now().Unix() * 1000,
	}

	return p
}

func (p *Profile) ToMap() map[string]interface{} {
	rootsMap := make([]interface{}, 0)

	for _, root := range p.Roots {
		rootsMap = append(rootsMap, root.ToMap())
	}

	profileMap := map[string]interface{}{
		"pid":       p.ProcessID,
		"id":        p.ID,
		"runtime":   p.Runtime,
		"category":  p.Category,
		"type":      p.Type,
		"unit":      p.Unit,
		"roots":     rootsMap,
		"duration":  p.Duration,
		"timespan":  p.Timespan,
		"timestamp": p.Timestamp,
	}

	return profileMap
}

func NewCallSite(methodName string, fileName string, fileLine int64) *CallSite {
	cn := &CallSite{
		MethodName: methodName,
		FileName:   fileName,
		FileLine:   fileLine,
		children:   make(map[string]*CallSite),
		updateLock: &sync.RWMutex{},
	}

	return cn
}

func (cs *CallSite) FindOrAddChild(methodName, fileName string, fileLine int64) *CallSite {
	child := cs.findChild(methodName, fileName, fileLine)
	if child == nil {
		child = NewCallSite(methodName, fileName, fileLine)
		cs.addChild(child)
	}

	return child
}

func (cs *CallSite) Increment(value float64, numSamples int64) {
	cs.measurement += value
	cs.numSamples += numSamples
}

func (cs *CallSite) Measurement() (value float64, numSamples int64) {
	return cs.measurement, cs.numSamples
}

func (cs *CallSite) ToMap() map[string]interface{} {
	childrenMap := make([]interface{}, 0)
	for _, child := range cs.children {
		childrenMap = append(childrenMap, child.ToMap())
	}

	m, ns := cs.Measurement()
	callSiteMap := map[string]interface{}{
		"method_name": cs.MethodName,
		"file_name":   cs.FileName,
		"file_line":   cs.FileLine,
		"measurement": m,
		"num_samples": ns,
		"children":    childrenMap,
	}

	return callSiteMap
}

func (cs *CallSite) findChild(methodName, fileName string, fileLine int64) *CallSite {
	cs.updateLock.RLock()
	defer cs.updateLock.RUnlock()

	if child, exists := cs.children[createKey(methodName, fileName, fileLine)]; exists {
		return child
	}

	return nil
}

func (cs *CallSite) addChild(child *CallSite) {
	cs.updateLock.Lock()
	defer cs.updateLock.Unlock()

	cs.children[createKey(child.MethodName, child.FileName, child.FileLine)] = child
}

func createKey(methodName, fileName string, fileLine int64) string {
	var b bytes.Buffer

	b.WriteString(methodName)
	b.WriteString(" (")
	b.WriteString(fileName)
	b.WriteString(":")
	b.WriteString(strconv.FormatInt(fileLine, 10))
	b.WriteString(")")

	return b.String()
}
<commit_msg>Add presenter types to marshal profiles before sending them to the host agent<commit_after>package internal

import (
	"bytes"
	"os"
	"strconv"
	"sync"
	"time"
)

const (
	RuntimeGolang = "golang"

	CategoryCPU    = "cpu"
	CategoryMemory = "memory"
	CategoryTime   = "time"

	TypeCPUUsage         = "cpu-usage"
	TypeMemoryAllocation = "memory-allocations"
	TypeBlockingCalls    = "blocking-calls"

	UnitSample      = "sample"
	UnitMillisecond = "millisecond"
	UnitMicrosecond = "microsecond"
	UnitNanosecond  = "nanosecond"
	UnitByte        = "byte"
	UnitKilobyte    = "kilobyte"
	UnitPercent     = "percent"
)

// AgentProfile is a presenter type used to serialize a collected profile
// to JSON format supported by Instana profile sensor
type AgentProfile struct {
	ID        string          `json:"id"`
	ProcessID string          `json:"pid"`
	Runtime   string          `json:"runtime"`
	Category  string          `json:"category"`
	Type      string          `json:"type"`
	Unit      string          `json:"unit"`
	Roots     []AgentCallSite `json:"roots"`
	Duration  int64           `json:"duration"`
	Timespan  int64           `json:"timespan"`
	Timestamp int64           `json:"timestamp"`
}

func NewAgentProfile(p *Profile) AgentProfile {
	callSites := make([]AgentCallSite, 0, len(p.Roots))
	for _, root := range p.Roots {
		callSites = append(callSites, NewAgentCallSite(root))
	}

	return AgentProfile{
		ProcessID: p.ProcessID,
		ID:        p.ID,
		Runtime:   p.Runtime,
		Category:  p.Category,
		Type:      p.Type,
		Unit:      p.Unit,
		Roots:     callSites,
		Duration:  p.Duration,
		Timespan:  p.Timespan,
		Timestamp: p.Timestamp,
	}
}

type Profile struct {
	ID        string
	ProcessID string
	Runtime   string
	Category  string
	Type      string
	Unit      string
	Roots     []*CallSite
	Duration  int64
	Timespan  int64
	Timestamp int64
}

func NewProfile(category string, typ string, unit string, roots []*CallSite, duration int64, timespan int64) *Profile {
	p := &Profile{
		ProcessID: strconv.Itoa(os.Getpid()),
		ID:        GenerateUUID(),
		Runtime:   RuntimeGolang,
		Category:  category,
		Type:      typ,
		Unit:      unit,
		Roots:     roots,
		Duration:  duration / int64(time.Millisecond),
		Timespan:  timespan * 1000,
		Timestamp: time.Now().Unix() * 1000,
	}

	return p
}

func (p *Profile) ToMap() map[string]interface{} {
	rootsMap := make([]interface{}, 0)

	for _, root := range p.Roots {
		rootsMap = append(rootsMap, root.ToMap())
	}

	profileMap := map[string]interface{}{
		"pid":       p.ProcessID,
		"id":        p.ID,
		"runtime":   p.Runtime,
		"category":  p.Category,
		"type":      p.Type,
		"unit":      p.Unit,
		"roots":     rootsMap,
		"duration":  p.Duration,
		"timespan":  p.Timespan,
		"timestamp": p.Timestamp,
	}

	return profileMap
}

// AgentCallSite is a presenter type used to serialize a call site
// to JSON format supported by Instana profile sensor
type AgentCallSite struct {
	MethodName  string          `json:"method_name"`
	FileName    string          `json:"file_name"`
	FileLine    int64           `json:"file_line"`
	Measurement float64         `json:"measurement"`
	NumSamples  int64           `json:"num_samples"`
	Children    []AgentCallSite `json:"children"`
}

func NewAgentCallSite(cs *CallSite) AgentCallSite {
	children := make([]AgentCallSite, 0, len(cs.children))
	for _, child := range cs.children {
		children = append(children, NewAgentCallSite(child))
	}

	m, ns := cs.Measurement()

	return AgentCallSite{
		MethodName:  cs.MethodName,
		FileName:    cs.FileName,
		FileLine:    cs.FileLine,
		Measurement: m,
		NumSamples:  ns,
		Children:    children,
	}
}

type CallSite struct {
	MethodName  string
	FileName    string
	FileLine    int64
	Metadata    map[string]string
	measurement float64
	numSamples  int64
	children    map[string]*CallSite
	updateLock  *sync.RWMutex
}

func NewCallSite(methodName string, fileName string, fileLine int64) *CallSite {
	cn := &CallSite{
		MethodName: methodName,
		FileName:   fileName,
		FileLine:   fileLine,
		children:   make(map[string]*CallSite),
		updateLock: &sync.RWMutex{},
	}

	return cn
}

func (cs *CallSite) FindOrAddChild(methodName, fileName string, fileLine int64) *CallSite {
	child := cs.findChild(methodName, fileName, fileLine)
	if child == nil {
		child = NewCallSite(methodName, fileName, fileLine)
		cs.addChild(child)
	}

	return child
}

func (cs *CallSite) Increment(value float64, numSamples int64) {
	cs.measurement += value
	cs.numSamples += numSamples
}

func (cs *CallSite) Measurement() (value float64, numSamples int64) {
	return cs.measurement, cs.numSamples
}

func (cs *CallSite) ToMap() map[string]interface{} {
	childrenMap := make([]interface{}, 0)
	for _, child := range cs.children {
		childrenMap = append(childrenMap, child.ToMap())
	}

	m, ns := cs.Measurement()
	callSiteMap := map[string]interface{}{
		"method_name": cs.MethodName,
		"file_name":   cs.FileName,
		"file_line":   cs.FileLine,
		"measurement": m,
		"num_samples": ns,
		"children":    childrenMap,
	}

	return callSiteMap
}

func (cs *CallSite) findChild(methodName, fileName string, fileLine int64) *CallSite {
	cs.updateLock.RLock()
	defer cs.updateLock.RUnlock()

	if child, exists := cs.children[createKey(methodName, fileName, fileLine)]; exists {
		return child
	}

	return nil
}

func (cs *CallSite) addChild(child *CallSite) {
	cs.updateLock.Lock()
	defer cs.updateLock.Unlock()

	cs.children[createKey(child.MethodName, child.FileName, child.FileLine)] = child
}

func createKey(methodName, fileName string, fileLine int64) string {
	var b bytes.Buffer

	b.WriteString(methodName)
	b.WriteString(" (")
	b.WriteString(fileName)
	b.WriteString(":")
	b.WriteString(strconv.FormatInt(fileLine, 10))
	b.WriteString(")")

	return b.String()
}
<|endoftext|>


<commit_before>package proxy

import (
	"net/http"
	"testing"
	"time"

	"github.com/go-openapi/loads"
	"github.com/go-openapi/spec"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type testResponse struct {
	status int
	header http.Header
	body   []byte
}

func (t *testResponse) Status() int         { return t.status }
func (t *testResponse) Header() http.Header { return t.header }
func (t *testResponse) Body() []byte        { return t.body }

func openFixture(t *testing.T, name string) *spec.Swagger {
	doc, err := loads.Spec("./fixtures/" + name)
	require.NoError(t, err)
	return doc.Spec()
}

func TestValidation(t *testing.T) {
	spec := openFixture(t, "petstore.json")
	app, err := New(spec, nil)
	require.NoError(t, err)

	op := spec.Paths.Paths["/pet/findByStatus"].Get
	resp := &testResponse{
		header: http.Header{},
		body:   []byte("[]"),
	}
	resp.Header().Set("Content-Type", "application/json")
	for _, status := range []int{200, 400} {
		resp.status = status
		assert.NoError(t, app.Validate(resp, op))
	}

	resp.status = 999
	assert.Error(t, app.Validate(resp, op))
}

func TestHeaderValidation(t *testing.T) {
	swagger := openFixture(t, "petstore.json")
	proxy, err := New(swagger, nil)
	require.NoError(t, err)

	op := swagger.Paths.Paths["/user/login"].Get
	resp := &testResponse{
		status: 200,
		header: http.Header{},
	}

	t.Run("Valid Headers", func(t *testing.T) {
		resp.Header().Set("X-Rate-Limit", "32")
		resp.Header().Set("X-Expires-After", time.Now().Format(time.RFC3339))

		assert.NoError(t, proxy.ValidateHeaders(resp, op))
	})

	t.Run("Invalid Headers", func(t *testing.T) {
		resp.Header().Set("X-Rate-Limit", "NaN")
		resp.Header().Set("X-Expires-After", "Not a date-time")

		assert.Error(t, proxy.ValidateHeaders(resp, op))
	})
}

func TestProducesDefinition(t *testing.T) {
	swagger := openFixture(t, "petstore.json")
	app, err := New(swagger, nil)
	require.NoError(t, err)

	resp := &testResponse{header: http.Header{}}
	op := swagger.Paths.Paths["/pet"].Post
	assert.Error(t, app.ValidateMIME(resp, op),
		"Content-Type: application/json is missing")

	resp.Header().Set("Content-Type", "application/json")
	assert.NoError(t, app.ValidateMIME(resp, op),
		"Content-Type: application/json is present")

	t.Run("IsInheritedFromRoot", func(t *testing.T) {
		// MOVE `Produces` property to root
		swagger.Produces = []string{"application/json"}
		op.Produces = []string{}

		resp.Header().Del("Content-Type")
		assert.Error(t, app.ValidateMIME(resp, op),
			"Content-Type: application/json is missing")

		resp.Header().Set("Content-Type", "application/json")
		assert.NoError(t, app.ValidateMIME(resp, op),
			"Content-Type: application/json is present")
	})
}
<commit_msg>Add Handler tests<commit_after>package proxy

import (
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/go-openapi/loads"
	"github.com/go-openapi/spec"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

type testResponse struct {
	status int
	header http.Header
	body   []byte
}

func (t *testResponse) Status() int         { return t.status }
func (t *testResponse) Header() http.Header { return t.header }
func (t *testResponse) Body() []byte        { return t.body }

type testReporter struct {
	success  []*http.Request
	errors   []error
	warnings []string
}

func (t *testReporter) Success(req *http.Request) {
	t.success = append(t.success, req)
}

func (t *testReporter) Error(req *http.Request, err error) {
	t.errors = append(t.errors, err)
}

func (t *testReporter) Warning(req *http.Request, msg string) {
	t.warnings = append(t.warnings, msg)
}

func (t *testReporter) Report() {
	panic("not implemented")
}

func openFixture(t *testing.T, name string) *spec.Swagger {
	doc, err := loads.Spec("./fixtures/" + name)
	require.NoError(t, err)
	return doc.Spec()
}

func TestValidation(t *testing.T) {
	spec := openFixture(t, "petstore.json")
	app, err := New(spec, nil)
	require.NoError(t, err)

	op := spec.Paths.Paths["/pet/findByStatus"].Get
	resp := &testResponse{
		header: http.Header{},
		body:   []byte("[]"),
	}
	resp.Header().Set("Content-Type", "application/json")
	for _, status := range []int{200, 400} {
		resp.status = status
		assert.NoError(t, app.Validate(resp, op))
	}

	resp.status = 999
	assert.Error(t, app.Validate(resp, op))
}

func TestHeaderValidation(t *testing.T) {
	swagger := openFixture(t, "petstore.json")
	proxy, err := New(swagger, nil)
	require.NoError(t, err)

	op := swagger.Paths.Paths["/user/login"].Get
	resp := &testResponse{
		status: 200,
		header: http.Header{},
	}

	t.Run("Valid Headers", func(t *testing.T) {
		resp.Header().Set("X-Rate-Limit", "32")
		resp.Header().Set("X-Expires-After", time.Now().Format(time.RFC3339))

		assert.NoError(t, proxy.ValidateHeaders(resp, op))
	})

	t.Run("Invalid Headers", func(t *testing.T) {
		resp.Header().Set("X-Rate-Limit", "NaN")
		resp.Header().Set("X-Expires-After", "Not a date-time")

		assert.Error(t, proxy.ValidateHeaders(resp, op))
	})
}

func TestProducesDefinition(t *testing.T) {
	swagger := openFixture(t, "petstore.json")
	app, err := New(swagger, nil)
	require.NoError(t, err)

	resp := &testResponse{header: http.Header{}}
	op := swagger.Paths.Paths["/pet"].Post
	assert.Error(t, app.ValidateMIME(resp, op),
		"Content-Type: application/json is missing")

	resp.Header().Set("Content-Type", "application/json")
	assert.NoError(t, app.ValidateMIME(resp, op),
		"Content-Type: application/json is present")

	t.Run("IsInheritedFromRoot", func(t *testing.T) {
		// MOVE `Produces` property to root
		swagger.Produces = []string{"application/json"}
		op.Produces = []string{}

		resp.Header().Del("Content-Type")
		assert.Error(t, app.ValidateMIME(resp, op),
			"Content-Type: application/json is missing")

		resp.Header().Set("Content-Type", "application/json")
		assert.NoError(t, app.ValidateMIME(resp, op),
			"Content-Type: application/json is present")
	})
}

func TestHTTPHandler(t *testing.T) {
	swagger := openFixture(t, "petstore.json")
	reporter := &testReporter{}
	app, err := New(swagger, reporter)
	require.NoError(t, err)

	fn := func(w http.ResponseWriter, req *http.Request) {
		if req.URL.String() == "/v2/store/inventory" {
			w.Header().Set("Content-Type", "application/json")
			w.Write([]byte("{}"))
		}
	}

	srv := httptest.NewServer(app.Handler(http.HandlerFunc(fn)))
	defer srv.Close()

	for _, url := range []string{
		"/v2/store/inventory",   // SUCCESS
		"/v2/pet/findByStatus",  // ERROR
		"/not_a_registered_url", // WARNING
	} {
		http.Get(srv.URL + url)
	}

	assert.Equal(t, 1, len(reporter.success))
	assert.Equal(t, 1, len(reporter.errors))
	assert.Equal(t, 1, len(reporter.warnings))

}
<|endoftext|>


<commit_before>package pt

import (
	"math"
	"math/rand"
)

type Sampler interface {
	Sample(scene *Scene, ray Ray, rnd *rand.Rand) Color
}

func NewSampler(firstHitSamples, maxBounces int) Sampler {
	return &DefaultSampler{firstHitSamples, maxBounces, true}
}

type DefaultSampler struct {
	FirstHitSamples int
	MaxBounces      int
	DirectLighting  bool
}

func (s *DefaultSampler) Sample(scene *Scene, ray Ray, rnd *rand.Rand) Color {
	return s.sample(scene, ray, true, s.FirstHitSamples, s.MaxBounces, rnd)
}

func (s *DefaultSampler) sample(scene *Scene, ray Ray, emission bool, samples, depth int, rnd *rand.Rand) Color {
	if depth < 0 {
		return Color{}
	}
	hit := scene.Intersect(ray)
	if !hit.Ok() {
		if scene.Texture != nil {
			d := ray.Direction
			u := math.Atan2(d.Z, d.X) + scene.TextureAngle
			v := math.Atan2(d.Y, Vector{d.X, 0, d.Z}.Length())
			u = (u + math.Pi) / (2 * math.Pi)
			v = (v + math.Pi/2) / math.Pi
			return scene.Texture.Sample(u, v)
		}
		return scene.Color
	}
	info := hit.Info(ray)
	material := info.Material
	result := Color{}
	if material.Emittance > 0 {
		if s.DirectLighting && !emission {
			return Color{}
		}
		result = result.Add(material.Color.MulScalar(material.Emittance * float64(samples)))
	}
	n := int(math.Sqrt(float64(samples)))
	for u := 0; u < n; u++ {
		for v := 0; v < n; v++ {
			fu := (float64(u) + rnd.Float64()) / float64(n)
			fv := (float64(v) + rnd.Float64()) / float64(n)
			reflect := rnd.Float64() < 0.5
			newRay, reflected, p := ray.Bounce(&info, fu, fv, reflect, rnd)
			indirect := s.sample(scene, newRay, reflected, 1, depth-1, rnd)
			if reflected {
				tinted := indirect.Mix(material.Color.Mul(indirect), material.Tint)
				result = result.Add(tinted.MulScalar(2 * p))
			} else {
				direct := Color{}
				if s.DirectLighting {
					direct = s.directLight(scene, info.Ray, rnd)
				}
				result = result.Add(material.Color.Mul(direct.Add(indirect)).MulScalar(2 * p))
			}
		}
	}
	return result.DivScalar(float64(n * n))
}

func (s *DefaultSampler) directLight(scene *Scene, n Ray, rnd *rand.Rand) Color {
	nLights := len(scene.Lights)
	if nLights == 0 {
		return Color{}
	}

	// pick a random light
	light := scene.Lights[rand.Intn(nLights)]

	// get bounding sphere center and radius
	var center Vector
	var radius float64
	switch t := light.(type) {
	case *Sphere:
		radius = t.Radius
		center = t.Center
	default:
		// get bounding sphere from bounding box
		box := t.BoundingBox()
		radius = box.OuterRadius()
		center = box.Center()
	}

	// get random point in disk
	var point Vector
	for {
		x := rnd.Float64()*2 - 1
		y := rnd.Float64()*2 - 1
		if x*x+y*y <= 1 {
			l := center.Sub(n.Origin).Normalize()
			u := l.Cross(RandomUnitVector(rnd)).Normalize()
			v := l.Cross(u)
			point = point.Add(u.MulScalar(x * radius))
			point = point.Add(v.MulScalar(y * radius))
			point = point.Add(center)
			break
		}
	}

	// construct ray toward light point
	ray := Ray{n.Origin, point.Sub(n.Origin).Normalize()}

	// get cosine term
	diffuse := ray.Direction.Dot(n.Direction)
	if diffuse <= 0 {
		return Color{}
	}

	// check for light visibility
	hit := scene.Intersect(ray)
	if !hit.Ok() || hit.Shape != light {
		return Color{}
	}

	// compute solid angle (hemisphere coverage)
	hyp := center.Sub(n.Origin).Length()
	opp := radius
	theta := math.Asin(opp / hyp)
	adj := opp / math.Tan(theta)
	d := math.Cos(theta) * adj
	r := math.Sin(theta) * adj
	coverage := (r * r) / (d * d)

	// TODO: fix issue where hyp < opp (point inside sphere)
	if hyp < opp {
		coverage = 1
	}
	coverage = math.Min(coverage, 1)

	// get material properties from light
	material := MaterialAt(light, point)

	// combine factors
	m := material.Emittance * diffuse * coverage * float64(nLights)
	return material.Color.MulScalar(m)
}
<commit_msg>do both<commit_after>package pt

import (
	"math"
	"math/rand"
)

type Sampler interface {
	Sample(scene *Scene, ray Ray, rnd *rand.Rand) Color
}

func NewSampler(firstHitSamples, maxBounces int) Sampler {
	return &DefaultSampler{firstHitSamples, maxBounces, true}
}

type DefaultSampler struct {
	FirstHitSamples int
	MaxBounces      int
	DirectLighting  bool
}

func (s *DefaultSampler) Sample(scene *Scene, ray Ray, rnd *rand.Rand) Color {
	return s.sample(scene, ray, true, s.FirstHitSamples, s.MaxBounces, rnd)
}

func (s *DefaultSampler) sample(scene *Scene, ray Ray, emission bool, samples, depth int, rnd *rand.Rand) Color {
	if depth < 0 {
		return Color{}
	}
	hit := scene.Intersect(ray)
	if !hit.Ok() {
		if scene.Texture != nil {
			d := ray.Direction
			u := math.Atan2(d.Z, d.X) + scene.TextureAngle
			v := math.Atan2(d.Y, Vector{d.X, 0, d.Z}.Length())
			u = (u + math.Pi) / (2 * math.Pi)
			v = (v + math.Pi/2) / math.Pi
			return scene.Texture.Sample(u, v)
		}
		return scene.Color
	}
	info := hit.Info(ray)
	material := info.Material
	result := Color{}
	if material.Emittance > 0 {
		if s.DirectLighting && !emission {
			return Color{}
		}
		result = result.Add(material.Color.MulScalar(material.Emittance * float64(samples)))
	}
	n := int(math.Sqrt(float64(samples)))
	for u := 0; u < n; u++ {
		for v := 0; v < n; v++ {
			fu := (float64(u) + rnd.Float64()) / float64(n)
			fv := (float64(v) + rnd.Float64()) / float64(n)
			// specular
			newRay, reflected, p := ray.Bounce(&info, fu, fv, true, rnd)
			if p > 0 {
				indirect := s.sample(scene, newRay, reflected, 1, depth-1, rnd)
				tinted := indirect.Mix(material.Color.Mul(indirect), material.Tint)
				result = result.Add(tinted.MulScalar(p))
			}
			// diffuse
			newRay, reflected, p = ray.Bounce(&info, fu, fv, false, rnd)
			if p > 0 {
				indirect := s.sample(scene, newRay, reflected, 1, depth-1, rnd)
				direct := Color{}
				if s.DirectLighting {
					direct = s.directLight(scene, info.Ray, rnd)
				}
				result = result.Add(material.Color.Mul(direct.Add(indirect)).MulScalar(p))
			}
		}
	}
	return result.DivScalar(float64(n * n))
}

func (s *DefaultSampler) directLight(scene *Scene, n Ray, rnd *rand.Rand) Color {
	nLights := len(scene.Lights)
	if nLights == 0 {
		return Color{}
	}

	// pick a random light
	light := scene.Lights[rand.Intn(nLights)]

	// get bounding sphere center and radius
	var center Vector
	var radius float64
	switch t := light.(type) {
	case *Sphere:
		radius = t.Radius
		center = t.Center
	default:
		// get bounding sphere from bounding box
		box := t.BoundingBox()
		radius = box.OuterRadius()
		center = box.Center()
	}

	// get random point in disk
	var point Vector
	for {
		x := rnd.Float64()*2 - 1
		y := rnd.Float64()*2 - 1
		if x*x+y*y <= 1 {
			l := center.Sub(n.Origin).Normalize()
			u := l.Cross(RandomUnitVector(rnd)).Normalize()
			v := l.Cross(u)
			point = point.Add(u.MulScalar(x * radius))
			point = point.Add(v.MulScalar(y * radius))
			point = point.Add(center)
			break
		}
	}

	// construct ray toward light point
	ray := Ray{n.Origin, point.Sub(n.Origin).Normalize()}

	// get cosine term
	diffuse := ray.Direction.Dot(n.Direction)
	if diffuse <= 0 {
		return Color{}
	}

	// check for light visibility
	hit := scene.Intersect(ray)
	if !hit.Ok() || hit.Shape != light {
		return Color{}
	}

	// compute solid angle (hemisphere coverage)
	hyp := center.Sub(n.Origin).Length()
	opp := radius
	theta := math.Asin(opp / hyp)
	adj := opp / math.Tan(theta)
	d := math.Cos(theta) * adj
	r := math.Sin(theta) * adj
	coverage := (r * r) / (d * d)

	// TODO: fix issue where hyp < opp (point inside sphere)
	if hyp < opp {
		coverage = 1
	}
	coverage = math.Min(coverage, 1)

	// get material properties from light
	material := MaterialAt(light, point)

	// combine factors
	m := material.Emittance * diffuse * coverage * float64(nLights)
	return material.Color.MulScalar(m)
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Mute Communications Ltd.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package padding contains helper functions to generate cheap paddings.
package padding

import (
	"crypto/aes"
	"crypto/cipher"
	"io"
)

// Generate generates a new cheap padding of the given length.
func Generate(length int, rand io.Reader) ([]byte, error) {
	var key [32]byte
	if _, err := io.ReadFull(rand, key[:]); err != nil {
		return nil, err
	}
	block, _ := aes.NewCipher(key[:]) // correct key length was set above
	padding := make([]byte, (1+length/aes.BlockSize)*aes.BlockSize)
	stream := cipher.NewCTR(block, make([]byte, aes.BlockSize))
	stream.XORKeyStream(padding, padding)
	return padding[0:length], nil
}
<commit_msg>padding: log errors<commit_after>// Copyright (c) 2015 Mute Communications Ltd.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package padding contains helper functions to generate cheap paddings.
package padding

import (
	"crypto/aes"
	"crypto/cipher"
	"io"

	"github.com/mutecomm/mute/log"
)

// Generate generates a new cheap padding of the given length.
func Generate(length int, rand io.Reader) ([]byte, error) {
	var key [32]byte
	if _, err := io.ReadFull(rand, key[:]); err != nil {
		return nil, log.Error(err)
	}
	block, _ := aes.NewCipher(key[:]) // correct key length was set above
	padding := make([]byte, (1+length/aes.BlockSize)*aes.BlockSize)
	stream := cipher.NewCTR(block, make([]byte, aes.BlockSize))
	stream.XORKeyStream(padding, padding)
	return padding[0:length], nil
}
<|endoftext|>


<commit_before>package queue

import (
	"crypto/rand"
	"errors"
	"sync"
	"time"
)

var ERROR_HASH_NOT_FOUND = errors.New("Hash not found")
var ERROR_ALREADY_RESERVED = errors.New("Item already reserved")
var ERROR_NOT_RESERVED = errors.New("Item not reserved")
var ERROR_NO_ITEMS_AVALIABLE = errors.New("No items available")

// Item struct is the basic queue item
type Item struct {
	ReservedAt time.Time `json:"reserved_at"`
	Value      []byte    `json:"value"`
}

// Data contains all data in the current queue
type Data struct {
	MaxReserTime float64
	ItemList     map[string]Item `json:"item"`
	mutex        *sync.RWMutex
}

// New queue
func New() (q *Data, err error) {
	q = &Data{
		MaxReserTime: 30.0,
		mutex:        &sync.RWMutex{},
		ItemList:     make(map[string]Item),
	}
	return
}

// Count items in the queue
func (q *Data) Count() int {
	return len(q.ItemList)
}

// Put data in the queue
func (q *Data) Put(b []byte) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	q.ItemList[randStr()] = Item{Value: b}
}

// Renew the reservation of an item in the queue
func (q *Data) Renew(hash string) (err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	v, ok := q.ItemList[hash]
	if !ok {
		err = ERROR_HASH_NOT_FOUND
		return
	}
	now := time.Now()
	diff := now.Sub(v.ReservedAt)
	if diff.Seconds() >= q.MaxReserTime {
		err = ERROR_NOT_RESERVED
		return
	}
	v.ReservedAt = now
	q.ItemList[hash] = v
	return
}

// Reserve searches for the next available item in the queue
// If the item is not removed or the reservation time is not
// renewed, the item will returns to the queue automatically
func (q *Data) Reserve() (hash string, value []byte, err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	for k, v := range q.ItemList {
		now := time.Now()
		diff := now.Sub(v.ReservedAt)
		if diff.Seconds() > q.MaxReserTime {
			v.ReservedAt = now
			q.ItemList[k] = v
			value = v.Value
			hash = k
			return
		}
	}
	err = ERROR_NO_ITEMS_AVALIABLE
	return
}

// Remove item from the queue, the item must be reserved
func (q *Data) Remove(hash string) (err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	v, ok := q.ItemList[hash]
	if !ok {
		err = ERROR_HASH_NOT_FOUND
		return
	}
	diff := time.Since(v.ReservedAt)
	if diff.Seconds() >= q.MaxReserTime {
		err = ERROR_NOT_RESERVED
		return
	}
	delete(q.ItemList, hash)
	return
}

func randStr() (ret string) {
	const charList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	const clLen byte = 62
	const size = 36
	var bytes = make([]byte, size)
	if _, err := rand.Read(bytes); err != nil {
		panic(err.Error())
	}
	for k, v := range bytes {
		bytes[k] = charList[v%clLen]
	}
	ret = string(bytes)
	return
}
<commit_msg>add mutex in Count function<commit_after>package queue

import (
	"crypto/rand"
	"errors"
	"sync"
	"time"
)

var ERROR_HASH_NOT_FOUND = errors.New("Hash not found")
var ERROR_ALREADY_RESERVED = errors.New("Item already reserved")
var ERROR_NOT_RESERVED = errors.New("Item not reserved")
var ERROR_NO_ITEMS_AVALIABLE = errors.New("No items available")

// Item struct is the basic queue item
type Item struct {
	ReservedAt time.Time `json:"reserved_at"`
	Value      []byte    `json:"value"`
}

// Data contains all data in the current queue
type Data struct {
	MaxReserTime float64
	ItemList     map[string]Item `json:"item"`
	mutex        *sync.RWMutex
}

// New queue
func New() (q *Data, err error) {
	q = &Data{
		MaxReserTime: 30.0,
		mutex:        &sync.RWMutex{},
		ItemList:     make(map[string]Item),
	}
	return
}

// Count items in the queue
func (q *Data) Count() int {
	q.mutex.RLock()
	defer q.mutex.RUnlock()
	return len(q.ItemList)
}

// Put data in the queue
func (q *Data) Put(b []byte) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	q.ItemList[randStr()] = Item{Value: b}
}

// Renew the reservation of an item in the queue
func (q *Data) Renew(hash string) (err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	v, ok := q.ItemList[hash]
	if !ok {
		err = ERROR_HASH_NOT_FOUND
		return
	}
	now := time.Now()
	diff := now.Sub(v.ReservedAt)
	if diff.Seconds() >= q.MaxReserTime {
		err = ERROR_NOT_RESERVED
		return
	}
	v.ReservedAt = now
	q.ItemList[hash] = v
	return
}

// Reserve searches for the next available item in the queue
// If the item is not removed or the reservation time is not
// renewed, the item will returns to the queue automatically
func (q *Data) Reserve() (hash string, value []byte, err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	for k, v := range q.ItemList {
		now := time.Now()
		diff := now.Sub(v.ReservedAt)
		if diff.Seconds() > q.MaxReserTime {
			v.ReservedAt = now
			q.ItemList[k] = v
			value = v.Value
			hash = k
			return
		}
	}
	err = ERROR_NO_ITEMS_AVALIABLE
	return
}

// Remove item from the queue, the item must be reserved
func (q *Data) Remove(hash string) (err error) {
	q.mutex.Lock()
	defer q.mutex.Unlock()
	v, ok := q.ItemList[hash]
	if !ok {
		err = ERROR_HASH_NOT_FOUND
		return
	}
	diff := time.Since(v.ReservedAt)
	if diff.Seconds() >= q.MaxReserTime {
		err = ERROR_NOT_RESERVED
		return
	}
	delete(q.ItemList, hash)
	return
}

func randStr() (ret string) {
	const charList = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
	const clLen byte = 62
	const size = 36
	var bytes = make([]byte, size)
	if _, err := rand.Read(bytes); err != nil {
		panic(err.Error())
	}
	for k, v := range bytes {
		bytes[k] = charList[v%clLen]
	}
	ret = string(bytes)
	return
}
<|endoftext|>


<commit_before>package main

import (
	"math"
	"testing"
)

const testRegion = "US"
const testSeason = 29
const testPlayerPath = "emerald-dream/exupery"

func TestCreateToken(t *testing.T) {
	var token string = createToken()
	if len(token) == 0 {
		t.Error("Creating token failed")
	}
	t.Logf("Created token '%s'", token)
}

func TestGet(t *testing.T) {
	var resp *[]byte = getDynamic(testRegion, "token/")
	if len(*resp) == 0 {
		t.Error("No response from GET")
	}
}

func TestParseRealms(t *testing.T) {
	var realmJSON *[]byte = getDynamic(testRegion, "realm/index")
	var realms []realm = parseRealms(realmJSON)

	if realms == nil || len(realms) == 0 {
		t.Error("Parsing realms failed")
	}
	t.Logf("Found and parsed %v realms", len(realms))
}

func TestParseRaces(t *testing.T) {
	var racesJSON *[]byte = getStatic(testRegion, "playable-race/index")
	var races []race = parseRaces(racesJSON)

	if races == nil || len(races) == 0 {
		t.Error("Parsing races failed")
	}
	t.Logf("Found and parsed %v races", len(races))
}

func TestParseClasses(t *testing.T) {
	var classesJSON *[]byte = getStatic(testRegion, "playable-class/index")
	var classes []class = parseClasses(classesJSON)

	if classes == nil || len(classes) == 0 {
		t.Error("Parsing classes failed")
	}
	t.Logf("Found and parsed %v classes", len(classes))
}

func TestParseSpecsTalents(t *testing.T) {
	var specsJSON *[]byte = getStatic(testRegion, "playable-specialization/index")
	var specs []spec = parseSpecs(specsJSON)

	if specs == nil || len(specs) == 0 {
		t.Error("Parsing specs failed")
	}
	t.Logf("Found and parsed %v specs", len(specs))

	for _, spec := range specs {
		talents := spec.Talents
		if talents == nil || len(talents) == 0 {
			t.Error("Parsing talents failed")
		}
		t.Logf("Found and parsed %v talents for %s", len(talents), spec.Name)
	}
}

func TestParsePvPTalents(t *testing.T) {
	var talentsJSON *[]byte = getStatic(region, "pvp-talent/index")
	var pvpTalents []pvpTalent = parsePvPTalents(talentsJSON)

	if pvpTalents == nil || len(pvpTalents) == 0 {
		t.Error("Parsing PvP Talents failed")
	}
	t.Logf("Found and parsed %v PvP Talents", len(pvpTalents))
}

func TestParseAchievements(t *testing.T) {
	var achievementsJSON *[]byte = getStatic(testRegion, "achievement-category/15270")
	var achievements []achievement = parseAchievements(achievementsJSON)

	if achievements == nil || len(achievements) == 0 {
		t.Error("Parsing achievements failed")
	}
	t.Logf("Found and parsed %v PvP achievements", len(achievements))
}

func TestGetCurrentSeason(t *testing.T) {
	var currentSeason = getCurrentSeason()

	if currentSeason == 0 {
		t.Error("Determining current season failed")
	}
	t.Logf("Current PvP season is %d", currentSeason)
}

func TestGetLeaderboard(t *testing.T) {
	var leaderboard = getLeaderboard("2v2", testSeason)

	if leaderboard == nil || len(leaderboard) == 0 {
		t.Error("Parsing current season failed")
	}
	t.Logf("Found %d players on leaderboard", len(leaderboard))
}

func TestGetPlayersFromLeaderboards(t *testing.T) {
	var a = getLeaderboard("2v2", testSeason)
	var b = getLeaderboard("3v3", testSeason)
	var players = getPlayersFromLeaderboards(map[string][]leaderboardEntry{"2v2": a, "3v3": b})

	if players == nil || len(players) == 0 {
		t.Error("Getting leaderboard players failed")
	}
	t.Logf("Found %d players from leaderboards", len(players))
}

func TestSliceSplitting(t *testing.T) {
	max := 100
	slice := make([]*player, 0)
	for i := 0; i < max; i++ {
		slice = append(slice, &player{BlizzardID: i})
	}

	groups := split(slice, len(slice))
	validateSplitting(t, groups, 1, max)

	groups = split(slice, 10)
	validateSplitting(t, groups, len(slice)/10, 10)
}

func validateSplitting(t *testing.T, groups [][]*player, expectedNumGroups, maxGroupSize int) {
	if len(groups) != expectedNumGroups {
		t.Errorf("Returned %d groups, but expected %d", len(groups), expectedNumGroups)
	}
	for _, group := range groups {
		if len(group) > maxGroupSize {
			t.Errorf("Group has %d elements - should not exceed %d", len(group), maxGroupSize)
		}
	}
}

func TestGetPlayerProfileDetails(t *testing.T) {
	player := player{Path: testPlayerPath}
	setPlayerDetails(&player)

	if player.ClassID == 0 {
		t.Error("Player class NOT set")
	}
	if player.SpecID == 0 {
		t.Error("Player spec NOT set")
	}
	if player.FactionID == 0 {
		t.Error("Player faction NOT set")
	}
	if player.RaceID == 0 {
		t.Error("Player race NOT set")
	}
}

func TestGetPlayerTalents(t *testing.T) {
	talents := getPlayerTalents(testPlayerPath)
	if len(talents.Talents) == 0 || len(talents.PvPTalents) == 0 {
		t.Error("Getting player talents failed")
	}
	t.Logf("Found %d talents and %d PvP talents", len(talents.Talents), len(talents.PvPTalents))
}

func TestGetPlayerStats(t *testing.T) {
	stats := getPlayerStats(testPlayerPath)
	if stats.Intellect == 0 || stats.Stamina == 0 {
		t.Error("Getting player stats failed")
	}
	t.Logf("Found stats: %v", stats)
}

func TestHighestStat(t *testing.T) {
	a := highestStat(3, 2, 1)
	if a != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", a)
	}
	b := highestStat(2, 3, 1)
	if b != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", b)
	}
	c := highestStat(1, 2, 3)
	if c != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", c)
	}
	ab := highestStat(3, 3, 1)
	if ab != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", ab)
	}
	max := math.MaxInt32
	z := highestStat(max, max, max)
	if z != max {
		t.Errorf("Expected %d as highest stat, not %d", max, z)
	}
}

func TestGetPlayerItems(t *testing.T) {
	items := getPlayerItems(testPlayerPath)
	if items.Back.ID == 0 || items.Shoulder.ID == 0 {
		t.Error("Getting player items failed")
	}
	t.Logf("Found items: %v", items)
}

func TestGetPlayerAchievements(t *testing.T) {
	achieved := getPlayerAchievements(testPlayerPath, map[int]bool{2092: true, 13989: true})
	if achieved == nil || len(achieved) == 0 {
		t.Error("Getting player achievements failed")
	}
	t.Logf("Found achievements: %v", achieved)
}
<commit_msg>skip TestGetPlayersFromLeaderboards in short mode<commit_after>package main

import (
	"math"
	"testing"
)

const testRegion = "US"
const testSeason = 29
const testPlayerPath = "emerald-dream/exupery"

func TestCreateToken(t *testing.T) {
	var token string = createToken()
	if len(token) == 0 {
		t.Error("Creating token failed")
	}
	t.Logf("Created token '%s'", token)
}

func TestGet(t *testing.T) {
	var resp *[]byte = getDynamic(testRegion, "token/")
	if len(*resp) == 0 {
		t.Error("No response from GET")
	}
}

func TestParseRealms(t *testing.T) {
	var realmJSON *[]byte = getDynamic(testRegion, "realm/index")
	var realms []realm = parseRealms(realmJSON)

	if realms == nil || len(realms) == 0 {
		t.Error("Parsing realms failed")
	}
	t.Logf("Found and parsed %v realms", len(realms))
}

func TestParseRaces(t *testing.T) {
	var racesJSON *[]byte = getStatic(testRegion, "playable-race/index")
	var races []race = parseRaces(racesJSON)

	if races == nil || len(races) == 0 {
		t.Error("Parsing races failed")
	}
	t.Logf("Found and parsed %v races", len(races))
}

func TestParseClasses(t *testing.T) {
	var classesJSON *[]byte = getStatic(testRegion, "playable-class/index")
	var classes []class = parseClasses(classesJSON)

	if classes == nil || len(classes) == 0 {
		t.Error("Parsing classes failed")
	}
	t.Logf("Found and parsed %v classes", len(classes))
}

func TestParseSpecsTalents(t *testing.T) {
	var specsJSON *[]byte = getStatic(testRegion, "playable-specialization/index")
	var specs []spec = parseSpecs(specsJSON)

	if specs == nil || len(specs) == 0 {
		t.Error("Parsing specs failed")
	}
	t.Logf("Found and parsed %v specs", len(specs))

	for _, spec := range specs {
		talents := spec.Talents
		if talents == nil || len(talents) == 0 {
			t.Error("Parsing talents failed")
		}
		t.Logf("Found and parsed %v talents for %s", len(talents), spec.Name)
	}
}

func TestParsePvPTalents(t *testing.T) {
	var talentsJSON *[]byte = getStatic(region, "pvp-talent/index")
	var pvpTalents []pvpTalent = parsePvPTalents(talentsJSON)

	if pvpTalents == nil || len(pvpTalents) == 0 {
		t.Error("Parsing PvP Talents failed")
	}
	t.Logf("Found and parsed %v PvP Talents", len(pvpTalents))
}

func TestParseAchievements(t *testing.T) {
	var achievementsJSON *[]byte = getStatic(testRegion, "achievement-category/15270")
	var achievements []achievement = parseAchievements(achievementsJSON)

	if achievements == nil || len(achievements) == 0 {
		t.Error("Parsing achievements failed")
	}
	t.Logf("Found and parsed %v PvP achievements", len(achievements))
}

func TestGetCurrentSeason(t *testing.T) {
	var currentSeason = getCurrentSeason()

	if currentSeason == 0 {
		t.Error("Determining current season failed")
	}
	t.Logf("Current PvP season is %d", currentSeason)
}

func TestGetLeaderboard(t *testing.T) {
	var leaderboard = getLeaderboard("2v2", testSeason)

	if leaderboard == nil || len(leaderboard) == 0 {
		t.Error("Parsing current season failed")
	}
	t.Logf("Found %d players on leaderboard", len(leaderboard))
}

func TestGetPlayersFromLeaderboards(t *testing.T) {
	if testing.Short() {
		t.Skip()
	}
	var a = getLeaderboard("2v2", testSeason)
	var b = getLeaderboard("3v3", testSeason)
	var players = getPlayersFromLeaderboards(map[string][]leaderboardEntry{"2v2": a, "3v3": b})

	if players == nil || len(players) == 0 {
		t.Error("Getting leaderboard players failed")
	}
	t.Logf("Found %d players from leaderboards", len(players))
}

func TestSliceSplitting(t *testing.T) {
	max := 100
	slice := make([]*player, 0)
	for i := 0; i < max; i++ {
		slice = append(slice, &player{BlizzardID: i})
	}

	groups := split(slice, len(slice))
	validateSplitting(t, groups, 1, max)

	groups = split(slice, 10)
	validateSplitting(t, groups, len(slice)/10, 10)
}

func validateSplitting(t *testing.T, groups [][]*player, expectedNumGroups, maxGroupSize int) {
	if len(groups) != expectedNumGroups {
		t.Errorf("Returned %d groups, but expected %d", len(groups), expectedNumGroups)
	}
	for _, group := range groups {
		if len(group) > maxGroupSize {
			t.Errorf("Group has %d elements - should not exceed %d", len(group), maxGroupSize)
		}
	}
}

func TestGetPlayerProfileDetails(t *testing.T) {
	player := player{Path: testPlayerPath}
	setPlayerDetails(&player)

	if player.ClassID == 0 {
		t.Error("Player class NOT set")
	}
	if player.SpecID == 0 {
		t.Error("Player spec NOT set")
	}
	if player.FactionID == 0 {
		t.Error("Player faction NOT set")
	}
	if player.RaceID == 0 {
		t.Error("Player race NOT set")
	}
}

func TestGetPlayerTalents(t *testing.T) {
	talents := getPlayerTalents(testPlayerPath)
	if len(talents.Talents) == 0 || len(talents.PvPTalents) == 0 {
		t.Error("Getting player talents failed")
	}
	t.Logf("Found %d talents and %d PvP talents", len(talents.Talents), len(talents.PvPTalents))
}

func TestGetPlayerStats(t *testing.T) {
	stats := getPlayerStats(testPlayerPath)
	if stats.Intellect == 0 || stats.Stamina == 0 {
		t.Error("Getting player stats failed")
	}
	t.Logf("Found stats: %v", stats)
}

func TestHighestStat(t *testing.T) {
	a := highestStat(3, 2, 1)
	if a != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", a)
	}
	b := highestStat(2, 3, 1)
	if b != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", b)
	}
	c := highestStat(1, 2, 3)
	if c != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", c)
	}
	ab := highestStat(3, 3, 1)
	if ab != 3 {
		t.Errorf("Expected 3 as highest stat, not %d", ab)
	}
	max := math.MaxInt32
	z := highestStat(max, max, max)
	if z != max {
		t.Errorf("Expected %d as highest stat, not %d", max, z)
	}
}

func TestGetPlayerItems(t *testing.T) {
	items := getPlayerItems(testPlayerPath)
	if items.Back.ID == 0 || items.Shoulder.ID == 0 {
		t.Error("Getting player items failed")
	}
	t.Logf("Found items: %v", items)
}

func TestGetPlayerAchievements(t *testing.T) {
	achieved := getPlayerAchievements(testPlayerPath, map[int]bool{2092: true, 13989: true})
	if achieved == nil || len(achieved) == 0 {
		t.Error("Getting player achievements failed")
	}
	t.Logf("Found achievements: %v", achieved)
}
<|endoftext|>


<commit_before>package copyvdisk

import (
	"context"
	"errors"
	"fmt"
	"runtime"

	"github.com/spf13/cobra"
	"github.com/zero-os/0-Disk/config"
	"github.com/zero-os/0-Disk/log"
	"github.com/zero-os/0-Disk/nbd/ardb/storage"
	"github.com/zero-os/0-Disk/tlog/generator"
	cmdconfig "github.com/zero-os/0-Disk/zeroctl/cmd/config"
)

var vdiskCmdCfg struct {
	SourceConfig            config.SourceConfig
	ForceSameStorageCluster bool
	DataShards              int
	ParityShards            int
	PrivKey                 string
	JobCount                int
}

// VdiskCmd represents the vdisk copy subcommand
var VdiskCmd = &cobra.Command{
	Use:   "vdisk source_vdiskid target_vdiskid [target_clusterid]",
	Short: "Copy a vdisk",
	RunE:  copyVdisk,
}

func copyVdisk(cmd *cobra.Command, args []string) error {
	logLevel := log.InfoLevel
	if cmdconfig.Verbose {
		logLevel = log.InfoLevel
	}
	log.SetLevel(logLevel)

	// create config source
	configSource, err := config.NewSource(vdiskCmdCfg.SourceConfig)
	if err != nil {
		return err
	}
	defer configSource.Close()

	log.Info("parsing positional arguments...")

	// validate pos arg length
	argn := len(args)
	if argn < 2 {
		return errors.New("not enough arguments")
	} else if argn > 3 {
		return errors.New("too many arguments")
	}

	// store pos arguments in named variables
	sourceVdiskID, targetVdiskID := args[0], args[1]
	var targetClusterID string
	if argn == 3 {
		targetClusterID = args[2]
	}

	var sourceClusterConfig, targetClusterConfig *config.StorageClusterConfig

	// try to read the Vdisk+NBD config of source vdisk
	sourceStaticConfig, err := config.ReadVdiskStaticConfig(configSource, sourceVdiskID)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's static config: %v", sourceVdiskID, err)
	}
	sourceStorageConfig, err := config.ReadNBDStorageConfig(
		configSource, sourceVdiskID, sourceStaticConfig)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's storage config: %v", sourceVdiskID, err)
	}

	sourceClusterConfig = &sourceStorageConfig.StorageCluster

	// if a targetClusterID is given, check if it's not the same as the source cluster ID,
	// and if it is not, get its config
	if targetClusterID != "" {
		sourceVdiskNBDConfig, err := config.ReadVdiskNBDConfig(configSource, sourceVdiskID)
		if err != nil {
			return fmt.Errorf(
				"couldn't read source vdisk %s's storage config: %v", sourceVdiskID, err)
		}

		if sourceVdiskNBDConfig.StorageClusterID != targetClusterID {
			targetClusterConfig, err = config.ReadStorageClusterConfig(configSource, targetClusterID)
			if err != nil {
				return fmt.Errorf(
					"couldn't read target vdisk %s's storage config: %v", targetVdiskID, err)
			}
		}
	}

	// try to read the Vdisk config of target vdisk
	targetStaticConfig, err := config.ReadVdiskStaticConfig(configSource, targetVdiskID)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's static config: %v", targetVdiskID, err)
	}

	if targetStaticConfig.Type.TlogSupport() {
		log.Infof("generating tlog data for target vdisk `%v`", targetVdiskID)
		generator, err := generator.New(configSource, generator.Config{
			SourceVdiskID: sourceVdiskID,
			TargetVdiskID: targetVdiskID,
			DataShards:    vdiskCmdCfg.DataShards,
			ParityShards:  vdiskCmdCfg.ParityShards,
			PrivKey:       vdiskCmdCfg.PrivKey,
			JobCount:      vdiskCmdCfg.JobCount,
		})
		if err != nil {
			return fmt.Errorf("failed to create tlog generator: %v", err)
		}

		if err := generator.GenerateFromStorage(context.Background()); err != nil {
			return fmt.Errorf("failed to generate tlog data for vdisk `%v` : %v", targetVdiskID, err)
		}
	}

	// copy the vdisk
	switch stype := sourceStaticConfig.Type.StorageType(); stype {
	case config.StorageDeduped:
		return storage.CopyDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	case config.StorageNonDeduped:
		return storage.CopyNonDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	case config.StorageSemiDeduped:
		return storage.CopySemiDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	default:
		return fmt.Errorf("vdisk %s has an unknown storage type %d",
			sourceVdiskID, stype)
	}
}

func init() {
	VdiskCmd.Long = VdiskCmd.Short + `

If no target storage cluster is given,
the storage cluster configured for the source vdisk
will also be used for the target vdisk.

If an error occured, the target vdisk should be considered as non-existent,
even though data which is already copied is not rolled back.

NOTE: by design,
  only the metadata of a deduped vdisk is copied,
  the data will be copied the first time the vdisk spins up,
  on the condition that the templateStorageCluster has been configured.

WARNING: when copying nondeduped vdisks,
  it is currently not supported that the target vdisk's data cluster
  has more or less storage servers, then the source vdisk's data cluster.
  See issue #206 for more information.
`

	VdiskCmd.Flags().Var(
		&vdiskCmdCfg.SourceConfig, "config",
		"config resource: dialstrings (etcd cluster) or path (yaml file)")
	VdiskCmd.Flags().BoolVar(
		&vdiskCmdCfg.ForceSameStorageCluster, "same", false,
		"enable flag to force copy within the same nbd servers")

	VdiskCmd.Flags().IntVar(
		&vdiskCmdCfg.DataShards,
		"data-shards", 4,
		"data shards (K) variable of erasure encoding")
	VdiskCmd.Flags().IntVar(
		&vdiskCmdCfg.ParityShards,
		"parity-shards", 2,
		"parity shards (M) variable of erasure encoding")
	VdiskCmd.Flags().StringVar(
		&vdiskCmdCfg.PrivKey,
		"priv-key", "12345678901234567890123456789012",
		"private key")

	VdiskCmd.Flags().IntVarP(
		&vdiskCmdCfg.JobCount,
		"jobs", "j", runtime.NumCPU(),
		"the amount of parallel jobs to run the tlog generator")

}
<commit_msg>Fix zeroctl copy : only generate tlog data when vdisk has tlog server cluster ID<commit_after>package copyvdisk

import (
	"context"
	"errors"
	"fmt"
	"runtime"

	"github.com/spf13/cobra"
	"github.com/zero-os/0-Disk/config"
	"github.com/zero-os/0-Disk/log"
	"github.com/zero-os/0-Disk/nbd/ardb/storage"
	"github.com/zero-os/0-Disk/tlog/generator"
	cmdconfig "github.com/zero-os/0-Disk/zeroctl/cmd/config"
)

var vdiskCmdCfg struct {
	SourceConfig            config.SourceConfig
	ForceSameStorageCluster bool
	DataShards              int
	ParityShards            int
	PrivKey                 string
	JobCount                int
}

// VdiskCmd represents the vdisk copy subcommand
var VdiskCmd = &cobra.Command{
	Use:   "vdisk source_vdiskid target_vdiskid [target_clusterid]",
	Short: "Copy a vdisk",
	RunE:  copyVdisk,
}

func copyVdisk(cmd *cobra.Command, args []string) error {
	logLevel := log.InfoLevel
	if cmdconfig.Verbose {
		logLevel = log.InfoLevel
	}
	log.SetLevel(logLevel)

	// create config source
	configSource, err := config.NewSource(vdiskCmdCfg.SourceConfig)
	if err != nil {
		return err
	}
	defer configSource.Close()

	log.Info("parsing positional arguments...")

	// validate pos arg length
	argn := len(args)
	if argn < 2 {
		return errors.New("not enough arguments")
	} else if argn > 3 {
		return errors.New("too many arguments")
	}

	// store pos arguments in named variables
	sourceVdiskID, targetVdiskID := args[0], args[1]
	var targetClusterID string
	if argn == 3 {
		targetClusterID = args[2]
	}

	var sourceClusterConfig, targetClusterConfig *config.StorageClusterConfig

	// try to read the Vdisk+NBD config of source vdisk
	sourceStaticConfig, err := config.ReadVdiskStaticConfig(configSource, sourceVdiskID)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's static config: %v", sourceVdiskID, err)
	}
	sourceStorageConfig, err := config.ReadNBDStorageConfig(
		configSource, sourceVdiskID, sourceStaticConfig)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's storage config: %v", sourceVdiskID, err)
	}

	sourceClusterConfig = &sourceStorageConfig.StorageCluster

	// if a targetClusterID is given, check if it's not the same as the source cluster ID,
	// and if it is not, get its config
	if targetClusterID != "" {
		sourceVdiskNBDConfig, err := config.ReadVdiskNBDConfig(configSource, sourceVdiskID)
		if err != nil {
			return fmt.Errorf(
				"couldn't read source vdisk %s's storage config: %v", sourceVdiskID, err)
		}

		if sourceVdiskNBDConfig.StorageClusterID != targetClusterID {
			targetClusterConfig, err = config.ReadStorageClusterConfig(configSource, targetClusterID)
			if err != nil {
				return fmt.Errorf(
					"couldn't read target vdisk %s's storage config: %v", targetVdiskID, err)
			}
		}
	}

	// try to read the Vdisk config of target vdisk
	targetStaticConfig, err := config.ReadVdiskStaticConfig(configSource, targetVdiskID)
	if err != nil {
		return fmt.Errorf(
			"couldn't read source vdisk %s's static config: %v", targetVdiskID, err)
	}

	if targetStaticConfig.Type.TlogSupport() {
		targetVdiskNBDConfig, err := config.ReadVdiskNBDConfig(configSource, targetVdiskID)
		if err != nil {
			return fmt.Errorf(
				"couldn't read target vdisk %s's storage config: %v", targetVdiskID, err)
		}

		if targetVdiskNBDConfig.TlogServerClusterID != "" {
			log.Infof("generating tlog data for target vdisk `%v`", targetVdiskID)
			generator, err := generator.New(configSource, generator.Config{
				SourceVdiskID: sourceVdiskID,
				TargetVdiskID: targetVdiskID,
				DataShards:    vdiskCmdCfg.DataShards,
				ParityShards:  vdiskCmdCfg.ParityShards,
				PrivKey:       vdiskCmdCfg.PrivKey,
				JobCount:      vdiskCmdCfg.JobCount,
			})
			if err != nil {
				return fmt.Errorf("failed to create tlog generator: %v", err)
			}

			if err := generator.GenerateFromStorage(context.Background()); err != nil {
				return fmt.Errorf("failed to generate tlog data for vdisk `%v` : %v", targetVdiskID, err)
			}
		}
	}

	// copy the vdisk
	switch stype := sourceStaticConfig.Type.StorageType(); stype {
	case config.StorageDeduped:
		return storage.CopyDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	case config.StorageNonDeduped:
		return storage.CopyNonDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	case config.StorageSemiDeduped:
		return storage.CopySemiDeduped(
			sourceVdiskID, targetVdiskID,
			sourceClusterConfig, targetClusterConfig)
	default:
		return fmt.Errorf("vdisk %s has an unknown storage type %d",
			sourceVdiskID, stype)
	}
}

func init() {
	VdiskCmd.Long = VdiskCmd.Short + `

If no target storage cluster is given,
the storage cluster configured for the source vdisk
will also be used for the target vdisk.

If an error occured, the target vdisk should be considered as non-existent,
even though data which is already copied is not rolled back.

NOTE: by design,
  only the metadata of a deduped vdisk is copied,
  the data will be copied the first time the vdisk spins up,
  on the condition that the templateStorageCluster has been configured.

WARNING: when copying nondeduped vdisks,
  it is currently not supported that the target vdisk's data cluster
  has more or less storage servers, then the source vdisk's data cluster.
  See issue #206 for more information.
`

	VdiskCmd.Flags().Var(
		&vdiskCmdCfg.SourceConfig, "config",
		"config resource: dialstrings (etcd cluster) or path (yaml file)")
	VdiskCmd.Flags().BoolVar(
		&vdiskCmdCfg.ForceSameStorageCluster, "same", false,
		"enable flag to force copy within the same nbd servers")

	VdiskCmd.Flags().IntVar(
		&vdiskCmdCfg.DataShards,
		"data-shards", 4,
		"data shards (K) variable of erasure encoding")
	VdiskCmd.Flags().IntVar(
		&vdiskCmdCfg.ParityShards,
		"parity-shards", 2,
		"parity shards (M) variable of erasure encoding")
	VdiskCmd.Flags().StringVar(
		&vdiskCmdCfg.PrivKey,
		"priv-key", "12345678901234567890123456789012",
		"private key")

	VdiskCmd.Flags().IntVarP(
		&vdiskCmdCfg.JobCount,
		"jobs", "j", runtime.NumCPU(),
		"the amount of parallel jobs to run the tlog generator")

}
<|endoftext|>


<commit_before>package subscriber // import "github.com/influxdata/influxdb/services/subscriber"

import (
	"errors"
	"expvar"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/coordinator"
	"github.com/influxdata/influxdb/services/meta"
)

// Statistics for the Subscriber service.
const (
	statPointsWritten = "pointsWritten"
	statWriteFailures = "writeFailures"
)

// PointsWriter is an interface for writing points to a subscription destination.
// Only WritePoints() needs to be satisfied.
type PointsWriter interface {
	WritePoints(p *coordinator.WritePointsRequest) error
}

// unique set that identifies a given subscription
type subEntry struct {
	db   string
	rp   string
	name string
}

// Service manages forking the incoming data from InfluxDB
// to defined third party destinations.
// Subscriptions are defined per database and retention policy.
type Service struct {
	MetaClient interface {
		Databases() []meta.DatabaseInfo
		WaitForDataChanged() chan struct{}
	}
	NewPointsWriter func(u url.URL) (PointsWriter, error)
	Logger          *log.Logger
	update          chan struct{}
	statMap         *expvar.Map
	points          chan *coordinator.WritePointsRequest
	wg              sync.WaitGroup
	closed          bool
	closing         chan struct{}
	mu              sync.Mutex
	conf            Config

	failures      *expvar.Int
	pointsWritten *expvar.Int
}

// NewService returns a subscriber service with given settings
func NewService(c Config) *Service {
	s := &Service{
		Logger:        log.New(os.Stderr, "[subscriber] ", log.LstdFlags),
		statMap:       influxdb.NewStatistics("subscriber", "subscriber", nil),
		points:        make(chan *coordinator.WritePointsRequest, 100),
		closed:        true,
		conf:          c,
		failures:      &expvar.Int{},
		pointsWritten: &expvar.Int{},
	}
	s.NewPointsWriter = s.newPointsWriter
	s.statMap.Set(statWriteFailures, s.failures)
	s.statMap.Set(statPointsWritten, s.pointsWritten)
	return s
}

// Open starts the subscription service.
func (s *Service) Open() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.MetaClient == nil {
		return errors.New("no meta store")
	}

	s.closed = false

	s.closing = make(chan struct{})
	s.update = make(chan struct{})

	s.wg.Add(2)
	go func() {
		defer s.wg.Done()
		s.run()
	}()
	go func() {
		defer s.wg.Done()
		s.waitForMetaUpdates()
	}()

	s.Logger.Println("opened service")
	return nil
}

// Close terminates the subscription service
// Will panic if called multiple times or without first opening the service.
func (s *Service) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.closed = true

	close(s.points)
	close(s.closing)

	s.wg.Wait()
	s.Logger.Println("closed service")
	return nil
}

// SetLogOutput sets the writer to which all logs are written. It must not be
// called after Open is called.
func (s *Service) SetLogOutput(w io.Writer) {
	s.Logger = log.New(w, "[subscriber] ", log.LstdFlags)
}

func (s *Service) waitForMetaUpdates() {
	for {
		ch := s.MetaClient.WaitForDataChanged()
		select {
		case <-ch:
			err := s.Update()
			if err != nil {
				s.Logger.Println("error updating subscriptions:", err)
			}
		case <-s.closing:
			return
		}
	}
}

// Update will start new and stop deleted subscriptions.
func (s *Service) Update() error {
	// signal update
	select {
	case s.update <- struct{}{}:
		return nil
	case <-s.closing:
		return errors.New("service closed cannot update")
	}
}

func (s *Service) createSubscription(se subEntry, mode string, destinations []string) (PointsWriter, error) {
	var bm BalanceMode
	switch mode {
	case "ALL":
		bm = ALL
	case "ANY":
		bm = ANY
	default:
		return nil, fmt.Errorf("unknown balance mode %q", mode)
	}
	writers := make([]PointsWriter, len(destinations))
	statMaps := make([]*expvar.Map, len(writers))
	for i, dest := range destinations {
		u, err := url.Parse(dest)
		if err != nil {
			return nil, err
		}
		w, err := s.NewPointsWriter(*u)
		if err != nil {
			return nil, err
		}
		writers[i] = w
		tags := map[string]string{
			"database":         se.db,
			"retention_policy": se.rp,
			"name":             se.name,
			"mode":             mode,
			"destination":      dest,
		}
		key := strings.Join([]string{"subscriber", se.db, se.rp, se.name, dest}, ":")
		statMaps[i] = influxdb.NewStatistics(key, "subscriber", tags)
	}
	return &balancewriter{
		bm:       bm,
		writers:  writers,
		statMaps: statMaps,
	}, nil
}

// Points returns a channel into which write point requests can be sent.
func (s *Service) Points() chan<- *coordinator.WritePointsRequest {
	return s.points
}

// read points off chan and write them
func (s *Service) run() {
	var wg sync.WaitGroup
	subs := make(map[subEntry]chanWriter)
	// Perform initial update
	s.updateSubs(subs, &wg)
	for {
		select {
		case <-s.update:
			err := s.updateSubs(subs, &wg)
			if err != nil {
				s.Logger.Println("failed to update subscriptions:", err)
			}
		case p, ok := <-s.points:
			if !ok {
				// Close out all chanWriters
				for _, cw := range subs {
					cw.Close()
				}
				// Wait for them to finish
				wg.Wait()
				return
			}
			for se, cw := range subs {
				if p.Database == se.db && p.RetentionPolicy == se.rp {
					select {
					case cw.writeRequests <- p:
					default:
						s.failures.Add(1)
					}
				}
			}
		}
	}
}

func (s *Service) updateSubs(subs map[subEntry]chanWriter, wg *sync.WaitGroup) error {
	dbis := s.MetaClient.Databases()
	allEntries := make(map[subEntry]bool, 0)
	// Add in new subscriptions
	for _, dbi := range dbis {
		for _, rpi := range dbi.RetentionPolicies {
			for _, si := range rpi.Subscriptions {
				se := subEntry{
					db:   dbi.Name,
					rp:   rpi.Name,
					name: si.Name,
				}
				allEntries[se] = true
				if _, ok := subs[se]; ok {
					continue
				}
				sub, err := s.createSubscription(se, si.Mode, si.Destinations)
				if err != nil {
					return err
				}
				cw := chanWriter{
					writeRequests: make(chan *coordinator.WritePointsRequest, 100),
					pw:            sub,
					failures:      s.failures,
					pointsWritten: s.pointsWritten,
					logger:        s.Logger,
				}
				wg.Add(1)
				go func() {
					defer wg.Done()
					cw.Run()
				}()
				subs[se] = cw
				s.Logger.Println("added new subscription for", se.db, se.rp)
			}
		}
	}

	// Remove deleted subs
	for se := range subs {
		if !allEntries[se] {
			// Close the chanWriter
			subs[se].Close()

			// Remove it from the set
			delete(subs, se)
			s.Logger.Println("deleted old subscription for", se.db, se.rp)
		}
	}

	return nil
}

// Creates a PointsWriter from the given URL
func (s *Service) newPointsWriter(u url.URL) (PointsWriter, error) {
	switch u.Scheme {
	case "udp":
		return NewUDP(u.Host), nil
	case "http", "https":
		return NewHTTP(u.String(), time.Duration(s.conf.HTTPTimeout))
	default:
		return nil, fmt.Errorf("unknown destination scheme %s", u.Scheme)
	}
}

// Sends WritePointsRequest to a PointsWriter received over a channel.
type chanWriter struct {
	writeRequests chan *coordinator.WritePointsRequest
	pw            PointsWriter
	pointsWritten *expvar.Int
	failures      *expvar.Int
	logger        *log.Logger
}

// Close the chanWriter
func (c chanWriter) Close() {
	close(c.writeRequests)
}

func (c chanWriter) Run() {
	for wr := range c.writeRequests {
		err := c.pw.WritePoints(wr)
		if err != nil {
			c.logger.Println(err)
			c.failures.Add(1)
		} else {
			c.pointsWritten.Add(int64(len(wr.Points)))
		}
	}
}

// BalanceMode sets what balance mode to use on a subscription.
// valid options are currently ALL or ANY
type BalanceMode int

//ALL is a Balance mode option
const (
	ALL BalanceMode = iota
	ANY
)

// balances writes across PointsWriters according to BalanceMode
type balancewriter struct {
	bm       BalanceMode
	writers  []PointsWriter
	statMaps []*expvar.Map
	i        int
}

func (b *balancewriter) WritePoints(p *coordinator.WritePointsRequest) error {
	var lastErr error
	for range b.writers {
		// round robin through destinations.
		i := b.i
		w := b.writers[i]
		b.i = (b.i + 1) % len(b.writers)

		// write points to destination.
		err := w.WritePoints(p)
		if err != nil {
			lastErr = err
			b.statMaps[i].Add(statWriteFailures, 1)
		} else {
			b.statMaps[i].Add(statPointsWritten, int64(len(p.Points)))
			if b.bm == ANY {
				break
			}
		}
	}
	return lastErr
}
<commit_msg>Initialize subscriber channel on open<commit_after>package subscriber // import "github.com/influxdata/influxdb/services/subscriber"

import (
	"errors"
	"expvar"
	"fmt"
	"io"
	"log"
	"net/url"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/influxdata/influxdb"
	"github.com/influxdata/influxdb/coordinator"
	"github.com/influxdata/influxdb/services/meta"
)

// Statistics for the Subscriber service.
const (
	statPointsWritten = "pointsWritten"
	statWriteFailures = "writeFailures"
)

// PointsWriter is an interface for writing points to a subscription destination.
// Only WritePoints() needs to be satisfied.
type PointsWriter interface {
	WritePoints(p *coordinator.WritePointsRequest) error
}

// unique set that identifies a given subscription
type subEntry struct {
	db   string
	rp   string
	name string
}

// Service manages forking the incoming data from InfluxDB
// to defined third party destinations.
// Subscriptions are defined per database and retention policy.
type Service struct {
	MetaClient interface {
		Databases() []meta.DatabaseInfo
		WaitForDataChanged() chan struct{}
	}
	NewPointsWriter func(u url.URL) (PointsWriter, error)
	Logger          *log.Logger
	update          chan struct{}
	statMap         *expvar.Map
	points          chan *coordinator.WritePointsRequest
	wg              sync.WaitGroup
	closed          bool
	closing         chan struct{}
	mu              sync.Mutex
	conf            Config

	failures      *expvar.Int
	pointsWritten *expvar.Int
}

// NewService returns a subscriber service with given settings
func NewService(c Config) *Service {
	s := &Service{
		Logger:        log.New(os.Stderr, "[subscriber] ", log.LstdFlags),
		statMap:       influxdb.NewStatistics("subscriber", "subscriber", nil),
		closed:        true,
		conf:          c,
		failures:      &expvar.Int{},
		pointsWritten: &expvar.Int{},
	}
	s.NewPointsWriter = s.newPointsWriter
	s.statMap.Set(statWriteFailures, s.failures)
	s.statMap.Set(statPointsWritten, s.pointsWritten)
	return s
}

// Open starts the subscription service.
func (s *Service) Open() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	if s.MetaClient == nil {
		return errors.New("no meta store")
	}

	s.closed = false

	s.closing = make(chan struct{})
	s.update = make(chan struct{})
	s.points = make(chan *coordinator.WritePointsRequest, 100)

	s.wg.Add(2)
	go func() {
		defer s.wg.Done()
		s.run()
	}()
	go func() {
		defer s.wg.Done()
		s.waitForMetaUpdates()
	}()

	s.Logger.Println("opened service")
	return nil
}

// Close terminates the subscription service
// Will panic if called multiple times or without first opening the service.
func (s *Service) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.closed = true

	close(s.points)
	close(s.closing)

	s.wg.Wait()
	s.Logger.Println("closed service")
	return nil
}

// SetLogOutput sets the writer to which all logs are written. It must not be
// called after Open is called.
func (s *Service) SetLogOutput(w io.Writer) {
	s.Logger = log.New(w, "[subscriber] ", log.LstdFlags)
}

func (s *Service) waitForMetaUpdates() {
	for {
		ch := s.MetaClient.WaitForDataChanged()
		select {
		case <-ch:
			err := s.Update()
			if err != nil {
				s.Logger.Println("error updating subscriptions:", err)
			}
		case <-s.closing:
			return
		}
	}
}

// Update will start new and stop deleted subscriptions.
func (s *Service) Update() error {
	// signal update
	select {
	case s.update <- struct{}{}:
		return nil
	case <-s.closing:
		return errors.New("service closed cannot update")
	}
}

func (s *Service) createSubscription(se subEntry, mode string, destinations []string) (PointsWriter, error) {
	var bm BalanceMode
	switch mode {
	case "ALL":
		bm = ALL
	case "ANY":
		bm = ANY
	default:
		return nil, fmt.Errorf("unknown balance mode %q", mode)
	}
	writers := make([]PointsWriter, len(destinations))
	statMaps := make([]*expvar.Map, len(writers))
	for i, dest := range destinations {
		u, err := url.Parse(dest)
		if err != nil {
			return nil, err
		}
		w, err := s.NewPointsWriter(*u)
		if err != nil {
			return nil, err
		}
		writers[i] = w
		tags := map[string]string{
			"database":         se.db,
			"retention_policy": se.rp,
			"name":             se.name,
			"mode":             mode,
			"destination":      dest,
		}
		key := strings.Join([]string{"subscriber", se.db, se.rp, se.name, dest}, ":")
		statMaps[i] = influxdb.NewStatistics(key, "subscriber", tags)
	}
	return &balancewriter{
		bm:       bm,
		writers:  writers,
		statMaps: statMaps,
	}, nil
}

// Points returns a channel into which write point requests can be sent.
func (s *Service) Points() chan<- *coordinator.WritePointsRequest {
	return s.points
}

// read points off chan and write them
func (s *Service) run() {
	var wg sync.WaitGroup
	subs := make(map[subEntry]chanWriter)
	// Perform initial update
	s.updateSubs(subs, &wg)
	for {
		select {
		case <-s.update:
			err := s.updateSubs(subs, &wg)
			if err != nil {
				s.Logger.Println("failed to update subscriptions:", err)
			}
		case p, ok := <-s.points:
			if !ok {
				// Close out all chanWriters
				for _, cw := range subs {
					cw.Close()
				}
				// Wait for them to finish
				wg.Wait()
				return
			}
			for se, cw := range subs {
				if p.Database == se.db && p.RetentionPolicy == se.rp {
					select {
					case cw.writeRequests <- p:
					default:
						s.failures.Add(1)
					}
				}
			}
		}
	}
}

func (s *Service) updateSubs(subs map[subEntry]chanWriter, wg *sync.WaitGroup) error {
	dbis := s.MetaClient.Databases()
	allEntries := make(map[subEntry]bool, 0)
	// Add in new subscriptions
	for _, dbi := range dbis {
		for _, rpi := range dbi.RetentionPolicies {
			for _, si := range rpi.Subscriptions {
				se := subEntry{
					db:   dbi.Name,
					rp:   rpi.Name,
					name: si.Name,
				}
				allEntries[se] = true
				if _, ok := subs[se]; ok {
					continue
				}
				sub, err := s.createSubscription(se, si.Mode, si.Destinations)
				if err != nil {
					return err
				}
				cw := chanWriter{
					writeRequests: make(chan *coordinator.WritePointsRequest, 100),
					pw:            sub,
					failures:      s.failures,
					pointsWritten: s.pointsWritten,
					logger:        s.Logger,
				}
				wg.Add(1)
				go func() {
					defer wg.Done()
					cw.Run()
				}()
				subs[se] = cw
				s.Logger.Println("added new subscription for", se.db, se.rp)
			}
		}
	}

	// Remove deleted subs
	for se := range subs {
		if !allEntries[se] {
			// Close the chanWriter
			subs[se].Close()

			// Remove it from the set
			delete(subs, se)
			s.Logger.Println("deleted old subscription for", se.db, se.rp)
		}
	}

	return nil
}

// Creates a PointsWriter from the given URL
func (s *Service) newPointsWriter(u url.URL) (PointsWriter, error) {
	switch u.Scheme {
	case "udp":
		return NewUDP(u.Host), nil
	case "http", "https":
		return NewHTTP(u.String(), time.Duration(s.conf.HTTPTimeout))
	default:
		return nil, fmt.Errorf("unknown destination scheme %s", u.Scheme)
	}
}

// Sends WritePointsRequest to a PointsWriter received over a channel.
type chanWriter struct {
	writeRequests chan *coordinator.WritePointsRequest
	pw            PointsWriter
	pointsWritten *expvar.Int
	failures      *expvar.Int
	logger        *log.Logger
}

// Close the chanWriter
func (c chanWriter) Close() {
	close(c.writeRequests)
}

func (c chanWriter) Run() {
	for wr := range c.writeRequests {
		err := c.pw.WritePoints(wr)
		if err != nil {
			c.logger.Println(err)
			c.failures.Add(1)
		} else {
			c.pointsWritten.Add(int64(len(wr.Points)))
		}
	}
}

// BalanceMode sets what balance mode to use on a subscription.
// valid options are currently ALL or ANY
type BalanceMode int

//ALL is a Balance mode option
const (
	ALL BalanceMode = iota
	ANY
)

// balances writes across PointsWriters according to BalanceMode
type balancewriter struct {
	bm       BalanceMode
	writers  []PointsWriter
	statMaps []*expvar.Map
	i        int
}

func (b *balancewriter) WritePoints(p *coordinator.WritePointsRequest) error {
	var lastErr error
	for range b.writers {
		// round robin through destinations.
		i := b.i
		w := b.writers[i]
		b.i = (b.i + 1) % len(b.writers)

		// write points to destination.
		err := w.WritePoints(p)
		if err != nil {
			lastErr = err
			b.statMaps[i].Add(statWriteFailures, 1)
		} else {
			b.statMaps[i].Add(statPointsWritten, int64(len(p.Points)))
			if b.bm == ANY {
				break
			}
		}
	}
	return lastErr
}
<|endoftext|>


<commit_before>package render

import (
	"image"

	"github.com/oakmound/oak/oakerr"
)

// Sheet is a 2D array of image rgbas
type Sheet [][]*image.RGBA

// SubSprite gets a sprite from a sheet at the given location
func (sh *Sheet) SubSprite(x, y int) *Sprite {
	return NewSprite(0, 0, (*sh)[x][y])
}

// ToSprites returns this sheet as a 2D array of Sprites
func (sh *Sheet) ToSprites() [][]*Sprite {
	sprites := make([][]*Sprite, len(*sh))
	for x, row := range *sh {
		sprites[x] = make([]*Sprite, len(row))
		for y := range row {
			sprites[x][y] = sh.SubSprite(x, y)
		}
	}
	return sprites
}

// NewSheetSequence creates a Sequence from a sheet and a list of x,y frame coordinates.
// A sequence will be created by getting the sheet's [i][i+1]th elements incrementally
// from the input frames. If the number of input frames is uneven, an error is returned.
func NewSheetSequence(sheet *Sheet, fps float64, frames ...int) (*Sequence, error) {
	if len(frames)%2 != 0 {
		return nil, oakerr.IndivisibleInput{
			InputName:    "frames",
			IsList:       true,
			MustDivideBy: 2,
		}
	}

	sh := *sheet

	mods := make([]Modifiable, len(frames)/2)
	for i := 0; i < len(frames); i += 2 {
		mods[i/2] = NewSprite(0, 0, sh[frames[i]][frames[i+1]])
	}
	return NewSequence(fps, mods...), nil
}
<commit_msg>A formerly unhandled panic is now a error in render.NewSheetSequence<commit_after>package render

import (
	"image"

	"github.com/oakmound/oak/oakerr"
)

// Sheet is a 2D array of image rgbas
type Sheet [][]*image.RGBA

// SubSprite gets a sprite from a sheet at the given location
func (sh *Sheet) SubSprite(x, y int) *Sprite {
	return NewSprite(0, 0, (*sh)[x][y])
}

// ToSprites returns this sheet as a 2D array of Sprites
func (sh *Sheet) ToSprites() [][]*Sprite {
	sprites := make([][]*Sprite, len(*sh))
	for x, row := range *sh {
		sprites[x] = make([]*Sprite, len(row))
		for y := range row {
			sprites[x][y] = sh.SubSprite(x, y)
		}
	}
	return sprites
}

// NewSheetSequence creates a Sequence from a sheet and a list of x,y frame coordinates.
// A sequence will be created by getting the sheet's [i][i+1]th elements incrementally
// from the input frames. If the number of input frames is uneven, an error is returned.
func NewSheetSequence(sheet *Sheet, fps float64, frames ...int) (*Sequence, error) {
	if len(frames)%2 != 0 {
		return nil, oakerr.IndivisibleInput{
			InputName:    "frames",
			IsList:       true,
			MustDivideBy: 2,
		}
	}

	sh := *sheet

	mods := make([]Modifiable, len(frames)/2)
	for i := 0; i < len(frames); i += 2 {
		if len(sh) < frames[i] || len(sh[frames[i]]) < frames[i+1] {
			return nil, oakerr.InvalidInput{InputName: "Frame requested does not exist"}
		}
		mods[i/2] = NewSprite(0, 0, sh[frames[i]][frames[i+1]])
	}
	return NewSequence(fps, mods...), nil
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"strconv"
	"sync"
	"time"
)

type ChatDB struct {
	db   *sql.DB
	lock sync.RWMutex
}

func (c *ChatDB) Put(messageId string, peerId string, subject string, message string, timestamp time.Time, read bool, outgoing bool) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	tx, err := c.db.Begin()
	if err != nil {
		return err
	}
	stm := `insert or replace into chat(messageID, peerID, subject, message, read, timestamp, outgoing) values(?,?,?,?,?,?,?)`
	stmt, err := tx.Prepare(stm)
	if err != nil {
		return err
	}
	readInt := 0
	if read {
		readInt = 1
	}

	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}

	defer stmt.Close()
	_, err = stmt.Exec(
		messageId,
		peerId,
		subject,
		message,
		readInt,
		int(timestamp.Unix()),
		outgoingInt,
	)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (c *ChatDB) GetConversations() []repo.ChatConversation {
	c.lock.RLock()
	defer c.lock.RUnlock()
	var ret []repo.ChatConversation

	stm := "select distinct peerID from chat order by timestamp desc;"
	rows, err := c.db.Query(stm)
	if err != nil {
		return ret
	}
	var ids []string
	for rows.Next() {
		var peerId string
		if err := rows.Scan(&peerId); err != nil {
			continue
		}
		ids = append(ids, peerId)

	}
	defer rows.Close()
	for _, peerId := range ids {
		stm := "select Count(*) from chat where peerID='" + peerId + "' and read=0 and subject='' and outgoing=0;"
		row := c.db.QueryRow(stm)
		var count int
		row.Scan(&count)
		stm = "select max(timestamp), message, outgoing from chat where peerID='" + peerId + "' and subject=''"
		row = c.db.QueryRow(stm)
		var m string
		var ts int
		var outInt int
		row.Scan(&ts, &m, &outInt)
		outgoing := false
		if outInt > 0 {
			outgoing = true
		}
		timestamp := time.Unix(int64(ts), 0)
		convo := repo.ChatConversation{
			PeerId:    peerId,
			Unread:    count,
			Last:      m,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, convo)
	}
	return ret
}

func (c *ChatDB) GetMessages(peerID string, subject string, offsetId string, limit int) []repo.ChatMessage {
	c.lock.RLock()
	defer c.lock.RUnlock()
	var ret []repo.ChatMessage

	var stm string
	if offsetId != "" {
		stm = "select messageID, message, read, timestamp, outgoing from chat where subject='" + subject + "' and peerID='" + peerID + "' and timestamp<(select timestamp from chat where messageID='" + offsetId + "') order by timestamp desc limit " + strconv.Itoa(limit) + " ;"
	} else {
		stm = "select messageID, message, read, timestamp, outgoing from chat where subject='" + subject + "' and peerID='" + peerID + "' order by timestamp desc limit " + strconv.Itoa(limit) + ";"
	}
	rows, err := c.db.Query(stm)
	if err != nil {
		log.Error(err)
		return ret
	}
	for rows.Next() {
		var msgID string
		var message string
		var readInt int
		var timestampInt int
		var outgoingInt int
		if err := rows.Scan(&msgID, &message, &readInt, &timestampInt, &outgoingInt); err != nil {
			continue
		}
		var read bool
		if readInt == 1 {
			read = true
		}
		var outgoing bool
		if outgoingInt == 1 {
			outgoing = true
		}
		timestamp := time.Unix(int64(timestampInt), 0)
		chatMessage := repo.ChatMessage{
			PeerId:    peerID,
			MessageId: msgID,
			Subject:   subject,
			Message:   message,
			Read:      read,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, chatMessage)
	}
	return ret
}

func (c *ChatDB) MarkAsRead(peerID string, subject string, outgoing bool, messageId string) (string, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}
	tx, err := c.db.Begin()
	if err != nil {
		return "", err
	}
	var stmt *sql.Stmt
	if messageId != "" {
		stmt, _ = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=? and timestamp<=(select timestamp from chat where messageID=?)")
		_, err = stmt.Exec(peerID, subject, outgoingInt, messageId)
	} else {
		stmt, _ = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=?")
		_, err = stmt.Exec(peerID, subject, outgoingInt)
	}
	defer stmt.Close()
	if err != nil {
		tx.Rollback()
		return "", err
	}
	tx.Commit()

	stmt2, err := c.db.Prepare("select max(timestamp), messageID from chat where peerID=? and subject=? and outgoing=?")
	if err != nil {
		return "", err
	}
	defer stmt2.Close()
	var ts int
	var msgId string
	err = stmt2.QueryRow(peerID, subject, outgoingInt).Scan(&ts, &msgId)
	if err != nil {
		return "", err
	}
	return msgId, nil
}

func (c *ChatDB) DeleteMessage(msgID string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where messageID=?", msgID)
	return nil
}

func (c *ChatDB) DeleteConversation(peerId string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where peerId=? and subject=''", peerId)
	return nil
}
<commit_msg>Prevent replacements in chat table<commit_after>package db

import (
	"database/sql"
	"github.com/OpenBazaar/openbazaar-go/repo"
	"strconv"
	"sync"
	"time"
)

type ChatDB struct {
	db   *sql.DB
	lock sync.RWMutex
}

func (c *ChatDB) Put(messageId string, peerId string, subject string, message string, timestamp time.Time, read bool, outgoing bool) error {
	c.lock.Lock()
	defer c.lock.Unlock()

	tx, err := c.db.Begin()
	if err != nil {
		return err
	}
	stm := `insert into chat(messageID, peerID, subject, message, read, timestamp, outgoing) values(?,?,?,?,?,?,?)`
	stmt, err := tx.Prepare(stm)
	if err != nil {
		return err
	}
	readInt := 0
	if read {
		readInt = 1
	}

	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}

	defer stmt.Close()
	_, err = stmt.Exec(
		messageId,
		peerId,
		subject,
		message,
		readInt,
		int(timestamp.Unix()),
		outgoingInt,
	)
	if err != nil {
		tx.Rollback()
		return err
	}
	tx.Commit()
	return nil
}

func (c *ChatDB) GetConversations() []repo.ChatConversation {
	c.lock.RLock()
	defer c.lock.RUnlock()
	var ret []repo.ChatConversation

	stm := "select distinct peerID from chat order by timestamp desc;"
	rows, err := c.db.Query(stm)
	if err != nil {
		return ret
	}
	var ids []string
	for rows.Next() {
		var peerId string
		if err := rows.Scan(&peerId); err != nil {
			continue
		}
		ids = append(ids, peerId)

	}
	defer rows.Close()
	for _, peerId := range ids {
		stm := "select Count(*) from chat where peerID='" + peerId + "' and read=0 and subject='' and outgoing=0;"
		row := c.db.QueryRow(stm)
		var count int
		row.Scan(&count)
		stm = "select max(timestamp), message, outgoing from chat where peerID='" + peerId + "' and subject=''"
		row = c.db.QueryRow(stm)
		var m string
		var ts int
		var outInt int
		row.Scan(&ts, &m, &outInt)
		outgoing := false
		if outInt > 0 {
			outgoing = true
		}
		timestamp := time.Unix(int64(ts), 0)
		convo := repo.ChatConversation{
			PeerId:    peerId,
			Unread:    count,
			Last:      m,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, convo)
	}
	return ret
}

func (c *ChatDB) GetMessages(peerID string, subject string, offsetId string, limit int) []repo.ChatMessage {
	c.lock.RLock()
	defer c.lock.RUnlock()
	var ret []repo.ChatMessage

	var stm string
	if offsetId != "" {
		stm = "select messageID, message, read, timestamp, outgoing from chat where subject='" + subject + "' and peerID='" + peerID + "' and timestamp<(select timestamp from chat where messageID='" + offsetId + "') order by timestamp desc limit " + strconv.Itoa(limit) + " ;"
	} else {
		stm = "select messageID, message, read, timestamp, outgoing from chat where subject='" + subject + "' and peerID='" + peerID + "' order by timestamp desc limit " + strconv.Itoa(limit) + ";"
	}
	rows, err := c.db.Query(stm)
	if err != nil {
		log.Error(err)
		return ret
	}
	for rows.Next() {
		var msgID string
		var message string
		var readInt int
		var timestampInt int
		var outgoingInt int
		if err := rows.Scan(&msgID, &message, &readInt, &timestampInt, &outgoingInt); err != nil {
			continue
		}
		var read bool
		if readInt == 1 {
			read = true
		}
		var outgoing bool
		if outgoingInt == 1 {
			outgoing = true
		}
		timestamp := time.Unix(int64(timestampInt), 0)
		chatMessage := repo.ChatMessage{
			PeerId:    peerID,
			MessageId: msgID,
			Subject:   subject,
			Message:   message,
			Read:      read,
			Timestamp: timestamp,
			Outgoing:  outgoing,
		}
		ret = append(ret, chatMessage)
	}
	return ret
}

func (c *ChatDB) MarkAsRead(peerID string, subject string, outgoing bool, messageId string) (string, error) {
	c.lock.Lock()
	defer c.lock.Unlock()
	outgoingInt := 0
	if outgoing {
		outgoingInt = 1
	}
	tx, err := c.db.Begin()
	if err != nil {
		return "", err
	}
	var stmt *sql.Stmt
	if messageId != "" {
		stmt, _ = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=? and timestamp<=(select timestamp from chat where messageID=?)")
		_, err = stmt.Exec(peerID, subject, outgoingInt, messageId)
	} else {
		stmt, _ = tx.Prepare("update chat set read=1 where peerID=? and subject=? and outgoing=?")
		_, err = stmt.Exec(peerID, subject, outgoingInt)
	}
	defer stmt.Close()
	if err != nil {
		tx.Rollback()
		return "", err
	}
	tx.Commit()

	stmt2, err := c.db.Prepare("select max(timestamp), messageID from chat where peerID=? and subject=? and outgoing=?")
	if err != nil {
		return "", err
	}
	defer stmt2.Close()
	var ts int
	var msgId string
	err = stmt2.QueryRow(peerID, subject, outgoingInt).Scan(&ts, &msgId)
	if err != nil {
		return "", err
	}
	return msgId, nil
}

func (c *ChatDB) DeleteMessage(msgID string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where messageID=?", msgID)
	return nil
}

func (c *ChatDB) DeleteConversation(peerId string) error {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.db.Exec("delete from chat where peerId=? and subject=''", peerId)
	return nil
}
<|endoftext|>


<commit_before>// Copyright © 2020 Phil Pennock.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt

package main

import "github.com/philpennock/character/commands/version"

// This should be "latest release + 0.0.1 with -devel suffix"
// OR if at the point where we're tagging, then that explicit tag.
const HARDCODED_VERSION = "v0.6.2"

func init() {
	if version.VersionString == "" {
		version.VersionString = HARDCODED_VERSION
	}
}
<commit_msg>Post-release -devel version<commit_after>// Copyright © 2020 Phil Pennock.
// All rights reserved, except as granted under license.
// Licensed per file LICENSE.txt

package main

import "github.com/philpennock/character/commands/version"

// This should be "latest release + 0.0.1 with -devel suffix"
// OR if at the point where we're tagging, then that explicit tag.
const HARDCODED_VERSION = "v0.6.3-devel"

func init() {
	if version.VersionString == "" {
		version.VersionString = HARDCODED_VERSION
	}
}
<|endoftext|>