<commit_before>// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fd

import (
	"math"
	"runtime"
	"sync"

	"github.com/gonum/floats"
)

// A Point is a stencil location in a difference method.
type Point struct {
	Loc   float64
	Coeff float64
}

// Method is a specific finite difference method. Method specifies the stencil,
// that is, the function locations (relative to x) which will be used to estimate
// the derivative. It also specifies the order of derivative it estimates. Order = 1
// represents the derivative, Order = 2 represents the curvature, etc.
type Method struct {
	Stencil []Point
	Order   int     // The order of the difference method (first derivative, second derivative, etc.)
	Step    float64 // Default step size for the method.
}

// Settings is the settings structure for computing finite differences.
type Settings struct {
	OriginKnown bool    // Flag that the value at the origin x is known
	OriginValue float64 // Value at the origin (only used if OriginKnown is true)
	Step        float64 // step size
	Concurrent  bool    // Should the function calls be executed concurrently
	Workers     int     // Maximum number of concurrent executions when evaluating concurrently
	Method      Method  // Finite difference method to use
}

// DefaultSettings is a basic set of settings for computing finite differences.
// Computes a central difference approximation for the first derivative
// of the function.
func DefaultSettings() *Settings {
	return &Settings{
		Method:  Central,
		Workers: runtime.GOMAXPROCS(0),
	}
}

// Derivative estimates the derivative of the function f at the given location.
// The order of derivative, sample locations, and other options are specified
// by settings. If the step is zero, then the step size of the method will
// be used.
func Derivative(f func(float64) float64, x float64, settings *Settings) float64 {
	if settings == nil {
		settings = DefaultSettings()
	}
	step := settings.Step
	if settings.Step == 0 {
		step = settings.Method.Step
	}
	var deriv float64
	method := settings.Method
	if !settings.Concurrent {
		for _, pt := range method.Stencil {
			if settings.OriginKnown && pt.Loc == 0 {
				deriv += pt.Coeff * settings.OriginValue
				continue
			}
			deriv += pt.Coeff * f(x+step*pt.Loc)
		}
		return deriv / math.Pow(step, float64(method.Order))
	}

	wg := &sync.WaitGroup{}
	mux := &sync.Mutex{}
	for _, pt := range method.Stencil {
		if settings.OriginKnown && pt.Loc == 0 {
			mux.Lock()
			deriv += pt.Coeff * settings.OriginValue
			mux.Unlock()
			continue
		}
		wg.Add(1)
		go func(pt Point) {
			defer wg.Done()
			fofx := f(x + step*pt.Loc)
			mux.Lock()
			defer mux.Unlock()
			deriv += pt.Coeff * fofx
		}(pt)
	}
	wg.Wait()
	return deriv / math.Pow(step, float64(method.Order))
}

// Gradient estimates the derivative of a multivariate function f at the location
// x. The resulting estimate is stored in-place into gradient. The order of derivative,
// sample locations, and other options are specified by settings.
// If the step size is zero, then the step size of the method will
// be used.
// Gradient panics if len(deriv) != len(x).
func Gradient(f func([]float64) float64, x []float64, settings *Settings, gradient []float64) {
	if len(gradient) != len(x) {
		panic("fd: location and gradient length mismatch")
	}
	if settings == nil {
		settings = DefaultSettings()
	}
	step := settings.Step
	if settings.Step == 0 {
		step = settings.Method.Step
	}
	if !settings.Concurrent {
		xcopy := make([]float64, len(x)) // So that x is not modified during the call
		copy(xcopy, x)
		for i := range xcopy {
			var deriv float64
			for _, pt := range settings.Method.Stencil {
				if settings.OriginKnown && pt.Loc == 0 {
					deriv += pt.Coeff * settings.OriginValue
					continue
				}
				xcopy[i] += pt.Loc * step
				deriv += pt.Coeff * f(xcopy)
				xcopy[i] = x[i]
			}
			gradient[i] = deriv / math.Pow(step, float64(settings.Method.Order))
		}
		return
	}

	nWorkers := settings.Workers
	expect := len(settings.Method.Stencil) * len(x)
	if nWorkers > expect {
		nWorkers = expect
	}

	quit := make(chan struct{})
	defer close(quit)
	sendChan := make(chan fdrun, expect)
	ansChan := make(chan fdrun, expect)

	// Launch workers. Workers receive an index and a step, and compute the answer
	for i := 0; i < settings.Workers; i++ {
		go func(sendChan <-chan fdrun, ansChan chan<- fdrun, quit <-chan struct{}) {
			xcopy := make([]float64, len(x))
			copy(xcopy, x)
			for {
				select {
				case <-quit:
					return
				case run := <-sendChan:
					xcopy[run.idx] += run.pt.Loc * step
					run.result = f(xcopy)
					xcopy[run.idx] = x[run.idx]
					ansChan <- run
				}
			}
		}(sendChan, ansChan, quit)
	}

	// Launch the distributor. Distributor sends the cases to be computed
	go func(sendChan chan<- fdrun, ansChan chan<- fdrun) {
		for i := range x {
			for _, pt := range settings.Method.Stencil {
				if settings.OriginKnown && pt.Loc == 0 {
					// Answer already known. Send the answer on the answer channel
					ansChan <- fdrun{
						idx:    i,
						pt:     pt,
						result: settings.OriginValue,
					}
					continue
				}
				// Answer not known, send the answer to be computed
				sendChan <- fdrun{
					idx: i,
					pt:  pt,
				}
			}
		}
	}(sendChan, ansChan)

	for i := range gradient {
		gradient[i] = 0
	}
	// Read in all of the results
	for i := 0; i < expect; i++ {
		run := <-ansChan
		gradient[run.idx] += run.pt.Coeff * run.result
	}
	floats.Scale(1/math.Pow(step, float64(settings.Method.Order)), gradient)
}

type fdrun struct {
	idx    int
	pt     Point
	result float64
}

// Forward represents a first-order forward difference.
var Forward = Method{
	Stencil: []Point{{Loc: 0, Coeff: -1}, {Loc: 1, Coeff: 1}},
	Order:   1,
	Step:    1e-6,
}

// Backward represents a first-order backward difference
var Backward = Method{
	Stencil: []Point{{Loc: -1, Coeff: -1}, {Loc: 0, Coeff: 1}},
	Order:   1,
	Step:    1e-6,
}

// Central represents a first-order central difference.
var Central = Method{
	Stencil: []Point{{Loc: -1, Coeff: -0.5}, {Loc: 1, Coeff: 0.5}},
	Order:   1,
	Step:    1e-6,
}

// Central2nd represents a secord-order central difference.
var Central2nd = Method{
	Stencil: []Point{{Loc: -1, Coeff: 1}, {Loc: 0, Coeff: -2}, {Loc: 1, Coeff: 1}},
	Order:   2,
	Step:    1e-3,
}
<commit_msg>Changed step to only exist in Method<commit_after>// Copyright ©2014 The gonum Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package fd

import (
	"math"
	"runtime"
	"sync"

	"github.com/gonum/floats"
)

// A Point is a stencil location in a difference method.
type Point struct {
	Loc   float64
	Coeff float64
}

// Method is a specific finite difference method. Method specifies the stencil,
// that is, the function locations (relative to x) which will be used to estimate
// the derivative. It also specifies the order of derivative it estimates. Order = 1
// represents the derivative, Order = 2 represents the curvature, etc.
type Method struct {
	Stencil []Point
	Order   int     // The order of the difference method (first derivative, second derivative, etc.)
	Step    float64 // Default step size for the method.
}

// Settings is the settings structure for computing finite differences.
type Settings struct {
	OriginKnown bool    // Flag that the value at the origin x is known
	OriginValue float64 // Value at the origin (only used if OriginKnown is true)
	Concurrent  bool    // Should the function calls be executed concurrently
	Workers     int     // Maximum number of concurrent executions when evaluating concurrently
	Method      Method  // Finite difference method to use
}

// DefaultSettings is a basic set of settings for computing finite differences.
// Computes a central difference approximation for the first derivative
// of the function.
func DefaultSettings() *Settings {
	return &Settings{
		Method:  Central,
		Workers: runtime.GOMAXPROCS(0),
	}
}

// Derivative estimates the derivative of the function f at the given location.
// The order of derivative, sample locations, and other options are specified
// by settings.
func Derivative(f func(float64) float64, x float64, settings *Settings) float64 {
	if settings == nil {
		settings = DefaultSettings()
	}
	step := settings.Method.Step
	var deriv float64
	method := settings.Method
	if !settings.Concurrent {
		for _, pt := range method.Stencil {
			if settings.OriginKnown && pt.Loc == 0 {
				deriv += pt.Coeff * settings.OriginValue
				continue
			}
			deriv += pt.Coeff * f(x+step*pt.Loc)
		}
		return deriv / math.Pow(step, float64(method.Order))
	}

	wg := &sync.WaitGroup{}
	mux := &sync.Mutex{}
	for _, pt := range method.Stencil {
		if settings.OriginKnown && pt.Loc == 0 {
			mux.Lock()
			deriv += pt.Coeff * settings.OriginValue
			mux.Unlock()
			continue
		}
		wg.Add(1)
		go func(pt Point) {
			defer wg.Done()
			fofx := f(x + step*pt.Loc)
			mux.Lock()
			defer mux.Unlock()
			deriv += pt.Coeff * fofx
		}(pt)
	}
	wg.Wait()
	return deriv / math.Pow(step, float64(method.Order))
}

// Gradient estimates the derivative of a multivariate function f at the location
// x. The resulting estimate is stored in-place into gradient. The order of derivative,
// sample locations, and other options are specified by settings.
// If the step size is zero, then the step size of the method will
// be used.
// Gradient panics if len(deriv) != len(x).
func Gradient(f func([]float64) float64, x []float64, settings *Settings, gradient []float64) {
	if len(gradient) != len(x) {
		panic("fd: location and gradient length mismatch")
	}
	if settings == nil {
		settings = DefaultSettings()
	}
	step := settings.Method.Step
	if !settings.Concurrent {
		xcopy := make([]float64, len(x)) // So that x is not modified during the call
		copy(xcopy, x)
		for i := range xcopy {
			var deriv float64
			for _, pt := range settings.Method.Stencil {
				if settings.OriginKnown && pt.Loc == 0 {
					deriv += pt.Coeff * settings.OriginValue
					continue
				}
				xcopy[i] += pt.Loc * step
				deriv += pt.Coeff * f(xcopy)
				xcopy[i] = x[i]
			}
			gradient[i] = deriv / math.Pow(step, float64(settings.Method.Order))
		}
		return
	}

	nWorkers := settings.Workers
	expect := len(settings.Method.Stencil) * len(x)
	if nWorkers > expect {
		nWorkers = expect
	}

	quit := make(chan struct{})
	defer close(quit)
	sendChan := make(chan fdrun, expect)
	ansChan := make(chan fdrun, expect)

	// Launch workers. Workers receive an index and a step, and compute the answer
	for i := 0; i < settings.Workers; i++ {
		go func(sendChan <-chan fdrun, ansChan chan<- fdrun, quit <-chan struct{}) {
			xcopy := make([]float64, len(x))
			copy(xcopy, x)
			for {
				select {
				case <-quit:
					return
				case run := <-sendChan:
					xcopy[run.idx] += run.pt.Loc * step
					run.result = f(xcopy)
					xcopy[run.idx] = x[run.idx]
					ansChan <- run
				}
			}
		}(sendChan, ansChan, quit)
	}

	// Launch the distributor. Distributor sends the cases to be computed
	go func(sendChan chan<- fdrun, ansChan chan<- fdrun) {
		for i := range x {
			for _, pt := range settings.Method.Stencil {
				if settings.OriginKnown && pt.Loc == 0 {
					// Answer already known. Send the answer on the answer channel
					ansChan <- fdrun{
						idx:    i,
						pt:     pt,
						result: settings.OriginValue,
					}
					continue
				}
				// Answer not known, send the answer to be computed
				sendChan <- fdrun{
					idx: i,
					pt:  pt,
				}
			}
		}
	}(sendChan, ansChan)

	for i := range gradient {
		gradient[i] = 0
	}
	// Read in all of the results
	for i := 0; i < expect; i++ {
		run := <-ansChan
		gradient[run.idx] += run.pt.Coeff * run.result
	}
	floats.Scale(1/math.Pow(step, float64(settings.Method.Order)), gradient)
}

type fdrun struct {
	idx    int
	pt     Point
	result float64
}

// Forward represents a first-order forward difference.
var Forward = Method{
	Stencil: []Point{{Loc: 0, Coeff: -1}, {Loc: 1, Coeff: 1}},
	Order:   1,
	Step:    1e-6,
}

// Backward represents a first-order backward difference.
var Backward = Method{
	Stencil: []Point{{Loc: -1, Coeff: -1}, {Loc: 0, Coeff: 1}},
	Order:   1,
	Step:    1e-6,
}

// Central represents a first-order central difference.
var Central = Method{
	Stencil: []Point{{Loc: -1, Coeff: -0.5}, {Loc: 1, Coeff: 0.5}},
	Order:   1,
	Step:    1e-6,
}

// Central2nd represents a secord-order central difference.
var Central2nd = Method{
	Stencil: []Point{{Loc: -1, Coeff: 1}, {Loc: 0, Coeff: -2}, {Loc: 1, Coeff: 1}},
	Order:   2,
	Step:    1e-3,
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package query

import (
	"fmt"
	"net/url"
	"reflect"
	"testing"
	"time"
)

type Nested struct {
	A   SubNested  `url:"a"`
	B   *SubNested `url:"b"`
	Ptr *SubNested `url:"ptr,omitempty"`
}

type SubNested struct {
	Value string `url:"value"`
}

func TestValues_types(t *testing.T) {
	str := "string"
	strPtr := &str
	timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)

	tests := []struct {
		in   interface{}
		want url.Values
	}{
		{
			// basic primitives
			struct {
				A string
				B int
				C uint
				D float32
				E bool
			}{},
			url.Values{
				"A": {""},
				"B": {"0"},
				"C": {"0"},
				"D": {"0"},
				"E": {"false"},
			},
		},
		{
			// pointers
			struct {
				A *string
				B *int
				C **string
				D *time.Time
			}{
				A: strPtr,
				C: &strPtr,
				D: &timeVal,
			},
			url.Values{
				"A": {str},
				"B": {""},
				"C": {str},
				"D": {"2000-01-01T12:34:56Z"},
			},
		},
		{
			// slices and arrays
			struct {
				A []string
				B []string `url:",comma"`
				C []string `url:",space"`
				D [2]string
				E [2]string `url:",comma"`
				F [2]string `url:",space"`
				G []*string `url:",space"`
				H []bool    `url:",int,space"`
				I []string  `url:",brackets"`
				J []string  `url:",semicolon"`
				K []string  `url:",numbered"`
			}{
				A: []string{"a", "b"},
				B: []string{"a", "b"},
				C: []string{"a", "b"},
				D: [2]string{"a", "b"},
				E: [2]string{"a", "b"},
				F: [2]string{"a", "b"},
				G: []*string{&str, &str},
				H: []bool{true, false},
				I: []string{"a", "b"},
				J: []string{"a", "b"},
				K: []string{"a", "b"},
			},
			url.Values{
				"A":   {"a", "b"},
				"B":   {"a,b"},
				"C":   {"a b"},
				"D":   {"a", "b"},
				"E":   {"a,b"},
				"F":   {"a b"},
				"G":   {"string string"},
				"H":   {"1 0"},
				"I[]": {"a", "b"},
				"J":   {"a;b"},
				"K0":  {"a"},
				"K1":  {"b"},
			},
		},
		{
			// other types
			struct {
				A time.Time
				B time.Time `url:",unix"`
				C bool      `url:",int"`
				D bool      `url:",int"`
			}{
				A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
				B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
				C: true,
				D: false,
			},
			url.Values{
				"A": {"2000-01-01T12:34:56Z"},
				"B": {"946730096"},
				"C": {"1"},
				"D": {"0"},
			},
		},
		{
			struct {
				Nest Nested `url:"nest"`
			}{
				Nested{
					A: SubNested{
						Value: "that",
					},
				},
			},
			url.Values{
				"nest[a][value]": {"that"},
				"nest[b]":        {""},
			},
		},
		{
			struct {
				Nest Nested `url:"nest"`
			}{
				Nested{
					Ptr: &SubNested{
						Value: "that",
					},
				},
			},
			url.Values{
				"nest[a][value]":   {""},
				"nest[b]":          {""},
				"nest[ptr][value]": {"that"},
			},
		},
		{
			nil,
			url.Values{},
		},
	}

	for i, tt := range tests {
		v, err := Values(tt.in)
		if err != nil {
			t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
		}

		if !reflect.DeepEqual(tt.want, v) {
			t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
		}
	}
}

func TestValues_omitEmpty(t *testing.T) {
	str := ""
	s := struct {
		a string
		A string
		B string  `url:",omitempty"`
		C string  `url:"-"`
		D string  `url:"omitempty"` // actually named omitempty, not an option
		E *string `url:",omitempty"`
	}{E: &str}

	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%v) returned error: %v", s, err)
	}

	want := url.Values{
		"A":         {""},
		"omitempty": {""},
		"E":         {""}, // E is included because the pointer is not empty, even though the string being pointed to is
	}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%v) returned %v, want %v", s, v, want)
	}
}

type A struct {
	B
}

type B struct {
	C string
}

type D struct {
	B
	C string
}

type e struct {
	B
	C string
}

type F struct {
	e
}

func TestValues_embeddedStructs(t *testing.T) {
	tests := []struct {
		in   interface{}
		want url.Values
	}{
		{
			A{B{C: "foo"}},
			url.Values{"C": {"foo"}},
		},
		{
			D{B: B{C: "bar"}, C: "foo"},
			url.Values{"C": {"foo", "bar"}},
		},
		{
			F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed
			url.Values{"C": {"foo", "bar"}},
		},
	}

	for i, tt := range tests {
		v, err := Values(tt.in)
		if err != nil {
			t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
		}

		if !reflect.DeepEqual(tt.want, v) {
			t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
		}
	}
}

func TestValues_invalidInput(t *testing.T) {
	_, err := Values("")
	if err == nil {
		t.Errorf("expected Values() to return an error on invalid input")
	}
}

type EncodedArgs []string

func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
	for i, arg := range m {
		v.Set(fmt.Sprintf("%s.%d", key, i), arg)
	}
	return nil
}

func TestValues_Marshaler(t *testing.T) {
	s := struct {
		Args EncodedArgs `url:"arg"`
	}{[]string{"a", "b", "c"}}
	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%q) returned error: %v", s, err)
	}

	want := url.Values{
		"arg.0": {"a"},
		"arg.1": {"b"},
		"arg.2": {"c"},
	}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%q) returned %v, want %v", s, v, want)
	}
}

func TestValues_MarshalerWithNilPointer(t *testing.T) {
	s := struct {
		Args *EncodedArgs `url:"arg"`
	}{}
	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%v) returned error: %v", s, err)
	}

	want := url.Values{}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%v) returned %v, want %v", s, v, want)
	}
}

func TestIsEmptyValue(t *testing.T) {
	str := "string"
	tests := []struct {
		value interface{}
		empty bool
	}{
		// slices, arrays, and maps
		{[]int{}, true},
		{[]int{0}, false},
		{[0]int{}, true},
		{[3]int{}, false},
		{[3]int{1}, false},
		{map[string]string{}, true},
		{map[string]string{"a": "b"}, false},

		// strings
		{"", true},
		{" ", false},
		{"a", false},

		// bool
		{true, false},
		{false, true},

		// ints of various types
		{(int)(0), true}, {(int)(1), false}, {(int)(-1), false},
		{(int8)(0), true}, {(int8)(1), false}, {(int8)(-1), false},
		{(int16)(0), true}, {(int16)(1), false}, {(int16)(-1), false},
		{(int32)(0), true}, {(int32)(1), false}, {(int32)(-1), false},
		{(int64)(0), true}, {(int64)(1), false}, {(int64)(-1), false},
		{(uint)(0), true}, {(uint)(1), false},
		{(uint8)(0), true}, {(uint8)(1), false},
		{(uint16)(0), true}, {(uint16)(1), false},
		{(uint32)(0), true}, {(uint32)(1), false},
		{(uint64)(0), true}, {(uint64)(1), false},

		// floats
		{(float32)(0), true}, {(float32)(0.0), true}, {(float32)(0.1), false},
		{(float64)(0), true}, {(float64)(0.0), true}, {(float64)(0.1), false},

		// pointers
		{(*int)(nil), true},
		{new([]int), false},
		{&str, false},

		// time
		{time.Time{}, true},
		{time.Now(), false},
	}

	for _, tt := range tests {
		got := isEmptyValue(reflect.ValueOf(tt.value))
		want := tt.empty
		if got != want {
			t.Errorf("isEmptyValue(%v) returned %t; want %t", tt.value, got, want)

		}
	}
}

func TestParseTag(t *testing.T) {
	name, opts := parseTag("field,foobar,foo")
	if name != "field" {
		t.Fatalf("name = %q, want field", name)
	}
	for _, tt := range []struct {
		opt  string
		want bool
	}{
		{"foobar", true},
		{"foo", true},
		{"bar", false},
		{"field", false},
	} {
		if opts.Contains(tt.opt) != tt.want {
			t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
		}
	}
}
<commit_msg>add isEmptyValue test for unknown type<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package query

import (
	"fmt"
	"net/url"
	"reflect"
	"testing"
	"time"
)

type Nested struct {
	A   SubNested  `url:"a"`
	B   *SubNested `url:"b"`
	Ptr *SubNested `url:"ptr,omitempty"`
}

type SubNested struct {
	Value string `url:"value"`
}

func TestValues_types(t *testing.T) {
	str := "string"
	strPtr := &str
	timeVal := time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC)

	tests := []struct {
		in   interface{}
		want url.Values
	}{
		{
			// basic primitives
			struct {
				A string
				B int
				C uint
				D float32
				E bool
			}{},
			url.Values{
				"A": {""},
				"B": {"0"},
				"C": {"0"},
				"D": {"0"},
				"E": {"false"},
			},
		},
		{
			// pointers
			struct {
				A *string
				B *int
				C **string
				D *time.Time
			}{
				A: strPtr,
				C: &strPtr,
				D: &timeVal,
			},
			url.Values{
				"A": {str},
				"B": {""},
				"C": {str},
				"D": {"2000-01-01T12:34:56Z"},
			},
		},
		{
			// slices and arrays
			struct {
				A []string
				B []string `url:",comma"`
				C []string `url:",space"`
				D [2]string
				E [2]string `url:",comma"`
				F [2]string `url:",space"`
				G []*string `url:",space"`
				H []bool    `url:",int,space"`
				I []string  `url:",brackets"`
				J []string  `url:",semicolon"`
				K []string  `url:",numbered"`
			}{
				A: []string{"a", "b"},
				B: []string{"a", "b"},
				C: []string{"a", "b"},
				D: [2]string{"a", "b"},
				E: [2]string{"a", "b"},
				F: [2]string{"a", "b"},
				G: []*string{&str, &str},
				H: []bool{true, false},
				I: []string{"a", "b"},
				J: []string{"a", "b"},
				K: []string{"a", "b"},
			},
			url.Values{
				"A":   {"a", "b"},
				"B":   {"a,b"},
				"C":   {"a b"},
				"D":   {"a", "b"},
				"E":   {"a,b"},
				"F":   {"a b"},
				"G":   {"string string"},
				"H":   {"1 0"},
				"I[]": {"a", "b"},
				"J":   {"a;b"},
				"K0":  {"a"},
				"K1":  {"b"},
			},
		},
		{
			// other types
			struct {
				A time.Time
				B time.Time `url:",unix"`
				C bool      `url:",int"`
				D bool      `url:",int"`
			}{
				A: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
				B: time.Date(2000, 1, 1, 12, 34, 56, 0, time.UTC),
				C: true,
				D: false,
			},
			url.Values{
				"A": {"2000-01-01T12:34:56Z"},
				"B": {"946730096"},
				"C": {"1"},
				"D": {"0"},
			},
		},
		{
			struct {
				Nest Nested `url:"nest"`
			}{
				Nested{
					A: SubNested{
						Value: "that",
					},
				},
			},
			url.Values{
				"nest[a][value]": {"that"},
				"nest[b]":        {""},
			},
		},
		{
			struct {
				Nest Nested `url:"nest"`
			}{
				Nested{
					Ptr: &SubNested{
						Value: "that",
					},
				},
			},
			url.Values{
				"nest[a][value]":   {""},
				"nest[b]":          {""},
				"nest[ptr][value]": {"that"},
			},
		},
		{
			nil,
			url.Values{},
		},
	}

	for i, tt := range tests {
		v, err := Values(tt.in)
		if err != nil {
			t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
		}

		if !reflect.DeepEqual(tt.want, v) {
			t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
		}
	}
}

func TestValues_omitEmpty(t *testing.T) {
	str := ""
	s := struct {
		a string
		A string
		B string  `url:",omitempty"`
		C string  `url:"-"`
		D string  `url:"omitempty"` // actually named omitempty, not an option
		E *string `url:",omitempty"`
	}{E: &str}

	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%v) returned error: %v", s, err)
	}

	want := url.Values{
		"A":         {""},
		"omitempty": {""},
		"E":         {""}, // E is included because the pointer is not empty, even though the string being pointed to is
	}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%v) returned %v, want %v", s, v, want)
	}
}

type A struct {
	B
}

type B struct {
	C string
}

type D struct {
	B
	C string
}

type e struct {
	B
	C string
}

type F struct {
	e
}

func TestValues_embeddedStructs(t *testing.T) {
	tests := []struct {
		in   interface{}
		want url.Values
	}{
		{
			A{B{C: "foo"}},
			url.Values{"C": {"foo"}},
		},
		{
			D{B: B{C: "bar"}, C: "foo"},
			url.Values{"C": {"foo", "bar"}},
		},
		{
			F{e{B: B{C: "bar"}, C: "foo"}}, // With unexported embed
			url.Values{"C": {"foo", "bar"}},
		},
	}

	for i, tt := range tests {
		v, err := Values(tt.in)
		if err != nil {
			t.Errorf("%d. Values(%q) returned error: %v", i, tt.in, err)
		}

		if !reflect.DeepEqual(tt.want, v) {
			t.Errorf("%d. Values(%q) returned %v, want %v", i, tt.in, v, tt.want)
		}
	}
}

func TestValues_invalidInput(t *testing.T) {
	_, err := Values("")
	if err == nil {
		t.Errorf("expected Values() to return an error on invalid input")
	}
}

type EncodedArgs []string

func (m EncodedArgs) EncodeValues(key string, v *url.Values) error {
	for i, arg := range m {
		v.Set(fmt.Sprintf("%s.%d", key, i), arg)
	}
	return nil
}

func TestValues_Marshaler(t *testing.T) {
	s := struct {
		Args EncodedArgs `url:"arg"`
	}{[]string{"a", "b", "c"}}
	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%q) returned error: %v", s, err)
	}

	want := url.Values{
		"arg.0": {"a"},
		"arg.1": {"b"},
		"arg.2": {"c"},
	}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%q) returned %v, want %v", s, v, want)
	}
}

func TestValues_MarshalerWithNilPointer(t *testing.T) {
	s := struct {
		Args *EncodedArgs `url:"arg"`
	}{}
	v, err := Values(s)
	if err != nil {
		t.Errorf("Values(%v) returned error: %v", s, err)
	}

	want := url.Values{}
	if !reflect.DeepEqual(want, v) {
		t.Errorf("Values(%v) returned %v, want %v", s, v, want)
	}
}

func TestIsEmptyValue(t *testing.T) {
	str := "string"
	tests := []struct {
		value interface{}
		empty bool
	}{
		// slices, arrays, and maps
		{[]int{}, true},
		{[]int{0}, false},
		{[0]int{}, true},
		{[3]int{}, false},
		{[3]int{1}, false},
		{map[string]string{}, true},
		{map[string]string{"a": "b"}, false},

		// strings
		{"", true},
		{" ", false},
		{"a", false},

		// bool
		{true, false},
		{false, true},

		// ints of various types
		{(int)(0), true}, {(int)(1), false}, {(int)(-1), false},
		{(int8)(0), true}, {(int8)(1), false}, {(int8)(-1), false},
		{(int16)(0), true}, {(int16)(1), false}, {(int16)(-1), false},
		{(int32)(0), true}, {(int32)(1), false}, {(int32)(-1), false},
		{(int64)(0), true}, {(int64)(1), false}, {(int64)(-1), false},
		{(uint)(0), true}, {(uint)(1), false},
		{(uint8)(0), true}, {(uint8)(1), false},
		{(uint16)(0), true}, {(uint16)(1), false},
		{(uint32)(0), true}, {(uint32)(1), false},
		{(uint64)(0), true}, {(uint64)(1), false},

		// floats
		{(float32)(0), true}, {(float32)(0.0), true}, {(float32)(0.1), false},
		{(float64)(0), true}, {(float64)(0.0), true}, {(float64)(0.1), false},

		// pointers
		{(*int)(nil), true},
		{new([]int), false},
		{&str, false},

		// time
		{time.Time{}, true},
		{time.Now(), false},

		// unknown type - always false unless a nil pointer, which are always empty.
		{(*struct{ int })(nil), true},
		{struct{ int }{}, false},
		{struct{ int }{0}, false},
		{struct{ int }{1}, false},
	}

	for _, tt := range tests {
		got := isEmptyValue(reflect.ValueOf(tt.value))
		want := tt.empty
		if got != want {
			t.Errorf("isEmptyValue(%v) returned %t; want %t", tt.value, got, want)

		}
	}
}

func TestParseTag(t *testing.T) {
	name, opts := parseTag("field,foobar,foo")
	if name != "field" {
		t.Fatalf("name = %q, want field", name)
	}
	for _, tt := range []struct {
		opt  string
		want bool
	}{
		{"foobar", true},
		{"foo", true},
		{"bar", false},
		{"field", false},
	} {
		if opts.Contains(tt.opt) != tt.want {
			t.Errorf("Contains(%q) = %v", tt.opt, !tt.want)
		}
	}
}
<|endoftext|>


<commit_before>package queue

import (
	"time"

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

// AggData type
type AggData struct {
	TotalReqs            int            `json:"total-reqs"`
	TotalTimedOut        int            `json:"total-timed-out"`
	TotalConnectionError int            `json:"total-conn-error"`
	AveTimeToFirst       int64          `json:"ave-time-to-first"`
	TotBytesRead         int            `json:"tot-bytes-read"`
	Statuses             map[string]int `json:"statuses"`
	AveTimeForReq        int64          `json:"ave-time-for-req"`
	AveReqPerSec         float32        `json:"ave-req-per-sec"`
	AveKBytesPerSec      float32        `json:"ave-kbytes-per-sec"`
	Slowest              int64          `json:"slowest"`
	Fastest              int64          `json:"fastest"`
	Region               string         `json:"region"`
	FatalError           string         `json:"fatal-error"`
}

// RegionsAggData type
type RegionsAggData struct {
	Regions               map[string]AggData
	TotalExpectedRequests uint
}

func (d *RegionsAggData) allRequestsReceived() bool {
	var requests uint

	for _, region := range d.Regions {
		requests += uint(region.TotalReqs)
	}

	return requests == d.TotalExpectedRequests
}

func addResult(data *AggData, result *AggData) {
	initialDataTot := data.TotalReqs
	initialDataTot64 := int64(data.TotalReqs)
	data.TotalReqs += result.TotalReqs
	data.TotalTimedOut += result.TotalTimedOut
	dataTot64 := int64(data.TotalReqs)
	resultTot64 := int64(result.TotalReqs)
	if dataTot64 > 0 {
		data.AveTimeToFirst = (data.AveTimeToFirst*initialDataTot64 + result.AveTimeToFirst*resultTot64) / dataTot64
		data.AveTimeForReq = (data.AveTimeForReq*initialDataTot64 + result.AveTimeForReq*resultTot64) / dataTot64
		data.AveReqPerSec = (data.AveReqPerSec*float32(initialDataTot) + result.AveReqPerSec*float32(result.TotalReqs)) / float32(data.TotalReqs)
		data.AveKBytesPerSec = (data.AveKBytesPerSec*float32(initialDataTot) + result.AveKBytesPerSec*float32(result.TotalReqs)) / float32(data.TotalReqs)
	}
	data.TotBytesRead += result.TotBytesRead

	for key, value := range result.Statuses {
		data.Statuses[key] += value
	}

	if result.Slowest > data.Slowest {
		data.Slowest = result.Slowest
	}
	if data.Fastest == 0 || result.Fastest < data.Fastest {
		data.Fastest = result.Fastest
	}
}

// SumRegionResults adds all the results together
func SumRegionResults(regionData *RegionsAggData) *AggData {
	var totals AggData
	totals.Statuses = make(map[string]int)
	for _, data := range regionData.Regions {
		addResult(&totals, &data)
	}
	return &totals
}

// Aggregate listens for results and sends totals, closing the channel when done
func Aggregate(awsConfig *aws.Config, queueURL string, totalExpectedRequests uint) chan RegionsAggData {
	results := make(chan RegionsAggData)
	go aggregate(results, awsConfig, queueURL, totalExpectedRequests)
	return results
}

func aggregate(results chan RegionsAggData, awsConfig *aws.Config, queueURL string, totalExpectedRequests uint) {
	defer close(results)
	data := RegionsAggData{make(map[string]AggData), totalExpectedRequests}

	adaptor := NewSQSAdaptor(awsConfig, queueURL)
	timeoutStart := time.Now()
	for {
		result := adaptor.Receive()
		if result != nil {
			regionData, ok := data.Regions[result.Region]
			if !ok {
				regionData.Statuses = make(map[string]int)
				regionData.Region = result.Region
			}
			addResult(&regionData, result)
			data.Regions[result.Region] = regionData
			results <- data
			if data.allRequestsReceived() {
				break
			}
			timeoutStart = time.Now()
		} else {
			waited := time.Since(timeoutStart)
			if waited.Seconds() > 20 {
				break
			}
		}
	}
}
<commit_msg>Change overall total to be sum of regions for req/s and bytes/s. Fixes #5<commit_after>package queue

import (
	"time"

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

// AggData type
type AggData struct {
	TotalReqs            int            `json:"total-reqs"`
	TotalTimedOut        int            `json:"total-timed-out"`
	TotalConnectionError int            `json:"total-conn-error"`
	AveTimeToFirst       int64          `json:"ave-time-to-first"`
	TotBytesRead         int            `json:"tot-bytes-read"`
	Statuses             map[string]int `json:"statuses"`
	AveTimeForReq        int64          `json:"ave-time-for-req"`
	AveReqPerSec         float32        `json:"ave-req-per-sec"`
	AveKBytesPerSec      float32        `json:"ave-kbytes-per-sec"`
	Slowest              int64          `json:"slowest"`
	Fastest              int64          `json:"fastest"`
	Region               string         `json:"region"`
	FatalError           string         `json:"fatal-error"`
}

// RegionsAggData type
type RegionsAggData struct {
	Regions               map[string]AggData
	TotalExpectedRequests uint
}

func (d *RegionsAggData) allRequestsReceived() bool {
	var requests uint

	for _, region := range d.Regions {
		requests += uint(region.TotalReqs)
	}

	return requests == d.TotalExpectedRequests
}

func addResult(data *AggData, result *AggData, isFinalSum bool) {
	initialDataTot := data.TotalReqs
	initialDataTot64 := int64(data.TotalReqs)
	data.TotalReqs += result.TotalReqs
	data.TotalTimedOut += result.TotalTimedOut
	dataTot64 := int64(data.TotalReqs)
	resultTot64 := int64(result.TotalReqs)
	if dataTot64 > 0 {
		data.AveTimeToFirst = (data.AveTimeToFirst*initialDataTot64 + result.AveTimeToFirst*resultTot64) / dataTot64
		data.AveTimeForReq = (data.AveTimeForReq*initialDataTot64 + result.AveTimeForReq*resultTot64) / dataTot64
		if isFinalSum {
			data.AveReqPerSec = data.AveReqPerSec + result.AveReqPerSec
			data.AveKBytesPerSec = data.AveKBytesPerSec + result.AveKBytesPerSec
		} else {
			data.AveReqPerSec = (data.AveReqPerSec*float32(initialDataTot) + result.AveReqPerSec*float32(result.TotalReqs)) / float32(data.TotalReqs)
			data.AveKBytesPerSec = (data.AveKBytesPerSec*float32(initialDataTot) + result.AveKBytesPerSec*float32(result.TotalReqs)) / float32(data.TotalReqs)
		}
	}
	data.TotBytesRead += result.TotBytesRead

	for key, value := range result.Statuses {
		data.Statuses[key] += value
	}

	if result.Slowest > data.Slowest {
		data.Slowest = result.Slowest
	}
	if data.Fastest == 0 || result.Fastest < data.Fastest {
		data.Fastest = result.Fastest
	}
}

// SumRegionResults adds all the results together
func SumRegionResults(regionData *RegionsAggData) *AggData {
	var totals AggData
	totals.Statuses = make(map[string]int)
	for _, data := range regionData.Regions {
		addResult(&totals, &data, true)
	}
	return &totals
}

// Aggregate listens for results and sends totals, closing the channel when done
func Aggregate(awsConfig *aws.Config, queueURL string, totalExpectedRequests uint) chan RegionsAggData {
	results := make(chan RegionsAggData)
	go aggregate(results, awsConfig, queueURL, totalExpectedRequests)
	return results
}

func aggregate(results chan RegionsAggData, awsConfig *aws.Config, queueURL string, totalExpectedRequests uint) {
	defer close(results)
	data := RegionsAggData{make(map[string]AggData), totalExpectedRequests}

	adaptor := NewSQSAdaptor(awsConfig, queueURL)
	timeoutStart := time.Now()
	for {
		result := adaptor.Receive()
		if result != nil {
			regionData, ok := data.Regions[result.Region]
			if !ok {
				regionData.Statuses = make(map[string]int)
				regionData.Region = result.Region
			}
			addResult(&regionData, result, false)
			data.Regions[result.Region] = regionData
			results <- data
			if data.allRequestsReceived() {
				break
			}
			timeoutStart = time.Now()
		} else {
			waited := time.Since(timeoutStart)
			if waited.Seconds() > 20 {
				break
			}
		}
	}
}
<|endoftext|>


<commit_before>// Package apns provides functionality for interaction with APN Service and Feedback Service
package apns

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/spf13/pflag"
	"io"
	"net"
	"runtime"
	"sync/atomic"
	"time"
)

const (
	// CommandsQueueSize specifies default notifications queue size
	CommandsQueueSize = 100000
)

var (
	env                              = "sandbox"
	commandsQueueSize         uint64 = CommandsQueueSize
	numberOfWorkers                  = uint32(runtime.NumCPU() * 2)
	certifcateFile            string
	certificatePrivateKeyFile string
	workerID                  uint32
)

func setupClientCommandLineFlags(fs *pflag.FlagSet) {
	fs.StringVar(&env, "env", env, "Environment of Apple's APNS and Feedback service gateways. For production use specify \"production\", for testing specify \"sandbox\".")
	fs.Uint64Var(&commandsQueueSize, "max-notifications", commandsQueueSize, "Number of notification that can be queued for processing at once. Once the queue is full all requests to raw push notification endpoint will result in 503 Service Unavailable response.")
	fs.Uint32Var(&numberOfWorkers, "workers", numberOfWorkers, "Number of workers that concurently process push notifications. Defaults to 2 * Number of CPU cores.")
	fs.StringVar(&certifcateFile, "cert", certifcateFile, "Absolute path to certificate file. Certificate is expected be in PEM format.")
	fs.StringVar(&certificatePrivateKeyFile, "cert-key", certificatePrivateKeyFile, "Absolute path to certificate private key file. Certificate key is expected be in PEM format.")
}

// ClientConfig holds some configuration options for Client
type ClientConfig struct {
	// Env is either "production" or "sandbox"
	Env string

	// NumberOfWorkers sets number of workers for sending push notifications
	NumberOfWorkers uint32

	// CertificateFile is absolute path to APNS certificate file
	CertificateFile string

	// CertificatePrivateKey is absolute path to APNS certificate private key file
	CertificatePrivateKeyFile string

	// CommandsQueueSize sets the queue size for push notifications
	CommandsQueueSize uint64
}

// NewClientConfig returns new client config
func NewClientConfig() (config *ClientConfig) {
	config = new(ClientConfig)
	config.Env = env
	config.NumberOfWorkers = numberOfWorkers
	config.CommandsQueueSize = commandsQueueSize
	config.CertificateFile = certifcateFile
	config.CertificatePrivateKeyFile = certificatePrivateKeyFile

	return
}

// Client struct is the main class for interacting with Apple Push Notification Service
type Client struct {
	Config             *ClientConfig
	certificate        tls.Certificate
	commandsQueue      chan CommandInterface
	workerQueue        chan chan CommandInterface
	commandErrorsQueue chan CommandErrorInterface
}

// NewClient creates a new Client
func NewClient(config *ClientConfig) (client *Client, err error) {
	client = nil
	err = nil

	logger.Debugf("Setting up client")
	logger.Debugf("Client config: %+v", config)

	// validate and create certificate
	logger.Debug("Validating certificate files...")
	var certificate tls.Certificate
	certificate, err = tls.LoadX509KeyPair(config.CertificateFile, config.CertificatePrivateKeyFile)

	if err != nil {
		logger.Fatalf("Error was encountered during certificate validation: %s", err)
		return
	}

	// setup channels
	logger.Debugf("Setting up command queue: %+v", config.CommandsQueueSize)
	nCh := make(chan CommandInterface, config.CommandsQueueSize)

	logger.Debugf("Setting up workers queue: %+v", config.NumberOfWorkers)
	wCh := make(chan chan CommandInterface, config.NumberOfWorkers)

	logger.Debugf("Setting up command errors queue: %+v", config.CommandsQueueSize)
	eCh := make(chan CommandErrorInterface, config.CommandsQueueSize)
	err = nil

	// client
	client = new(Client)

	client.Config = config
	client.certificate = certificate
	client.commandsQueue = nCh
	client.workerQueue = wCh
	client.commandErrorsQueue = eCh

	err = client.init()
	if err != nil {
		logger.Fatal(err)
	}

	return
}

// Errors returns a channel to consume command errors
func (c *Client) Errors() <-chan CommandErrorInterface {
	return c.commandErrorsQueue
}

func (c *Client) init() (err error) {
	var i uint32
	err = nil

	logger.Infof("Initializing %d worker(s)", c.Config.NumberOfWorkers)

	for i = 0; i < c.Config.NumberOfWorkers; i++ {
		atomic.AddUint32(&workerID, 1)
		worker, workerErr := newWorker(int(workerID), c)
		if workerErr != nil {
			//TODO issue warning about this and try to create the worker again later
			logger.Warningf("Worker #%d couldn't be initialized: %s", worker.id, workerErr)
		} else {
			// logger.Infof("%s%+v %s", "Worker #", worker.id, "ready")
		}
	}

	logger.Debugf("Starting client dispatcher routines")

	// errors
	go func() {
		for {
			select {
			case commandError := <-c.commandErrorsQueue:
				go func() {
					//TODO logging
					logger.Warningf("Received error: %s for command %s", commandError, commandError.GetCommand())
				}()
			}
		}
	}()

	// main dispatch loop
	go func() {
		for {
			select {
			case cmd := <-c.commandsQueue:
				go func() {
					logger.Debugf("Received command from queue %+v", cmd)
					select {
					case workerWorkQueue := <-c.workerQueue:
						logger.Debugf("Forwarding command to worker")
						workerWorkQueue <- cmd
						break

					}
				}()
			}
		}
	}()

	return
}

// ExecuteCommand queues command for execution
func (c *Client) ExecuteCommand(cmd CommandInterface) error {
	select {
	case c.commandsQueue <- cmd:
		logger.Debugf("Scheduled %s for execution", cmd)
		break

	default:
		close(cmd.Errors())
		logger.Warningf("Command queue is full, dropping command: %s", cmd)
		return NewCommandError(errors.New("apns: Queue is full, dismissing command"), cmd)
	}

	return nil
}

// CheckFeedbackService connects to Apple's feedback service and returns FeedbackResponse object
func (c *Client) CheckFeedbackService() (rsp *FeedbackResponse, err error) {
	var conn net.Conn
	var read int
	var responseBytes = make([]byte, 38)

	dialer := &net.Dialer{}
	dialer.KeepAlive = time.Second * 10

	var gateway string
	if c.isProdEnv() {
		gateway = FeedbackGatewayProduction
	} else {
		gateway = FeedbackGatewaySandbox
	}

	tlsConfig := &tls.Config{}
	tlsConfig.ServerName = gateway
	tlsConfig.Certificates = []tls.Certificate{c.certificate}

	logger.Infof("Connecting to %s:%d", tlsConfig.ServerName, FeedbackGatewayPort)

	conn, err = dialer.Dial("tcp", fmt.Sprintf("%s:%d", tlsConfig.ServerName, FeedbackGatewayPort))
	if err != nil {
		logger.Error("Error connecting feedback service")
		return
	}

	logger.Debugf("Connected to %s", conn.RemoteAddr().String())

	tlsConn := tls.Client(conn, tlsConfig)
	err = tlsConn.Handshake()
	if err != nil {
		logger.Error("Error establishing tls connection to feedback service")
		return
	}

	rsp = new(FeedbackResponse)

	for {
		tlsConn.SetReadDeadline(time.Now().Add(time.Millisecond * 500))
		read, err = tlsConn.Read(responseBytes)
		logger.Debugf("Read %d bytes %+v", read, responseBytes)

		if read > 0 {
			rsp.addEntryFromBytes(responseBytes)
		}

		if err != nil {
			if err == io.EOF {
				logger.Info("Read all data from feedback service and connection was closed by peer")
				err = nil
				return
			}

			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				err = nil
				return
			}

			logger.Warningf("Error reading response from feedback service: %s", err)
		}
	}

	return
}

func (c *Client) isProdEnv() bool {
	return c.Config.Env == "production"
}
<commit_msg>apns package godoc<commit_after>// Package exposes APNS Provider for Apple's APNS and Feedback services and uses job/worker pattern to process notifications concurrently
package apns

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/spf13/pflag"
	"io"
	"net"
	"runtime"
	"sync/atomic"
	"time"
)

const (
	// CommandsQueueSize specifies default notifications queue size
	CommandsQueueSize = 100000
)

var (
	env                              = "sandbox"
	commandsQueueSize         uint64 = CommandsQueueSize
	numberOfWorkers                  = uint32(runtime.NumCPU() * 2)
	certifcateFile            string
	certificatePrivateKeyFile string
	workerID                  uint32
)

func setupClientCommandLineFlags(fs *pflag.FlagSet) {
	fs.StringVar(&env, "env", env, "Environment of Apple's APNS and Feedback service gateways. For production use specify \"production\", for testing specify \"sandbox\".")
	fs.Uint64Var(&commandsQueueSize, "max-notifications", commandsQueueSize, "Number of notification that can be queued for processing at once. Once the queue is full all requests to raw push notification endpoint will result in 503 Service Unavailable response.")
	fs.Uint32Var(&numberOfWorkers, "workers", numberOfWorkers, "Number of workers that concurently process push notifications. Defaults to 2 * Number of CPU cores.")
	fs.StringVar(&certifcateFile, "cert", certifcateFile, "Absolute path to certificate file. Certificate is expected be in PEM format.")
	fs.StringVar(&certificatePrivateKeyFile, "cert-key", certificatePrivateKeyFile, "Absolute path to certificate private key file. Certificate key is expected be in PEM format.")
}

// ClientConfig holds some configuration options for Client
type ClientConfig struct {
	// Env is either "production" or "sandbox"
	Env string

	// NumberOfWorkers sets number of workers for sending push notifications
	NumberOfWorkers uint32

	// CertificateFile is absolute path to APNS certificate file
	CertificateFile string

	// CertificatePrivateKey is absolute path to APNS certificate private key file
	CertificatePrivateKeyFile string

	// CommandsQueueSize sets the queue size for push notifications
	CommandsQueueSize uint64
}

// NewClientConfig returns new client config
func NewClientConfig() (config *ClientConfig) {
	config = new(ClientConfig)
	config.Env = env
	config.NumberOfWorkers = numberOfWorkers
	config.CommandsQueueSize = commandsQueueSize
	config.CertificateFile = certifcateFile
	config.CertificatePrivateKeyFile = certificatePrivateKeyFile

	return
}

// Client struct is the main class for interacting with Apple Push Notification Service
type Client struct {
	Config             *ClientConfig
	certificate        tls.Certificate
	commandsQueue      chan CommandInterface
	workerQueue        chan chan CommandInterface
	commandErrorsQueue chan CommandErrorInterface
}

// NewClient creates a new Client
func NewClient(config *ClientConfig) (client *Client, err error) {
	client = nil
	err = nil

	logger.Debugf("Setting up client")
	logger.Debugf("Client config: %+v", config)

	// validate and create certificate
	logger.Debug("Validating certificate files...")
	var certificate tls.Certificate
	certificate, err = tls.LoadX509KeyPair(config.CertificateFile, config.CertificatePrivateKeyFile)

	if err != nil {
		logger.Fatalf("Error was encountered during certificate validation: %s", err)
		return
	}

	// setup channels
	logger.Debugf("Setting up command queue: %+v", config.CommandsQueueSize)
	nCh := make(chan CommandInterface, config.CommandsQueueSize)

	logger.Debugf("Setting up workers queue: %+v", config.NumberOfWorkers)
	wCh := make(chan chan CommandInterface, config.NumberOfWorkers)

	logger.Debugf("Setting up command errors queue: %+v", config.CommandsQueueSize)
	eCh := make(chan CommandErrorInterface, config.CommandsQueueSize)
	err = nil

	// client
	client = new(Client)

	client.Config = config
	client.certificate = certificate
	client.commandsQueue = nCh
	client.workerQueue = wCh
	client.commandErrorsQueue = eCh

	err = client.init()
	if err != nil {
		logger.Fatal(err)
	}

	return
}

// Errors returns a channel to consume command errors
func (c *Client) Errors() <-chan CommandErrorInterface {
	return c.commandErrorsQueue
}

func (c *Client) init() (err error) {
	var i uint32
	err = nil

	logger.Infof("Initializing %d worker(s)", c.Config.NumberOfWorkers)

	for i = 0; i < c.Config.NumberOfWorkers; i++ {
		atomic.AddUint32(&workerID, 1)
		worker, workerErr := newWorker(int(workerID), c)
		if workerErr != nil {
			//TODO issue warning about this and try to create the worker again later
			logger.Warningf("Worker #%d couldn't be initialized: %s", worker.id, workerErr)
		} else {
			// logger.Infof("%s%+v %s", "Worker #", worker.id, "ready")
		}
	}

	logger.Debugf("Starting client dispatcher routines")

	// errors
	go func() {
		for {
			select {
			case commandError := <-c.commandErrorsQueue:
				go func() {
					//TODO logging
					logger.Warningf("Received error: %s for command %s", commandError, commandError.GetCommand())
				}()
			}
		}
	}()

	// main dispatch loop
	go func() {
		for {
			select {
			case cmd := <-c.commandsQueue:
				go func() {
					logger.Debugf("Received command from queue %+v", cmd)
					select {
					case workerWorkQueue := <-c.workerQueue:
						logger.Debugf("Forwarding command to worker")
						workerWorkQueue <- cmd
						break

					}
				}()
			}
		}
	}()

	return
}

// ExecuteCommand queues command for execution
func (c *Client) ExecuteCommand(cmd CommandInterface) error {
	select {
	case c.commandsQueue <- cmd:
		logger.Debugf("Scheduled %s for execution", cmd)
		break

	default:
		close(cmd.Errors())
		logger.Warningf("Command queue is full, dropping command: %s", cmd)
		return NewCommandError(errors.New("apns: Queue is full, dismissing command"), cmd)
	}

	return nil
}

// CheckFeedbackService connects to Apple's feedback service and returns FeedbackResponse object
func (c *Client) CheckFeedbackService() (rsp *FeedbackResponse, err error) {
	var conn net.Conn
	var read int
	var responseBytes = make([]byte, 38)

	dialer := &net.Dialer{}
	dialer.KeepAlive = time.Second * 10

	var gateway string
	if c.isProdEnv() {
		gateway = FeedbackGatewayProduction
	} else {
		gateway = FeedbackGatewaySandbox
	}

	tlsConfig := &tls.Config{}
	tlsConfig.ServerName = gateway
	tlsConfig.Certificates = []tls.Certificate{c.certificate}

	logger.Infof("Connecting to %s:%d", tlsConfig.ServerName, FeedbackGatewayPort)

	conn, err = dialer.Dial("tcp", fmt.Sprintf("%s:%d", tlsConfig.ServerName, FeedbackGatewayPort))
	if err != nil {
		logger.Error("Error connecting feedback service")
		return
	}

	logger.Debugf("Connected to %s", conn.RemoteAddr().String())

	tlsConn := tls.Client(conn, tlsConfig)
	err = tlsConn.Handshake()
	if err != nil {
		logger.Error("Error establishing tls connection to feedback service")
		return
	}

	rsp = new(FeedbackResponse)

	for {
		tlsConn.SetReadDeadline(time.Now().Add(time.Millisecond * 500))
		read, err = tlsConn.Read(responseBytes)
		logger.Debugf("Read %d bytes %+v", read, responseBytes)

		if read > 0 {
			rsp.addEntryFromBytes(responseBytes)
		}

		if err != nil {
			if err == io.EOF {
				logger.Info("Read all data from feedback service and connection was closed by peer")
				err = nil
				return
			}

			if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
				err = nil
				return
			}

			logger.Warningf("Error reading response from feedback service: %s", err)
		}
	}

	return
}

func (c *Client) isProdEnv() bool {
	return c.Config.Env == "production"
}
<|endoftext|>


<commit_before>package findhdr

/*
TODO
----
- what about configurable # of shots?
- allow any variation of bias values
- should we really be treating bias value as a string? why didn't StringVal() work?
- support non-JPG filenames
- print out some stats afterwards
- configurable verbosity!
*/

import (
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"

	"github.com/rwcarlsen/goexif/exif"
)

func init() {
	addMimeTypes()
}

// Hdr represents a set of candidate images which may -- or may not -- represent
// valid sources for a single merged hdr image. See IsHdr() for details on
// what constitues valid sources.
type Hdr struct {
	a *Image
	b *Image
	c *Image
}

// Image represents a single image file and its metadata.
type Image struct {
	Path string
	Info os.FileInfo
	Meta ImageMeta
}

// ImageMeta is an interface providing access to image metadata.
type ImageMeta interface {
	PixelXDimension() (int, error)
	PixelYDimension() (int, error)
	ExposureBiasValue() (string, error)
}

/*
Metadata available from exif.Exif:

*exif.Exif, DateTime: "2017:02:26 13:04:32"
ExifVersion: "0221"
DateTimeOriginal: "2017:02:26 13:04:32"
ComponentsConfiguration: ""
Flash: 16
Make: "Canon"
ThumbJPEGInterchangeFormat: 10988
ExposureTime: "3/10"
ExposureProgram: 3
ExposureBiasValue: "0/1"
FocalLength: "18/1"
MakerNote: ""
SubSecTimeDigitized: "00"
ExifIFDPointer: 360
FocalPlaneXResolution: "5184000/907"
InteroperabilityIFDPointer: 9052
YResolution: "72/1"
UserComment: ""
PixelXDimension: 5184
FocalPlaneResolutionUnit: 2
ExposureMode: 0
GPSVersionID: [2,2,0,0]
Orientation: 8
WhiteBalance: 0
InteroperabilityIndex: "R98"
Artist: ""
FNumber: "35/10"
MeteringMode: 2
ColorSpace: 1
CustomRendered: 0
ThumbJPEGInterchangeFormatLength: 20838
YCbCrPositioning: 2
ApertureValue: "237568/65536"
SceneCaptureType: 0
Model: "Canon EOS 7D"
DateTimeDigitized: "2017:02:26 13:04:32"
SubSecTimeOriginal: "00"
PixelYDimension: 3456
GPSInfoIFDPointer: 9098
ResolutionUnit: 2
Copyright: ""
ISOSpeedRatings: 400
ShutterSpeedValue: "106496/65536"
SubSecTime: "00"
FlashpixVersion: "0100"
FocalPlaneYResolution: "3456000/595"
XResolution: "72/1"
*/
type exifMeta struct {
	exif *exif.Exif
}

// PixelXDimension returns the width of the image.
func (e *exifMeta) PixelXDimension() (val int, err error) {
	tag, err := e.exif.Get(exif.PixelXDimension)
	if err != nil {
		return
	}

	val, err = tag.Int(0)
	return
}

// PixelYDimension returns the height of the image.
func (e *exifMeta) PixelYDimension() (val int, err error) {
	tag, err := e.exif.Get(exif.PixelYDimension)
	if err != nil {
		return
	}

	val, err = tag.Int(0)
	return
}

// ExposureBiasValue returns the exposure bias value (exposure compensation)
// for the image. This is a string in the form "X/1" (NOTE: including the double-quotes):
// - "0/1" for normal
// - "-2/1" for darker
// - "2/1" for lighter)
func (e *exifMeta) ExposureBiasValue() (val string, err error) {
	tag, err := e.exif.Get(exif.ExposureBiasValue)
	if err != nil {
		return
	}

	val = tag.String()
	return
}

// Add adds a candidate image to an Hdr.
func (hdr *Hdr) Add(meta ImageMeta, path string, info os.FileInfo) {
	img := &Image{Path: path, Info: info, Meta: meta}
	if hdr.a == nil {
		hdr.a = img
	} else if hdr.b == nil {
		hdr.b = img
	} else if hdr.c == nil {
		hdr.c = img
	} else {
		hdr.a = hdr.b
		hdr.b = hdr.c
		hdr.c = img
	}
}

func (hdr *Hdr) String() string {
	return fmt.Sprintf("[%s, %s, %s]", hdr.a.Info.Name(), hdr.b.Info.Name(), hdr.c.Info.Name())
}

// IsHdr returns true if the Hdr contains a valid set of source images which can be
// merged into a single hdr image. Source images must have identical dimensions
// but unique exposure bias values (e.g., "0/1", "-2/1", and "2/1").
func (hdr *Hdr) IsHdr() (bool, error) {
	sufficient := hdr.sufficientImages()
	if !sufficient {
		// fmt.Println("Skipping: insufficient candidates")
		return false, nil
	}

	if match, err := hdr.dimensionsMatch(); err != nil || !match {
		// fmt.Println("Skipping: x dimension mismatch", ax, bx, cx)
		return match, err
	}

	if unique, err := hdr.biasValuesUnique(); err != nil || !unique {
		// fmt.Println("Skipping: bias mismatch", abias, bbias, cbias)
		return unique, err
	}

	return true, nil
}

func (hdr *Hdr) sufficientImages() bool {
	return !(hdr.a == nil || hdr.b == nil || hdr.c == nil)
}

func (hdr *Hdr) dimensionsMatch() (bool, error) {
	ydim, xdim := 0, 0
	for _, img := range hdr.Images() {
		y, err := img.Meta.PixelYDimension()
		if err != nil {
			return false, err
		}
		x, err := img.Meta.PixelXDimension()
		if err != nil {
			return false, err
		}
		if ydim == 0 {
			ydim = y
		} else if y != ydim {
			return false, nil
		}
		if xdim == 0 {
			xdim = x
		} else if x != xdim {
			return false, nil
		}
	}

	return true, nil
}

func (hdr *Hdr) biasValuesUnique() (bool, error) {
	biases := map[string]bool{}
	for _, img := range hdr.Images() {
		bias, err := img.Meta.ExposureBiasValue()
		if err != nil {
			return false, err
		}

		if _, ok := biases[bias]; ok {
			return false, nil
		}
		biases[bias] = true
	}
	return true, nil
}

// Images returns the candidate images in this Hdr.
func (hdr *Hdr) Images() []*Image {
	return []*Image{
		hdr.a,
		hdr.b,
		hdr.c,
	}
}

// FileFinderFunc is the type of the function called for each file or directory
// visited by Find.
type FileFinderFunc filepath.WalkFunc

// FileFinder is the interface providing a list of files or directories to
// process via the Find method.
type FileFinder interface {
	Find(fileFinderFn FileFinderFunc) error
}

// filePathWalker is a FileFinder using filepath.Walk to visit all files
// and directories in the given root path.
type filePathWalker struct {
	Root string
}

// NewFileFinder returns a FileFinder which walks all files in the given directory.
func NewFileFinder(path string) FileFinder {
	return filePathWalker{Root: path}
}

// Find walks the file tree rooted at root, calling fileFinderFn for each file or
// directory in the tree, including root.
func (f filePathWalker) Find(fileFinderFn FileFinderFunc) error {
	return filepath.Walk(f.Root, func(path string, info os.FileInfo, err error) error {
		return fileFinderFn(path, info, err)
	})
}

// Decoder is the interface providing exif metadata for an image file via the
// Decode method.
type Decoder interface {
	Decode(path string) (meta ImageMeta, err error)
}

// exifDecoder is a Decoder using exif.Exif to extract exif tags from an image file.
type exifDecoder struct{}

// NewDecoder returns a Decoder using exif.Exif to extract exif tags from an image file.
func NewDecoder() Decoder {
	return &exifDecoder{}
}

// Decode returns Exif metadata for an image file. If the file does not exist,
// we do not have permission to read it, or if any other error occurs while
// extracting metadata from the image, it will be returned along with nil metadata.
func (d *exifDecoder) Decode(path string) (ImageMeta, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	w := new(exifMeta)
	w.exif, err = exif.Decode(f)
	if err != nil {
		return nil, err
	}
	return w, nil
}

// HdrFunc is the type of the function called for each valid Hdr found by Find.
type HdrFunc func(hdr *Hdr)

// Find visits all files found by the file finder, tests if they are images,
// uses the decoder to extract their metadata, and adds them to a candidate Hdr.
// Every valid Hdr found this way is delivered to HdrFunc (see IsHdr() for
// details on what constitutes a valid hdr).
func Find(finder FileFinder, decoder Decoder, hdrFn HdrFunc) error {
	hdr := Hdr{}

	// See https://golang.org/pkg/path/filepath/#WalkFunc
	return finder.Find(func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info != nil && info.IsDir() {
			return nil
		}

		if isimage := hasImageMimeType(path); !isimage {
			return nil
		}

		x, err := decoder.Decode(path)
		if err != nil {
			fmt.Println(err)
			return nil
		}

		hdr.Add(x, path, info)

		isHdr, err := hdr.IsHdr()
		if err != nil {
			fmt.Println(err)
			return nil
		}

		if isHdr {
			if hdrFn != nil {
				hdrFn(&hdr)
			}
			hdr = Hdr{}
		}

		return nil // or SkipDir to skip processng this dir
	})
}

// hasImageMimeType returns true if the image at the given path has a mime-type of
// "image/*". Mime types are calculated using mime.TypeByExtension, which
// understands mime types built in to the system, as well as custom mime types
// mappings which we have added for all known RAW image extensions.
func hasImageMimeType(path string) bool {
	ext := filepath.Ext(path)
	mimetype := mime.TypeByExtension(ext)
	return strings.HasPrefix(mimetype, "image/")
}

func addMimeTypes() {
	// See https://bugs.freedesktop.org/show_bug.cgi?id=8170
	var mimeTypes = []struct {
		ext      string
		mimetype string
	}{
		{".crw", "image/x-canon-crw"},
	}

	for _, mimetype := range mimeTypes {
		mime.AddExtensionType(mimetype.ext, mimetype.mimetype)
	}
}
<commit_msg>Switch exif library dependency to active fork. https://github.com/rwcarlsen/goexif has ceased development; to https://github.com/xor-gate/goexif2 is the active fork.<commit_after>package findhdr

/*
TODO
----
- what about configurable # of shots?
- allow any variation of bias values
- should we really be treating bias value as a string? why didn't StringVal() work?
- support non-JPG filenames
- print out some stats afterwards
- configurable verbosity!
*/

import (
	"fmt"
	"mime"
	"os"
	"path/filepath"
	"strings"

	"github.com/xor-gate/goexif2/exif"
)

func init() {
	addMimeTypes()
}

// Hdr represents a set of candidate images which may -- or may not -- represent
// valid sources for a single merged hdr image. See IsHdr() for details on
// what constitues valid sources.
type Hdr struct {
	a *Image
	b *Image
	c *Image
}

// Image represents a single image file and its metadata.
type Image struct {
	Path string
	Info os.FileInfo
	Meta ImageMeta
}

// ImageMeta is an interface providing access to image metadata.
type ImageMeta interface {
	PixelXDimension() (int, error)
	PixelYDimension() (int, error)
	ExposureBiasValue() (string, error)
}

/*
Metadata available from exif.Exif:

*exif.Exif, DateTime: "2017:02:26 13:04:32"
ExifVersion: "0221"
DateTimeOriginal: "2017:02:26 13:04:32"
ComponentsConfiguration: ""
Flash: 16
Make: "Canon"
ThumbJPEGInterchangeFormat: 10988
ExposureTime: "3/10"
ExposureProgram: 3
ExposureBiasValue: "0/1"
FocalLength: "18/1"
MakerNote: ""
SubSecTimeDigitized: "00"
ExifIFDPointer: 360
FocalPlaneXResolution: "5184000/907"
InteroperabilityIFDPointer: 9052
YResolution: "72/1"
UserComment: ""
PixelXDimension: 5184
FocalPlaneResolutionUnit: 2
ExposureMode: 0
GPSVersionID: [2,2,0,0]
Orientation: 8
WhiteBalance: 0
InteroperabilityIndex: "R98"
Artist: ""
FNumber: "35/10"
MeteringMode: 2
ColorSpace: 1
CustomRendered: 0
ThumbJPEGInterchangeFormatLength: 20838
YCbCrPositioning: 2
ApertureValue: "237568/65536"
SceneCaptureType: 0
Model: "Canon EOS 7D"
DateTimeDigitized: "2017:02:26 13:04:32"
SubSecTimeOriginal: "00"
PixelYDimension: 3456
GPSInfoIFDPointer: 9098
ResolutionUnit: 2
Copyright: ""
ISOSpeedRatings: 400
ShutterSpeedValue: "106496/65536"
SubSecTime: "00"
FlashpixVersion: "0100"
FocalPlaneYResolution: "3456000/595"
XResolution: "72/1"
*/
type exifMeta struct {
	exif *exif.Exif
}

// PixelXDimension returns the width of the image.
func (e *exifMeta) PixelXDimension() (val int, err error) {
	tag, err := e.exif.Get(exif.PixelXDimension)
	if err != nil {
		return
	}

	val, err = tag.Int(0)
	return
}

// PixelYDimension returns the height of the image.
func (e *exifMeta) PixelYDimension() (val int, err error) {
	tag, err := e.exif.Get(exif.PixelYDimension)
	if err != nil {
		return
	}

	val, err = tag.Int(0)
	return
}

// ExposureBiasValue returns the exposure bias value (exposure compensation)
// for the image. This is a string in the form "X/1" (NOTE: including the double-quotes):
// - "0/1" for normal
// - "-2/1" for darker
// - "2/1" for lighter)
func (e *exifMeta) ExposureBiasValue() (val string, err error) {
	tag, err := e.exif.Get(exif.ExposureBiasValue)
	if err != nil {
		return
	}

	val = tag.String()
	return
}

// Add adds a candidate image to an Hdr.
func (hdr *Hdr) Add(meta ImageMeta, path string, info os.FileInfo) {
	img := &Image{Path: path, Info: info, Meta: meta}
	if hdr.a == nil {
		hdr.a = img
	} else if hdr.b == nil {
		hdr.b = img
	} else if hdr.c == nil {
		hdr.c = img
	} else {
		hdr.a = hdr.b
		hdr.b = hdr.c
		hdr.c = img
	}
}

func (hdr *Hdr) String() string {
	return fmt.Sprintf("[%s, %s, %s]", hdr.a.Info.Name(), hdr.b.Info.Name(), hdr.c.Info.Name())
}

// IsHdr returns true if the Hdr contains a valid set of source images which can be
// merged into a single hdr image. Source images must have identical dimensions
// but unique exposure bias values (e.g., "0/1", "-2/1", and "2/1").
func (hdr *Hdr) IsHdr() (bool, error) {
	sufficient := hdr.sufficientImages()
	if !sufficient {
		// fmt.Println("Skipping: insufficient candidates")
		return false, nil
	}

	if match, err := hdr.dimensionsMatch(); err != nil || !match {
		// fmt.Println("Skipping: x dimension mismatch", ax, bx, cx)
		return match, err
	}

	if unique, err := hdr.biasValuesUnique(); err != nil || !unique {
		// fmt.Println("Skipping: bias mismatch", abias, bbias, cbias)
		return unique, err
	}

	return true, nil
}

func (hdr *Hdr) sufficientImages() bool {
	return !(hdr.a == nil || hdr.b == nil || hdr.c == nil)
}

func (hdr *Hdr) dimensionsMatch() (bool, error) {
	ydim, xdim := 0, 0
	for _, img := range hdr.Images() {
		y, err := img.Meta.PixelYDimension()
		if err != nil {
			return false, err
		}
		x, err := img.Meta.PixelXDimension()
		if err != nil {
			return false, err
		}
		if ydim == 0 {
			ydim = y
		} else if y != ydim {
			return false, nil
		}
		if xdim == 0 {
			xdim = x
		} else if x != xdim {
			return false, nil
		}
	}

	return true, nil
}

func (hdr *Hdr) biasValuesUnique() (bool, error) {
	biases := map[string]bool{}
	for _, img := range hdr.Images() {
		bias, err := img.Meta.ExposureBiasValue()
		if err != nil {
			return false, err
		}

		if _, ok := biases[bias]; ok {
			return false, nil
		}
		biases[bias] = true
	}
	return true, nil
}

// Images returns the candidate images in this Hdr.
func (hdr *Hdr) Images() []*Image {
	return []*Image{
		hdr.a,
		hdr.b,
		hdr.c,
	}
}

// FileFinderFunc is the type of the function called for each file or directory
// visited by Find.
type FileFinderFunc filepath.WalkFunc

// FileFinder is the interface providing a list of files or directories to
// process via the Find method.
type FileFinder interface {
	Find(fileFinderFn FileFinderFunc) error
}

// filePathWalker is a FileFinder using filepath.Walk to visit all files
// and directories in the given root path.
type filePathWalker struct {
	Root string
}

// NewFileFinder returns a FileFinder which walks all files in the given directory.
func NewFileFinder(path string) FileFinder {
	return filePathWalker{Root: path}
}

// Find walks the file tree rooted at root, calling fileFinderFn for each file or
// directory in the tree, including root.
func (f filePathWalker) Find(fileFinderFn FileFinderFunc) error {
	return filepath.Walk(f.Root, func(path string, info os.FileInfo, err error) error {
		return fileFinderFn(path, info, err)
	})
}

// Decoder is the interface providing exif metadata for an image file via the
// Decode method.
type Decoder interface {
	Decode(path string) (meta ImageMeta, err error)
}

// exifDecoder is a Decoder using exif.Exif to extract exif tags from an image file.
type exifDecoder struct{}

// NewDecoder returns a Decoder using exif.Exif to extract exif tags from an image file.
func NewDecoder() Decoder {
	return &exifDecoder{}
}

// Decode returns Exif metadata for an image file. If the file does not exist,
// we do not have permission to read it, or if any other error occurs while
// extracting metadata from the image, it will be returned along with nil metadata.
func (d *exifDecoder) Decode(path string) (ImageMeta, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	w := new(exifMeta)
	w.exif, err = exif.Decode(f)
	if err != nil {
		return nil, err
	}
	return w, nil
}

// HdrFunc is the type of the function called for each valid Hdr found by Find.
type HdrFunc func(hdr *Hdr)

// Find visits all files found by the file finder, tests if they are images,
// uses the decoder to extract their metadata, and adds them to a candidate Hdr.
// Every valid Hdr found this way is delivered to HdrFunc (see IsHdr() for
// details on what constitutes a valid hdr).
func Find(finder FileFinder, decoder Decoder, hdrFn HdrFunc) error {
	hdr := Hdr{}

	// See https://golang.org/pkg/path/filepath/#WalkFunc
	return finder.Find(func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info != nil && info.IsDir() {
			return nil
		}

		if isimage := hasImageMimeType(path); !isimage {
			return nil
		}

		x, err := decoder.Decode(path)
		if err != nil {
			fmt.Println(err)
			return nil
		}

		hdr.Add(x, path, info)

		isHdr, err := hdr.IsHdr()
		if err != nil {
			fmt.Println(err)
			return nil
		}

		if isHdr {
			if hdrFn != nil {
				hdrFn(&hdr)
			}
			hdr = Hdr{}
		}

		return nil // or SkipDir to skip processng this dir
	})
}

// hasImageMimeType returns true if the image at the given path has a mime-type of
// "image/*". Mime types are calculated using mime.TypeByExtension, which
// understands mime types built in to the system, as well as custom mime types
// mappings which we have added for all known RAW image extensions.
func hasImageMimeType(path string) bool {
	ext := filepath.Ext(path)
	mimetype := mime.TypeByExtension(ext)
	return strings.HasPrefix(mimetype, "image/")
}

func addMimeTypes() {
	// See https://bugs.freedesktop.org/show_bug.cgi?id=8170
	var mimeTypes = []struct {
		ext      string
		mimetype string
	}{
		{".crw", "image/x-canon-crw"},
	}

	for _, mimetype := range mimeTypes {
		mime.AddExtensionType(mimetype.ext, mimetype.mimetype)
	}
}
<|endoftext|>


<commit_before>package email

import (
	"fmt"
	"net"
	"net/smtp"
	"strings"
)

// Message creates a email to be sent
type Message struct {
	To      string
	From    string
	Subject string
	Body    string
}

var (
	ports = []int{25, 2525, 587}
)

// Send sends a message to recipient(s) listed in the 'To' field of a Message
func (m Message) Send() error {
	if !strings.Contains(m.To, "@") {
		return fmt.Errorf("Invalid recipient address: <%s>", m.To)
	}

	host := strings.Split(m.To, "@")[1]
	addrs, err := net.LookupMX(host)
	if err != nil {
		return err
	}

	c, err := newClient(addrs, ports)
	if err != nil {
		return err
	}

	err = send(m, c)
	if err != nil {
		return err
	}

	return nil
}

func newClient(mx []*net.MX, ports []int) (*smtp.Client, error) {
	for i := range mx {
		for j := range ports {
			server := strings.TrimSuffix(mx[i].Host, ".")
			hostPort := fmt.Sprintf("%s:%d", server, ports[j])
			client, err := smtp.Dial(hostPort)
			if err != nil {
				if j == len(ports)-1 {
					return nil, err
				}

				continue
			}

			return client, nil
		}
	}

	return nil, fmt.Errorf("Coudln't connect to servers %v on any common port.", mx)
}

func send(m Message, c *smtp.Client) error {
	if err := c.Mail(m.From); err != nil {
		return err
	}

	if err := c.Rcpt(m.To); err != nil {
		return err
	}

	msg, err := c.Data()
	if err != nil {
		return err
	}

	if m.Subject != "" {
		_, err = msg.Write([]byte("Subject: " + m.Subject + "\r\n"))
		if err != nil {
			return err
		}
	}

	if m.From != "" {
		_, err = msg.Write([]byte("From: " + m.From + "\r\n"))
		if err != nil {
			return err
		}
	}

	if m.To != "" {
		_, err = msg.Write([]byte("To: " + m.To + "\r\n"))
		if err != nil {
			return err
		}
	}

	_, err = fmt.Fprint(msg, m.Body)
	if err != nil {
		return err
	}

	err = msg.Close()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Fix typo in error message<commit_after>package email

import (
	"fmt"
	"net"
	"net/smtp"
	"strings"
)

// Message creates a email to be sent
type Message struct {
	To      string
	From    string
	Subject string
	Body    string
}

var (
	ports = []int{25, 2525, 587}
)

// Send sends a message to recipient(s) listed in the 'To' field of a Message
func (m Message) Send() error {
	if !strings.Contains(m.To, "@") {
		return fmt.Errorf("Invalid recipient address: <%s>", m.To)
	}

	host := strings.Split(m.To, "@")[1]
	addrs, err := net.LookupMX(host)
	if err != nil {
		return err
	}

	c, err := newClient(addrs, ports)
	if err != nil {
		return err
	}

	err = send(m, c)
	if err != nil {
		return err
	}

	return nil
}

func newClient(mx []*net.MX, ports []int) (*smtp.Client, error) {
	for i := range mx {
		for j := range ports {
			server := strings.TrimSuffix(mx[i].Host, ".")
			hostPort := fmt.Sprintf("%s:%d", server, ports[j])
			client, err := smtp.Dial(hostPort)
			if err != nil {
				if j == len(ports)-1 {
					return nil, err
				}

				continue
			}

			return client, nil
		}
	}

	return nil, fmt.Errorf("Couldn't connect to servers %v on any common port.", mx)
}

func send(m Message, c *smtp.Client) error {
	if err := c.Mail(m.From); err != nil {
		return err
	}

	if err := c.Rcpt(m.To); err != nil {
		return err
	}

	msg, err := c.Data()
	if err != nil {
		return err
	}

	if m.Subject != "" {
		_, err = msg.Write([]byte("Subject: " + m.Subject + "\r\n"))
		if err != nil {
			return err
		}
	}

	if m.From != "" {
		_, err = msg.Write([]byte("From: " + m.From + "\r\n"))
		if err != nil {
			return err
		}
	}

	if m.To != "" {
		_, err = msg.Write([]byte("To: " + m.To + "\r\n"))
		if err != nil {
			return err
		}
	}

	_, err = fmt.Fprint(msg, m.Body)
	if err != nil {
		return err
	}

	err = msg.Close()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Package email is designed to provide an "email interface for humans."
// Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	// MaxLineLength is the maximum line length per RFC 2045
	MaxLineLength = 76
)

// Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     string
	Text        []byte // Plaintext message (optional)
	HTML        []byte // Html message (optional)
	Headers     textproto.MIMEHeader
	Attachments []*Attachment
	ReadReceipt []string
}

// NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
	return &Email{Headers: textproto.MIMEHeader{}}
}

// Attach is used to attach content from an io.Reader to the email.
// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type
// The function will return the created Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {
	var buffer bytes.Buffer
	if _, err = io.Copy(&buffer, r); err != nil {
		return
	}
	at := &Attachment{
		Filename: filename,
		Header:   textproto.MIMEHeader{},
		Content:  buffer.Bytes(),
	}
	// Get the Content-Type to be used in the MIMEHeader
	if c != "" {
		at.Header.Set("Content-Type", c)
	} else {
		// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
		at.Header.Set("Content-Type", "application/octet-stream")
	}
	at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
	at.Header.Set("Content-ID", fmt.Sprintf("<%s>", filename))
	at.Header.Set("Content-Transfer-Encoding", "base64")
	e.Attachments = append(e.Attachments, at)
	return at, nil
}

// AttachFile is used to attach content to the email.
// It attempts to open the file referenced by filename and, if successful, creates an Attachment.
// This Attachment is then appended to the slice of Email.Attachments.
// The function will then return the Attachment for reference, as well as nil for the error, if successful.
func (e *Email) AttachFile(filename string) (a *Attachment, err error) {
	f, err := os.Open(filename)
	if err != nil {
		return
	}
	ct := mime.TypeByExtension(filepath.Ext(filename))
	basename := filepath.Base(filename)
	return e.Attach(f, basename, ct)
}

// msgHeaders merges the Email's various fields and custom headers together in a
// standards compliant way to create a MIMEHeader to be used in the resulting
// message. It does not alter e.Headers.
//
// "e"'s fields To, Cc, From, Subject will be used unless they are present in
// e.Headers. Unless set in e.Headers, "Date" will filled with the current time.
func (e *Email) msgHeaders() textproto.MIMEHeader {
	res := make(textproto.MIMEHeader, len(e.Headers)+4)
	if e.Headers != nil {
		for _, h := range []string{"To", "Cc", "From", "Subject", "Date"} {
			if v, ok := e.Headers[h]; ok {
				res[h] = v
			}
		}
	}
	// Set headers if there are values.
	if _, ok := res["To"]; !ok && len(e.To) > 0 {
		res.Set("To", strings.Join(e.To, ", "))
	}
	if _, ok := res["Cc"]; !ok && len(e.Cc) > 0 {
		res.Set("Cc", strings.Join(e.Cc, ", "))
	}
	if _, ok := res["Subject"]; !ok && e.Subject != "" {
		res.Set("Subject", e.Subject)
	}
	// Date and From are required headers.
	if _, ok := res["From"]; !ok {
		res.Set("From", e.From)
	}
	if _, ok := res["Date"]; !ok {
		res.Set("Date", time.Now().Format(time.RFC1123Z))
	}
	if _, ok := res["Mime-Version"]; !ok {
		res.Set("Mime-Version", "1.0")
	}
	for field, vals := range e.Headers {
		if _, ok := res[field]; !ok {
			res[field] = vals
		}
	}
	return res
}

// Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.
func (e *Email) Bytes() ([]byte, error) {
	// TODO: better guess buffer size
	buff := bytes.NewBuffer(make([]byte, 0, 4096))

	headers := e.msgHeaders()
	w := multipart.NewWriter(buff)
	// TODO: determine the content type based on message/attachment mix.
	headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())
	headerToBytes(buff, headers)
	io.WriteString(buff, "\r\n")

	// Start the multipart/mixed part
	fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
	header := textproto.MIMEHeader{}
	// Check to see if there is a Text or HTML field
	if len(e.Text) > 0 || len(e.HTML) > 0 {
		subWriter := multipart.NewWriter(buff)
		// Create the multipart alternative part
		header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
		// Write the header
		headerToBytes(buff, header)
		// Create the body sections
		if len(e.Text) > 0 {
			header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			if _, err := subWriter.CreatePart(header); err != nil {
				return nil, err
			}
			// Write the text
			if err := quotePrintEncode(buff, e.Text); err != nil {
				return nil, err
			}
		}
		if len(e.HTML) > 0 {
			header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			if _, err := subWriter.CreatePart(header); err != nil {
				return nil, err
			}
			// Write the text
			if err := quotePrintEncode(buff, e.HTML); err != nil {
				return nil, err
			}
		}
		if err := subWriter.Close(); err != nil {
			return nil, err
		}
	}
	// Create attachment part, if necessary
	for _, a := range e.Attachments {
		ap, err := w.CreatePart(a.Header)
		if err != nil {
			return nil, err
		}
		// Write the base64Wrapped content to the part
		base64Wrap(ap, a.Content)
	}
	if err := w.Close(); err != nil {
		return nil, err
	}
	return buff.Bytes(), nil
}

// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) SendXD(addr string, a smtp.Auth) error {
	// Merge the To, Cc, and Bcc fields
	to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
	to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
	for i := 0; i < len(to); i++ {
		addr, err := mail.ParseAddress(to[i])
		if err != nil {
			from, err = mail.ParseAddress(stripNameForEmailValidation(e.From))
			if err != nil {
				return err
			}
		}
		to[i] = addr.Address
	}
	// Check to make sure there is at least one recipient and one "From" address
	if e.From == "" || len(to) == 0 {
		return errors.New("Must specify at least one From address and one To address")
	}
	from, err := mail.ParseAddress(e.From)
	if err != nil {
		from, err = mail.ParseAddress(stripNameForEmailValidation(e.From))
		if err != nil {
			return err
		}
	}
	raw, err := e.Bytes()
	if err != nil {
		return err
	}
	return smtp.SendMail(addr, a, from.Address, to, raw)
}

// Attachment is a struct representing an email attachment.
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
type Attachment struct {
	Filename string
	Header   textproto.MIMEHeader
	Content  []byte
}

func stripNameForEmailValidation(address string) string {
	results := regexp.MustCompile(`.*?<(.*?)>`).FindAllStringSubmatch(address, -1)
	if len(results) == 0 || len(results[0]) == 0 {
		return address
	}
	return results[0][1]
}

// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
func quotePrintEncode(w io.Writer, body []byte) error {
	var buf [3]byte
	mc := 0
	for _, c := range body {
		// We're assuming Unix style text formats as input (LF line break), and
		// quoted-printable uses CRLF line breaks. (Literal CRs will become
		// "=0D", but probably shouldn't be there to begin with!)
		if c == '\n' {
			io.WriteString(w, "\r\n")
			mc = 0
			continue
		}

		var nextOut []byte
		if isPrintable[c] {
			buf[0] = c
			nextOut = buf[:1]
		} else {
			nextOut = buf[:]
			qpEscape(nextOut, c)
		}

		// Add a soft line break if the next (encoded) byte would push this line
		// to or past the limit.
		if mc+len(nextOut) >= MaxLineLength {
			if _, err := io.WriteString(w, "=\r\n"); err != nil {
				return err
			}
			mc = 0
		}

		if _, err := w.Write(nextOut); err != nil {
			return err
		}
		mc += len(nextOut)
	}
	// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
	if mc > 0 {
		io.WriteString(w, "=\r\n")
	}
	return nil
}

// isPrintable holds true if the byte given is "printable" according to RFC 2045, false otherwise
var isPrintable [256]bool

func init() {
	for c := '!'; c <= '<'; c++ {
		isPrintable[c] = true
	}
	for c := '>'; c <= '~'; c++ {
		isPrintable[c] = true
	}
	isPrintable[' '] = true
	isPrintable['\n'] = true
	isPrintable['\t'] = true
}

// qpEscape is a helper function for quotePrintEncode which escapes a
// non-printable byte. Expects len(dest) == 3.
func qpEscape(dest []byte, c byte) {
	const nums = "0123456789ABCDEF"
	dest[0] = '='
	dest[1] = nums[(c&0xf0)>>4]
	dest[2] = nums[(c & 0xf)]
}

// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
// The output is then written to the specified io.Writer
func base64Wrap(w io.Writer, b []byte) {
	// 57 raw bytes per 76-byte base64 line.
	const maxRaw = 57
	// Buffer for each line, including trailing CRLF.
	buffer := make([]byte, MaxLineLength+len("\r\n"))
	copy(buffer[MaxLineLength:], "\r\n")
	// Process raw chunks until there's no longer enough to fill a line.
	for len(b) >= maxRaw {
		base64.StdEncoding.Encode(buffer, b[:maxRaw])
		w.Write(buffer)
		b = b[maxRaw:]
	}
	// Handle the last chunk of bytes.
	if len(b) > 0 {
		out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
		base64.StdEncoding.Encode(out, b)
		out = append(out, "\r\n"...)
		w.Write(out)
	}
}

// headerToBytes renders "header" to "buff". If there are multiple values for a
// field, multiple "Field: value\r\n" lines will be emitted.
func headerToBytes(buff *bytes.Buffer, header textproto.MIMEHeader) {
	for field, vals := range header {
		for _, subval := range vals {
			// bytes.Buffer.Write() never returns an error.
			io.WriteString(buff, field)
			io.WriteString(buff, ": ")
			io.WriteString(buff, subval)
			io.WriteString(buff, "\r\n")
		}
	}
}
<commit_msg>Fix problem with special characters<commit_after>// Package email is designed to provide an "email interface for humans."
// Designed to be robust and flexible, the email package aims to make sending email easy without getting in the way.
package email

import (
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"net/mail"
	"net/smtp"
	"net/textproto"
	"os"
	"path/filepath"
	"strings"
	"time"
	"regexp"
)

const (
	// MaxLineLength is the maximum line length per RFC 2045
	MaxLineLength = 76
)

// Email is the type used for email messages
type Email struct {
	From        string
	To          []string
	Bcc         []string
	Cc          []string
	Subject     string
	Text        []byte // Plaintext message (optional)
	HTML        []byte // Html message (optional)
	Headers     textproto.MIMEHeader
	Attachments []*Attachment
	ReadReceipt []string
}

// NewEmail creates an Email, and returns the pointer to it.
func NewEmail() *Email {
	return &Email{Headers: textproto.MIMEHeader{}}
}

// Attach is used to attach content from an io.Reader to the email.
// Required parameters include an io.Reader, the desired filename for the attachment, and the Content-Type
// The function will return the created Attachment for reference, as well as nil for the error, if successful.
func (e *Email) Attach(r io.Reader, filename string, c string) (a *Attachment, err error) {
	var buffer bytes.Buffer
	if _, err = io.Copy(&buffer, r); err != nil {
		return
	}
	at := &Attachment{
		Filename: filename,
		Header:   textproto.MIMEHeader{},
		Content:  buffer.Bytes(),
	}
	// Get the Content-Type to be used in the MIMEHeader
	if c != "" {
		at.Header.Set("Content-Type", c)
	} else {
		// If the Content-Type is blank, set the Content-Type to "application/octet-stream"
		at.Header.Set("Content-Type", "application/octet-stream")
	}
	at.Header.Set("Content-Disposition", fmt.Sprintf("attachment;\r\n filename=\"%s\"", filename))
	at.Header.Set("Content-ID", fmt.Sprintf("<%s>", filename))
	at.Header.Set("Content-Transfer-Encoding", "base64")
	e.Attachments = append(e.Attachments, at)
	return at, nil
}

// AttachFile is used to attach content to the email.
// It attempts to open the file referenced by filename and, if successful, creates an Attachment.
// This Attachment is then appended to the slice of Email.Attachments.
// The function will then return the Attachment for reference, as well as nil for the error, if successful.
func (e *Email) AttachFile(filename string) (a *Attachment, err error) {
	f, err := os.Open(filename)
	if err != nil {
		return
	}
	ct := mime.TypeByExtension(filepath.Ext(filename))
	basename := filepath.Base(filename)
	return e.Attach(f, basename, ct)
}

// msgHeaders merges the Email's various fields and custom headers together in a
// standards compliant way to create a MIMEHeader to be used in the resulting
// message. It does not alter e.Headers.
//
// "e"'s fields To, Cc, From, Subject will be used unless they are present in
// e.Headers. Unless set in e.Headers, "Date" will filled with the current time.
func (e *Email) msgHeaders() textproto.MIMEHeader {
	res := make(textproto.MIMEHeader, len(e.Headers)+4)
	if e.Headers != nil {
		for _, h := range []string{"To", "Cc", "From", "Subject", "Date"} {
			if v, ok := e.Headers[h]; ok {
				res[h] = v
			}
		}
	}
	// Set headers if there are values.
	if _, ok := res["To"]; !ok && len(e.To) > 0 {
		res.Set("To", strings.Join(e.To, ", "))
	}
	if _, ok := res["Cc"]; !ok && len(e.Cc) > 0 {
		res.Set("Cc", strings.Join(e.Cc, ", "))
	}
	if _, ok := res["Subject"]; !ok && e.Subject != "" {
		res.Set("Subject", e.Subject)
	}
	// Date and From are required headers.
	if _, ok := res["From"]; !ok {
		res.Set("From", e.From)
	}
	if _, ok := res["Date"]; !ok {
		res.Set("Date", time.Now().Format(time.RFC1123Z))
	}
	if _, ok := res["Mime-Version"]; !ok {
		res.Set("Mime-Version", "1.0")
	}
	for field, vals := range e.Headers {
		if _, ok := res[field]; !ok {
			res[field] = vals
		}
	}
	return res
}

// Bytes converts the Email object to a []byte representation, including all needed MIMEHeaders, boundaries, etc.
func (e *Email) Bytes() ([]byte, error) {
	// TODO: better guess buffer size
	buff := bytes.NewBuffer(make([]byte, 0, 4096))

	headers := e.msgHeaders()
	w := multipart.NewWriter(buff)
	// TODO: determine the content type based on message/attachment mix.
	headers.Set("Content-Type", "multipart/mixed;\r\n boundary="+w.Boundary())
	headerToBytes(buff, headers)
	io.WriteString(buff, "\r\n")

	// Start the multipart/mixed part
	fmt.Fprintf(buff, "--%s\r\n", w.Boundary())
	header := textproto.MIMEHeader{}
	// Check to see if there is a Text or HTML field
	if len(e.Text) > 0 || len(e.HTML) > 0 {
		subWriter := multipart.NewWriter(buff)
		// Create the multipart alternative part
		header.Set("Content-Type", fmt.Sprintf("multipart/alternative;\r\n boundary=%s\r\n", subWriter.Boundary()))
		// Write the header
		headerToBytes(buff, header)
		// Create the body sections
		if len(e.Text) > 0 {
			header.Set("Content-Type", fmt.Sprintf("text/plain; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			if _, err := subWriter.CreatePart(header); err != nil {
				return nil, err
			}
			// Write the text
			if err := quotePrintEncode(buff, e.Text); err != nil {
				return nil, err
			}
		}
		if len(e.HTML) > 0 {
			header.Set("Content-Type", fmt.Sprintf("text/html; charset=UTF-8"))
			header.Set("Content-Transfer-Encoding", "quoted-printable")
			if _, err := subWriter.CreatePart(header); err != nil {
				return nil, err
			}
			// Write the text
			if err := quotePrintEncode(buff, e.HTML); err != nil {
				return nil, err
			}
		}
		if err := subWriter.Close(); err != nil {
			return nil, err
		}
	}
	// Create attachment part, if necessary
	for _, a := range e.Attachments {
		ap, err := w.CreatePart(a.Header)
		if err != nil {
			return nil, err
		}
		// Write the base64Wrapped content to the part
		base64Wrap(ap, a.Content)
	}
	if err := w.Close(); err != nil {
		return nil, err
	}
	return buff.Bytes(), nil
}

// Send an email using the given host and SMTP auth (optional), returns any error thrown by smtp.SendMail
// This function merges the To, Cc, and Bcc fields and calls the smtp.SendMail function using the Email.Bytes() output as the message
func (e *Email) Send(addr string, a smtp.Auth) error {
	// Merge the To, Cc, and Bcc fields
	to := make([]string, 0, len(e.To)+len(e.Cc)+len(e.Bcc))
	to = append(append(append(to, e.To...), e.Cc...), e.Bcc...)
	for i := 0; i < len(to); i++ {
		addr, err := mail.ParseAddress(to[i])
		if err != nil {
			addr, err = mail.ParseAddress(stripNameForEmailValidation(to[i]))
			if err != nil {
				return err
			}
		}
		to[i] = addr.Address
	}
	// Check to make sure there is at least one recipient and one "From" address
	if e.From == "" || len(to) == 0 {
		return errors.New("Must specify at least one From address and one To address")
	}
	from, err := mail.ParseAddress(e.From)
	if err != nil {
		from, err = mail.ParseAddress(stripNameForEmailValidation(e.From))
		if err != nil {
			return err
		}
	}
	raw, err := e.Bytes()
	if err != nil {
		return err
	}
	return smtp.SendMail(addr, a, from.Address, to, raw)
}

// Attachment is a struct representing an email attachment.
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
type Attachment struct {
	Filename string
	Header   textproto.MIMEHeader
	Content  []byte
}

func stripNameForEmailValidation(address string) string {
	results := regexp.MustCompile(`.*?<(.*?)>`).FindAllStringSubmatch(address, -1)
	if len(results) == 0 || len(results[0]) == 0 {
		return address
	}
	return results[0][1]
}

// quotePrintEncode writes the quoted-printable text to the IO Writer (according to RFC 2045)
func quotePrintEncode(w io.Writer, body []byte) error {
	var buf [3]byte
	mc := 0
	for _, c := range body {
		// We're assuming Unix style text formats as input (LF line break), and
		// quoted-printable uses CRLF line breaks. (Literal CRs will become
		// "=0D", but probably shouldn't be there to begin with!)
		if c == '\n' {
			io.WriteString(w, "\r\n")
			mc = 0
			continue
		}

		var nextOut []byte
		if isPrintable[c] {
			buf[0] = c
			nextOut = buf[:1]
		} else {
			nextOut = buf[:]
			qpEscape(nextOut, c)
		}

		// Add a soft line break if the next (encoded) byte would push this line
		// to or past the limit.
		if mc+len(nextOut) >= MaxLineLength {
			if _, err := io.WriteString(w, "=\r\n"); err != nil {
				return err
			}
			mc = 0
		}

		if _, err := w.Write(nextOut); err != nil {
			return err
		}
		mc += len(nextOut)
	}
	// No trailing end-of-line?? Soft line break, then. TODO: is this sane?
	if mc > 0 {
		io.WriteString(w, "=\r\n")
	}
	return nil
}

// isPrintable holds true if the byte given is "printable" according to RFC 2045, false otherwise
var isPrintable [256]bool

func init() {
	for c := '!'; c <= '<'; c++ {
		isPrintable[c] = true
	}
	for c := '>'; c <= '~'; c++ {
		isPrintable[c] = true
	}
	isPrintable[' '] = true
	isPrintable['\n'] = true
	isPrintable['\t'] = true
}

// qpEscape is a helper function for quotePrintEncode which escapes a
// non-printable byte. Expects len(dest) == 3.
func qpEscape(dest []byte, c byte) {
	const nums = "0123456789ABCDEF"
	dest[0] = '='
	dest[1] = nums[(c&0xf0)>>4]
	dest[2] = nums[(c & 0xf)]
}

// base64Wrap encodes the attachment content, and wraps it according to RFC 2045 standards (every 76 chars)
// The output is then written to the specified io.Writer
func base64Wrap(w io.Writer, b []byte) {
	// 57 raw bytes per 76-byte base64 line.
	const maxRaw = 57
	// Buffer for each line, including trailing CRLF.
	buffer := make([]byte, MaxLineLength+len("\r\n"))
	copy(buffer[MaxLineLength:], "\r\n")
	// Process raw chunks until there's no longer enough to fill a line.
	for len(b) >= maxRaw {
		base64.StdEncoding.Encode(buffer, b[:maxRaw])
		w.Write(buffer)
		b = b[maxRaw:]
	}
	// Handle the last chunk of bytes.
	if len(b) > 0 {
		out := buffer[:base64.StdEncoding.EncodedLen(len(b))]
		base64.StdEncoding.Encode(out, b)
		out = append(out, "\r\n"...)
		w.Write(out)
	}
}

// headerToBytes renders "header" to "buff". If there are multiple values for a
// field, multiple "Field: value\r\n" lines will be emitted.
func headerToBytes(buff *bytes.Buffer, header textproto.MIMEHeader) {
	for field, vals := range header {
		for _, subval := range vals {
			// bytes.Buffer.Write() never returns an error.
			io.WriteString(buff, field)
			io.WriteString(buff, ": ")
			io.WriteString(buff, subval)
			io.WriteString(buff, "\r\n")
		}
	}
}
<|endoftext|>