<commit_before>package board

import (
	"math/rand"
	"time"
)

// Board size
const (
	X = 4
	Y = 4
)

// Direction
type Direction int32

// Direction
const (
	LEFT  = iota
	UP    = iota
	RIGHT = iota
	DOWN  = iota
)

var (
	DIRECTIONS = map[int]int{
		LEFT:  -1,
		UP:    -1,
		RIGHT: 1,
		DOWN:  1,
	}
)

type Board struct {
	Cells [Y][X]int

	goal   int
	points int
}

func New() Board {
	board := Board{
		/*
		   Cells: [Y][X]int {
		       {0, 3, 0, 0},
		       {1, 0, 2, 0},
		       {2, 1, 1, 0},
		       {0, 6, 5, 0},
		   },
		*/
		Cells: [Y][X]int{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 0, 0},
		},
		goal:   2048,
		points: 0,
	}

	// Seed rng
	rand.Seed(time.Now().Unix())

	// Add two random tiles
	board.AddTile()
	board.AddTile()

	return board
}

func (b *Board) emptyRow(n int) []int {
	row := []int{0, 0, 0, 0}
	return row[0:n]
}

func (b *Board) moveLine(row [4]int, direction int) [4]int {
	var empty []int
	var nonEmpty []int
	var result [4]int

	for i := 0; i < len(row); i++ {
		if row[i] == 0 {
			continue
		}

		nonEmpty = append(nonEmpty, row[i])
	}

	empty = b.emptyRow(X - len(nonEmpty))

	// Copy merges to result array
	if direction == -1 {
		copy(result[:], append(nonEmpty, empty...)[0:4])
	} else {
		copy(result[:], append(empty, nonEmpty...)[0:4])
	}

	return result
}

// Is a given line mergeable
func canMergeLine(row [4]int) bool {
	for i := 0; i < len(row); i++ {
		// Previous
		if i > 0 && row[i] == row[i-1] {
			return true
		}

		// Next
		if i+1 < len(row) && row[i] == row[i+1] {
			return true
		}
	}

	return false
}

func (b *Board) mergeLine(row [4]int, direction int) [4]int {
	var newRow [4]int
	var start, end, pos, nextpos int

	if direction == -1 {
		end = 0
		start = len(row) - 1
	} else {
		start = 0
		end = len(row) - 1
	}

	pos = start
	for i := 0; i < len(row); i++ {
		nextpos = pos + direction

		// Don't merge empty cells
		// or already merged cells
		if row[pos] == 0 || newRow[pos] != 0 {
			pos = nextpos
			continue
		}

		// Next cell is identical
		if pos != end && row[pos] == row[nextpos] {
			var value = row[pos] + 1
			newRow[pos] = 0
			newRow[nextpos] = value
		} else {
			newRow[pos] = row[pos]
		}

		// Update position
		pos = nextpos
	}

	return newRow
}

func (b *Board) setRow(y int, row [4]int) {
	for x := 0; x < X; x++ {
		b.Cells[y][x] = row[x]
	}
}

func (b *Board) getRow(y int) [4]int {
	return b.Cells[y]
}

func (b *Board) setCol(x int, row [4]int) {
	for y := 0; y < Y; y++ {
		b.Cells[y][x] = row[y]
	}
}

func (b *Board) getCol(x int) [4]int {
	var a [4]int

	for y := 0; y < Y; y++ {
		a[y] = b.Cells[y][x]
	}

	return a
}

func (b *Board) moveRows(d int) {
	for y := 0; y < Y; y++ {
		// Get new row by moving and merging previous row
		var newRow = b.moveLine(
			b.mergeLine(
				b.moveLine(
					b.getRow(y),
					d,
				),
				d,
			),
			d,
		)

		// Set new row
		b.setRow(y, newRow)
	}
}

func (b *Board) moveCols(d int) {
	for x := 0; x < X; x++ {
		// Get new col by moving and merging previous col
		var newCol = b.moveLine(
			b.mergeLine(
				b.moveLine(
					b.getCol(x),
					d,
				),
				d,
			),
			d,
		)

		// Set new col
		b.setCol(x, newCol)
	}
}

func (b *Board) emptyCells() [][2]int {
	var arr [][2]int

	for y := 0; y < Y; y++ {
		for x := 0; x < X; x++ {
			if b.Cells[y][x] == 0 {
				var cell = [2]int{x, y}
				arr = append(arr, cell)
			}
		}
	}

	return arr
}

func (b *Board) IsFull() bool {
	return len(b.emptyCells()) == 0
}

func (b *Board) AddTile() {
	cells := b.emptyCells()
	cell := cells[rand.Int()%len(cells)]
	x := cell[0]
	y := cell[1]

	// Set cell randomly to 1 or 2
	b.Cells[y][x] = (rand.Int() % 2) + 1
}

func (b *Board) Playable() bool {
	if !b.IsFull() {
		return true
	}

	for y := 0; y < Y; y++ {
		if canMergeLine(b.getRow(y)) {
			return true
		}
	}

	for x := 0; x < X; x++ {
		if canMergeLine(b.getCol(x)) {
			return true
		}
	}

	return false
}

func (b *Board) Values() []int {
	var arr []int

	for y := 0; y < Y; y++ {
		for x := 0; x < X; x++ {
			if b.Cells[y][x] != 0 {
				arr = append(arr, b.Cells[y][x])
			}
		}
	}
	return arr
}

// Move board in a given direction
func (b *Board) Move(d Direction) {
	switch d {
	case UP:
		b.moveCols(DIRECTIONS[UP])
	case DOWN:
		b.moveCols(DIRECTIONS[DOWN])

	case LEFT:
		b.moveRows(DIRECTIONS[LEFT])
	case RIGHT:
		b.moveRows(DIRECTIONS[RIGHT])
	}

	// Add new tile if not empty
	if !b.IsFull() {
		b.AddTile()
	}
}
<commit_msg>Board: use struct to represent cellLocation instead of [2]int<commit_after>package board

import (
	"math/rand"
	"time"
)

// Board size
const (
	X = 4
	Y = 4
)

// Direction
type Direction int32

// Direction
const (
	LEFT  = iota
	UP    = iota
	RIGHT = iota
	DOWN  = iota
)

var (
	DIRECTIONS = map[int]int{
		LEFT:  -1,
		UP:    -1,
		RIGHT: 1,
		DOWN:  1,
	}
)

type Board struct {
	Cells [Y][X]int

	goal   int
	points int
}

func New() Board {
	board := Board{
		/*
		   Cells: [Y][X]int {
		       {0, 3, 0, 0},
		       {1, 0, 2, 0},
		       {2, 1, 1, 0},
		       {0, 6, 5, 0},
		   },
		*/
		Cells: [Y][X]int{
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 0, 0},
			{0, 0, 0, 0},
		},
		goal:   2048,
		points: 0,
	}

	// Seed rng
	rand.Seed(time.Now().Unix())

	// Add two random tiles
	board.AddTile()
	board.AddTile()

	return board
}

func (b *Board) emptyRow(n int) []int {
	row := []int{0, 0, 0, 0}
	return row[0:n]
}

func (b *Board) moveLine(row [4]int, direction int) [4]int {
	var empty []int
	var nonEmpty []int
	var result [4]int

	for i := 0; i < len(row); i++ {
		if row[i] == 0 {
			continue
		}

		nonEmpty = append(nonEmpty, row[i])
	}

	empty = b.emptyRow(X - len(nonEmpty))

	// Copy merges to result array
	if direction == -1 {
		copy(result[:], append(nonEmpty, empty...)[0:4])
	} else {
		copy(result[:], append(empty, nonEmpty...)[0:4])
	}

	return result
}

// Is a given line mergeable
func canMergeLine(row [4]int) bool {
	for i := 0; i < len(row); i++ {
		// Previous
		if i > 0 && row[i] == row[i-1] {
			return true
		}

		// Next
		if i+1 < len(row) && row[i] == row[i+1] {
			return true
		}
	}

	return false
}

func (b *Board) mergeLine(row [4]int, direction int) [4]int {
	var newRow [4]int
	var start, end, pos, nextpos int

	if direction == -1 {
		end = 0
		start = len(row) - 1
	} else {
		start = 0
		end = len(row) - 1
	}

	pos = start
	for i := 0; i < len(row); i++ {
		nextpos = pos + direction

		// Don't merge empty cells
		// or already merged cells
		if row[pos] == 0 || newRow[pos] != 0 {
			pos = nextpos
			continue
		}

		// Next cell is identical
		if pos != end && row[pos] == row[nextpos] {
			var value = row[pos] + 1
			newRow[pos] = 0
			newRow[nextpos] = value
		} else {
			newRow[pos] = row[pos]
		}

		// Update position
		pos = nextpos
	}

	return newRow
}

func (b *Board) setRow(y int, row [4]int) {
	for x := 0; x < X; x++ {
		b.Cells[y][x] = row[x]
	}
}

func (b *Board) getRow(y int) [4]int {
	return b.Cells[y]
}

func (b *Board) setCol(x int, row [4]int) {
	for y := 0; y < Y; y++ {
		b.Cells[y][x] = row[y]
	}
}

func (b *Board) getCol(x int) [4]int {
	var a [4]int

	for y := 0; y < Y; y++ {
		a[y] = b.Cells[y][x]
	}

	return a
}

func (b *Board) moveRows(d int) {
	for y := 0; y < Y; y++ {
		// Get new row by moving and merging previous row
		var newRow = b.moveLine(
			b.mergeLine(
				b.moveLine(
					b.getRow(y),
					d,
				),
				d,
			),
			d,
		)

		// Set new row
		b.setRow(y, newRow)
	}
}

func (b *Board) moveCols(d int) {
	for x := 0; x < X; x++ {
		// Get new col by moving and merging previous col
		var newCol = b.moveLine(
			b.mergeLine(
				b.moveLine(
					b.getCol(x),
					d,
				),
				d,
			),
			d,
		)

		// Set new col
		b.setCol(x, newCol)
	}
}

type cellLocation struct {
	x, y int
}

func (b *Board) emptyCells() []cellLocation {
	var arr []cellLocation

	for y := 0; y < Y; y++ {
		for x := 0; x < X; x++ {
			if b.Cells[y][x] == 0 {
				var cell = cellLocation{x, y}
				arr = append(arr, cell)
			}
		}
	}

	return arr
}

func (b *Board) IsFull() bool {
	return len(b.emptyCells()) == 0
}

func (b *Board) AddTile() {
	cells := b.emptyCells()
	cell := cells[rand.Int()%len(cells)]

	// Set cell randomly to 1 or 2
	b.Cells[cell.y][cell.x] = (rand.Int() % 2) + 1
}

func (b *Board) Playable() bool {
	if !b.IsFull() {
		return true
	}

	for y := 0; y < Y; y++ {
		if canMergeLine(b.getRow(y)) {
			return true
		}
	}

	for x := 0; x < X; x++ {
		if canMergeLine(b.getCol(x)) {
			return true
		}
	}

	return false
}

func (b *Board) Values() []int {
	var arr []int

	for y := 0; y < Y; y++ {
		for x := 0; x < X; x++ {
			if b.Cells[y][x] != 0 {
				arr = append(arr, b.Cells[y][x])
			}
		}
	}
	return arr
}

// Move board in a given direction
func (b *Board) Move(d Direction) {
	switch d {
	case UP:
		b.moveCols(DIRECTIONS[UP])
	case DOWN:
		b.moveCols(DIRECTIONS[DOWN])

	case LEFT:
		b.moveRows(DIRECTIONS[LEFT])
	case RIGHT:
		b.moveRows(DIRECTIONS[RIGHT])
	}

	// Add new tile if not empty
	if !b.IsFull() {
		b.AddTile()
	}
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package xurls

import (
	"regexp"
	"testing"
)

type regexTestCase struct {
	in   string
	want interface{}
}

func doTest(t *testing.T, name string, re *regexp.Regexp, cases []regexTestCase) {
	for _, c := range cases {
		got := re.FindString(c.in)
		want, _ := c.want.(string)
		if got != want {
			t.Errorf(`%s.FindString("%s") got "%s", want "%s"`, name, c.in, got, want)
		}
	}
}

var constantTestCases = []regexTestCase{
	{``, nil},
	{` `, nil},
	{`:`, nil},
	{`::`, nil},
	{`:::`, nil},
	{`::::`, nil},
	{`.`, nil},
	{`..`, nil},
	{`...`, nil},
	{`1.1`, nil},
	{`.1.`, nil},
	{`1.1.1`, nil},
	{`1:1`, nil},
	{`:1:`, nil},
	{`1:1:1`, nil},
	{`://`, nil},
	{`foo`, nil},
	{`foo:`, nil},
	{`mailto:`, nil},
	{`randomxmpp:foo`, nil},
	{`foo://`, nil},
	{`http://`, nil},
	{`:foo`, nil},
	{`://foo`, nil},
	{`foo:bar`, nil},
	{`zzz.`, nil},
	{`.zzz`, nil},
	{`zzz.zzz`, nil},
	{`/some/path`, nil},
	{`localhost`, nil},
	{`com`, nil},
	{`.com`, nil},
	{`http`, nil},

	{`http://foo`, `http://foo`},
	{`http://FOO`, `http://FOO`},
	{`http://FAÀ`, `http://FAÀ`},
	{`https://localhost`, `https://localhost`},
	{`git+https://localhost`, `git+https://localhost`},
	{`foo.bar://localhost`, `foo.bar://localhost`},
	{`foo-bar://localhost`, `foo-bar://localhost`},
	{`mailto:foo`, `mailto:foo`},
	{`MAILTO:foo`, `MAILTO:foo`},
	{`sms:123`, `sms:123`},
	{`xmpp:foo@bar`, `xmpp:foo@bar`},
	{`bitcoin:Addr23?amount=1&message=foo`, `bitcoin:Addr23?amount=1&message=foo`},
	{`http://foo.com`, `http://foo.com`},
	{`http://foo.random`, `http://foo.random`},
	{` http://foo.com/bar `, `http://foo.com/bar`},
	{` http://foo.com/bar more`, `http://foo.com/bar`},
	{`<http://foo.com/bar>`, `http://foo.com/bar`},
	{`<http://foo.com/bar>more`, `http://foo.com/bar`},
	{`.http://foo.com/bar.`, `http://foo.com/bar`},
	{`.http://foo.com/bar.more`, `http://foo.com/bar.more`},
	{`,http://foo.com/bar,`, `http://foo.com/bar`},
	{`,http://foo.com/bar,more`, `http://foo.com/bar,more`},
	{`(http://foo.com/bar)`, `http://foo.com/bar`},
	{`"http://foo.com/bar'`, `http://foo.com/bar`},
	{`"http://foo.com/bar'more`, `http://foo.com/bar'more`},
	{`"http://foo.com/bar"`, `http://foo.com/bar`},
	{`http://a.b/a.,:;-+_()?@&=#$~!*%'a`, `http://a.b/a.,:;-+_()?@&=#$~!*%'a`},
	{`http://foo.com/path_(more)`, `http://foo.com/path_(more)`},
	{`http://foo.com/path_(even)-(more)`, `http://foo.com/path_(even)-(more)`},
	{`http://foo.com/path_(even)(more)`, `http://foo.com/path_(even)(more)`},
	{`http://foo.com/path_(even_(nested))`, `http://foo.com/path_(even_(nested))`},
	{`http://foo.com/path#fragment`, `http://foo.com/path#fragment`},
	{`http://test.foo.com/`, `http://test.foo.com/`},
	{`http://foo.com/path`, `http://foo.com/path`},
	{`http://foo.com:8080/path`, `http://foo.com:8080/path`},
	{`http://1.1.1.1/path`, `http://1.1.1.1/path`},
	{`http://1080::8:800:200c:417a/path`, `http://1080::8:800:200c:417a/path`},
	{`http://中国.中国/foo中国`, `http://中国.中国/foo中国`},
	{`http://✪foo.bar/pa✪th`, `http://✪foo.bar/pa✪th`},
	{`✪http://✪foo.bar/pa✪th✪`, `http://✪foo.bar/pa✪th`},
	{`what is http://foo.com?`, `http://foo.com`},
	{`the http://foo.com!`, `http://foo.com`},
	{`https://test.foo.bar/path?a=b`, `https://test.foo.bar/path?a=b`},
	{`ftp://user@foo.bar`, `ftp://user@foo.bar`},
}

func TestRegexes(t *testing.T) {
	doTest(t, "Relaxed", Relaxed, constantTestCases)
	doTest(t, "Strict", Strict, constantTestCases)
	doTest(t, "Relaxed", Relaxed, []regexTestCase{
		{`foo.a`, nil},
		{`foo.com`, `foo.com`},
		{`foo.com bar.com`, `foo.com`},
		{`foo.com-foo`, `foo.com`},
		{`foo.company`, `foo.company`},
		{`foo.comrandom`, nil},
		{`foo.onion`, `foo.onion`},
		{`foo.i2p`, `foo.i2p`},
		{`中国.中国`, `中国.中国`},
		{`中国.中国/foo中国`, `中国.中国/foo中国`},
		{`foo.com/`, `foo.com/`},
		{`1.1.1.1`, `1.1.1.1`},
		{`10.50.23.250`, `10.50.23.250`},
		{`121.1.1.1`, `121.1.1.1`},
		{`255.1.1.1`, `255.1.1.1`},
		{`300.1.1.1`, nil},
		{`1.1.1.300`, nil},
		{`1080:0:0:0:8:800:200C:4171`, `1080:0:0:0:8:800:200C:4171`},
		{`3ffe:2a00:100:7031::1`, `3ffe:2a00:100:7031::1`},
		{`1080::8:800:200c:417a`, `1080::8:800:200c:417a`},
		{`foo.com:8080`, `foo.com:8080`},
		{`foo.com:8080/path`, `foo.com:8080/path`},
		{`test.foo.com`, `test.foo.com`},
		{`test.foo.com/path`, `test.foo.com/path`},
		{`test.foo.com/path/more/`, `test.foo.com/path/more/`},
		{`TEST.FOO.COM/PATH`, `TEST.FOO.COM/PATH`},
		{`TEST.FÓO.COM/PÁTH`, `TEST.FÓO.COM/PÁTH`},
		{`foo.com/path_(more)`, `foo.com/path_(more)`},
		{`foo.com/path_(even)_(more)`, `foo.com/path_(even)_(more)`},
		{`foo.com/path_(more)/more`, `foo.com/path_(more)/more`},
		{`foo.com/path_(more)/end)`, `foo.com/path_(more)/end`},
		{`www.foo.com`, `www.foo.com`},
		{` foo.com/bar `, `foo.com/bar`},
		{` foo.com/bar more`, `foo.com/bar`},
		{`<foo.com/bar>`, `foo.com/bar`},
		{`<foo.com/bar>more`, `foo.com/bar`},
		{`,foo.com/bar.`, `foo.com/bar`},
		{`,foo.com/bar.more`, `foo.com/bar.more`},
		{`,foo.com/bar,`, `foo.com/bar`},
		{`,foo.com/bar,more`, `foo.com/bar,more`},
		{`(foo.com/bar)`, `foo.com/bar`},
		{`"foo.com/bar'`, `foo.com/bar`},
		{`"foo.com/bar'more`, `foo.com/bar'more`},
		{`"foo.com/bar"`, `foo.com/bar`},
		{`what is foo.com?`, `foo.com`},
		{`the foo.com!`, `foo.com`},

		{`foo@bar`, nil},
		{`foo@bar.a`, nil},
		{`foo@bar.com`, `foo@bar.com`},
		{`foo@bar.com bar@bar.com`, `foo@bar.com`},
		{`foo@bar.onion`, `foo@bar.onion`},
		{`foo@中国.中国`, `foo@中国.中国`},
		{`foo@test.bar.com`, `foo@test.bar.com`},
		{`FOO@TEST.BAR.COM`, `FOO@TEST.BAR.COM`},
		{`foo@bar.com/path`, `foo@bar.com`},
		{`foo+test@bar.com`, `foo+test@bar.com`},
		{`foo+._%-@bar.com`, `foo+._%-@bar.com`},
	})
	doTest(t, "Strict", Strict, []regexTestCase{
		{`http:// foo.com`, nil},
		{`http:// foo.com`, nil},
		{`foo.a`, nil},
		{`foo.com`, nil},
		{`foo.com/`, nil},
		{`1.1.1.1`, nil},
		{`3ffe:2a00:100:7031::1`, nil},
		{`test.foo.com:8080/path`, nil},
		{`foo@bar.com`, nil},
	})
}

func TestStrictMatchingError(t *testing.T) {
	for _, c := range []struct {
		exp     string
		wantErr bool
	}{
		{`http://`, false},
		{`https?://`, false},
		{`http://|mailto:`, false},
		{`http://(`, true},
	} {
		_, err := StrictMatching(c.exp)
		if c.wantErr && err == nil {
			t.Errorf(`StrictMatching("%s") did not error as expected`, c.exp)
		} else if !c.wantErr && err != nil {
			t.Errorf(`StrictMatching("%s") unexpectedly errored`, c.exp)
		}
	}
}

func TestStrictMatching(t *testing.T) {
	strictMatching, _ := StrictMatching("http://|ftps?://|mailto:")
	doTest(t, "StrictMatching", strictMatching, []regexTestCase{
		{`foo.com`, nil},
		{`foo@bar.com`, nil},
		{`http://foo`, `http://foo`},
		{`https://foo`, nil},
		{`ftp://foo`, `ftp://foo`},
		{`ftps://foo`, `ftps://foo`},
		{`mailto:foo`, `mailto:foo`},
		{`sms:123`, nil},
	})
}

func bench(b *testing.B, re *regexp.Regexp, str string) {
	for i := 0; i < b.N; i++ {
		re.FindAllString(str, -1)
	}
}

func BenchmarkStrictEmpty(b *testing.B) {
	bench(b, Strict, "foo")
}

func BenchmarkStrictSingle(b *testing.B) {
	bench(b, Strict, "http://foo.foo foo.com")
}

func BenchmarkStrictMany(b *testing.B) {
	bench(b, Strict, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}

func BenchmarkRelaxedEmpty(b *testing.B) {
	bench(b, Relaxed, "foo")
}

func BenchmarkRelaxedSingle(b *testing.B) {
	bench(b, Relaxed, "http://foo.foo foo.com")
}

func BenchmarkRelaxedMany(b *testing.B) {
	bench(b, Relaxed, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}
<commit_msg>Add some more test cases<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package xurls

import (
	"regexp"
	"testing"
)

type regexTestCase struct {
	in   string
	want interface{}
}

func doTest(t *testing.T, name string, re *regexp.Regexp, cases []regexTestCase) {
	for _, c := range cases {
		got := re.FindString(c.in)
		want, _ := c.want.(string)
		if got != want {
			t.Errorf(`%s.FindString("%s") got "%s", want "%s"`, name, c.in, got, want)
		}
	}
}

var constantTestCases = []regexTestCase{
	{``, nil},
	{` `, nil},
	{`:`, nil},
	{`::`, nil},
	{`:::`, nil},
	{`::::`, nil},
	{`.`, nil},
	{`..`, nil},
	{`...`, nil},
	{`1.1`, nil},
	{`.1.`, nil},
	{`1.1.1`, nil},
	{`1:1`, nil},
	{`:1:`, nil},
	{`1:1:1`, nil},
	{`://`, nil},
	{`foo`, nil},
	{`foo:`, nil},
	{`mailto:`, nil},
	{`randomxmpp:foo`, nil},
	{`foo://`, nil},
	{`http://`, nil},
	{`:foo`, nil},
	{`://foo`, nil},
	{`foo:bar`, nil},
	{`zzz.`, nil},
	{`.zzz`, nil},
	{`zzz.zzz`, nil},
	{`/some/path`, nil},
	{`localhost`, nil},
	{`com`, nil},
	{`.com`, nil},
	{`http`, nil},

	{`http://foo`, `http://foo`},
	{`http://FOO`, `http://FOO`},
	{`http://FAÀ`, `http://FAÀ`},
	{`https://localhost`, `https://localhost`},
	{`git+https://localhost`, `git+https://localhost`},
	{`foo.bar://localhost`, `foo.bar://localhost`},
	{`foo-bar://localhost`, `foo-bar://localhost`},
	{`mailto:foo`, `mailto:foo`},
	{`MAILTO:foo`, `MAILTO:foo`},
	{`sms:123`, `sms:123`},
	{`xmpp:foo@bar`, `xmpp:foo@bar`},
	{`bitcoin:Addr23?amount=1&message=foo`, `bitcoin:Addr23?amount=1&message=foo`},
	{`http://foo.com`, `http://foo.com`},
	{`http://foo.co.uk`, `http://foo.co.uk`},
	{`http://foo.random`, `http://foo.random`},
	{` http://foo.com/bar `, `http://foo.com/bar`},
	{` http://foo.com/bar more`, `http://foo.com/bar`},
	{`<http://foo.com/bar>`, `http://foo.com/bar`},
	{`<http://foo.com/bar>more`, `http://foo.com/bar`},
	{`.http://foo.com/bar.`, `http://foo.com/bar`},
	{`.http://foo.com/bar.more`, `http://foo.com/bar.more`},
	{`,http://foo.com/bar,`, `http://foo.com/bar`},
	{`,http://foo.com/bar,more`, `http://foo.com/bar,more`},
	{`(http://foo.com/bar)`, `http://foo.com/bar`},
	{`"http://foo.com/bar'`, `http://foo.com/bar`},
	{`"http://foo.com/bar'more`, `http://foo.com/bar'more`},
	{`"http://foo.com/bar"`, `http://foo.com/bar`},
	{`http://a.b/a.,:;-+_()?@&=#$~!*%'a`, `http://a.b/a.,:;-+_()?@&=#$~!*%'a`},
	{`http://foo.com/path_(more)`, `http://foo.com/path_(more)`},
	{`(http://foo.com/path_(more))`, `http://foo.com/path_(more)`},
	{`http://foo.com/path_(even)-(more)`, `http://foo.com/path_(even)-(more)`},
	{`http://foo.com/path_(even)(more)`, `http://foo.com/path_(even)(more)`},
	{`http://foo.com/path_(even_(nested))`, `http://foo.com/path_(even_(nested))`},
	{`(http://foo.com/path_(even_(nested)))`, `http://foo.com/path_(even_(nested))`},
	{`http://foo.com/path#fragment`, `http://foo.com/path#fragment`},
	{`http://test.foo.com/`, `http://test.foo.com/`},
	{`http://foo.com/path`, `http://foo.com/path`},
	{`http://foo.com:8080/path`, `http://foo.com:8080/path`},
	{`http://1.1.1.1/path`, `http://1.1.1.1/path`},
	{`http://1080::8:800:200c:417a/path`, `http://1080::8:800:200c:417a/path`},
	{`http://中国.中国/foo中国`, `http://中国.中国/foo中国`},
	{`http://✪foo.bar/pa✪th`, `http://✪foo.bar/pa✪th`},
	{`✪http://✪foo.bar/pa✪th✪`, `http://✪foo.bar/pa✪th`},
	{`what is http://foo.com?`, `http://foo.com`},
	{`the http://foo.com!`, `http://foo.com`},
	{`https://test.foo.bar/path?a=b`, `https://test.foo.bar/path?a=b`},
	{`ftp://user@foo.bar`, `ftp://user@foo.bar`},
}

func TestRegexes(t *testing.T) {
	doTest(t, "Relaxed", Relaxed, constantTestCases)
	doTest(t, "Strict", Strict, constantTestCases)
	doTest(t, "Relaxed", Relaxed, []regexTestCase{
		{`foo.a`, nil},
		{`foo.com`, `foo.com`},
		{`foo.com bar.com`, `foo.com`},
		{`foo.com-foo`, `foo.com`},
		{`foo.company`, `foo.company`},
		{`foo.comrandom`, nil},
		{`foo.onion`, `foo.onion`},
		{`foo.i2p`, `foo.i2p`},
		{`中国.中国`, `中国.中国`},
		{`中国.中国/foo中国`, `中国.中国/foo中国`},
		{`foo.com/`, `foo.com/`},
		{`1.1.1.1`, `1.1.1.1`},
		{`10.50.23.250`, `10.50.23.250`},
		{`121.1.1.1`, `121.1.1.1`},
		{`255.1.1.1`, `255.1.1.1`},
		{`300.1.1.1`, nil},
		{`1.1.1.300`, nil},
		{`1080:0:0:0:8:800:200C:4171`, `1080:0:0:0:8:800:200C:4171`},
		{`3ffe:2a00:100:7031::1`, `3ffe:2a00:100:7031::1`},
		{`1080::8:800:200c:417a`, `1080::8:800:200c:417a`},
		{`foo.com:8080`, `foo.com:8080`},
		{`foo.com:8080/path`, `foo.com:8080/path`},
		{`test.foo.com`, `test.foo.com`},
		{`test.foo.com/path`, `test.foo.com/path`},
		{`test.foo.com/path/more/`, `test.foo.com/path/more/`},
		{`TEST.FOO.COM/PATH`, `TEST.FOO.COM/PATH`},
		{`TEST.FÓO.COM/PÁTH`, `TEST.FÓO.COM/PÁTH`},
		{`foo.com/path_(more)`, `foo.com/path_(more)`},
		{`foo.com/path_(even)_(more)`, `foo.com/path_(even)_(more)`},
		{`foo.com/path_(more)/more`, `foo.com/path_(more)/more`},
		{`foo.com/path_(more)/end)`, `foo.com/path_(more)/end`},
		{`www.foo.com`, `www.foo.com`},
		{` foo.com/bar `, `foo.com/bar`},
		{` foo.com/bar more`, `foo.com/bar`},
		{`<foo.com/bar>`, `foo.com/bar`},
		{`<foo.com/bar>more`, `foo.com/bar`},
		{`,foo.com/bar.`, `foo.com/bar`},
		{`,foo.com/bar.more`, `foo.com/bar.more`},
		{`,foo.com/bar,`, `foo.com/bar`},
		{`,foo.com/bar,more`, `foo.com/bar,more`},
		{`(foo.com/bar)`, `foo.com/bar`},
		{`"foo.com/bar'`, `foo.com/bar`},
		{`"foo.com/bar'more`, `foo.com/bar'more`},
		{`"foo.com/bar"`, `foo.com/bar`},
		{`what is foo.com?`, `foo.com`},
		{`the foo.com!`, `foo.com`},

		{`foo@bar`, nil},
		{`foo@bar.a`, nil},
		{`foo@bar.com`, `foo@bar.com`},
		{`foo@bar.com bar@bar.com`, `foo@bar.com`},
		{`foo@bar.onion`, `foo@bar.onion`},
		{`foo@中国.中国`, `foo@中国.中国`},
		{`foo@test.bar.com`, `foo@test.bar.com`},
		{`FOO@TEST.BAR.COM`, `FOO@TEST.BAR.COM`},
		{`foo@bar.com/path`, `foo@bar.com`},
		{`foo+test@bar.com`, `foo+test@bar.com`},
		{`foo+._%-@bar.com`, `foo+._%-@bar.com`},
	})
	doTest(t, "Strict", Strict, []regexTestCase{
		{`http:// foo.com`, nil},
		{`http:// foo.com`, nil},
		{`foo.a`, nil},
		{`foo.com`, nil},
		{`foo.com/`, nil},
		{`1.1.1.1`, nil},
		{`3ffe:2a00:100:7031::1`, nil},
		{`test.foo.com:8080/path`, nil},
		{`foo@bar.com`, nil},
	})
}

func TestStrictMatchingError(t *testing.T) {
	for _, c := range []struct {
		exp     string
		wantErr bool
	}{
		{`http://`, false},
		{`https?://`, false},
		{`http://|mailto:`, false},
		{`http://(`, true},
	} {
		_, err := StrictMatching(c.exp)
		if c.wantErr && err == nil {
			t.Errorf(`StrictMatching("%s") did not error as expected`, c.exp)
		} else if !c.wantErr && err != nil {
			t.Errorf(`StrictMatching("%s") unexpectedly errored`, c.exp)
		}
	}
}

func TestStrictMatching(t *testing.T) {
	strictMatching, _ := StrictMatching("http://|ftps?://|mailto:")
	doTest(t, "StrictMatching", strictMatching, []regexTestCase{
		{`foo.com`, nil},
		{`foo@bar.com`, nil},
		{`http://foo`, `http://foo`},
		{`https://foo`, nil},
		{`ftp://foo`, `ftp://foo`},
		{`ftps://foo`, `ftps://foo`},
		{`mailto:foo`, `mailto:foo`},
		{`sms:123`, nil},
	})
}

func bench(b *testing.B, re *regexp.Regexp, str string) {
	for i := 0; i < b.N; i++ {
		re.FindAllString(str, -1)
	}
}

func BenchmarkStrictEmpty(b *testing.B) {
	bench(b, Strict, "foo")
}

func BenchmarkStrictSingle(b *testing.B) {
	bench(b, Strict, "http://foo.foo foo.com")
}

func BenchmarkStrictMany(b *testing.B) {
	bench(b, Strict, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}

func BenchmarkRelaxedEmpty(b *testing.B) {
	bench(b, Relaxed, "foo")
}

func BenchmarkRelaxedSingle(b *testing.B) {
	bench(b, Relaxed, "http://foo.foo foo.com")
}

func BenchmarkRelaxedMany(b *testing.B) {
	bench(b, Relaxed, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}
<|endoftext|>


<commit_before>package xviper

import "github.com/spf13/viper"

type options struct {
	v           *viper.Viper
	configName  string
	configPaths []string
	defaults    map[string]interface{}
	values      map[string]interface{}
}

type option func(*options)

func New(o ...option) *viper.Viper {
	opts := options{}
	for _, f := range o {
		f(&opts)
	}

	v := opts.v
	if v != nil {
		v = viper.New()
	}

	for _, p := range opts.configPaths {
		v.AddConfigPath(p)
	}

	return v
}
<commit_msg>interim<commit_after>package xviper

import (
	"fmt"

	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

const (
	DefaultNameFlag = "name"
	DefaultFileFlag = "file"
)

type option func(*viper.Viper) error

func AddConfigPaths(paths ...string) option {
	return func(v *viper.Viper) error {
		for _, p := range paths {
			v.AddConfigPath(p)
		}

		return nil
	}
}

func SetEnvPrefix(prefix string) option {
	return func(v *viper.Viper) error {
		v.SetEnvPrefix(prefix)
		return nil
	}
}

func SetConfigName(name string) option {
	return func(v *viper.Viper) error {
		v.SetConfigName(name)
		return nil
	}
}

func SetConfigFile(file string) option {
	return func(v *viper.Viper) error {
		v.SetConfigFile(file)
		return nil
	}
}

func AutomaticEnv(v *viper.Viper) error {
	v.AutomaticEnv()
	return nil
}

func BindPFlags(fs *pflag.FlagSet) option {
	return func(v *viper.Viper) error {
		return v.BindPFlags(fs)
	}
}

func BindConfigName(fs *pflag.FlagSet, flag string) option {
	return func(v *viper.Viper) error {
		if f := fs.Lookup(flag); f != nil {
			configName := f.Value.String()
			if len(configName) > 0 {
				v.SetConfigName(configName)
			}
		}

		return nil
	}
}

func BindConfigFile(fs *pflag.FlagSet, flag string) option {
	return func(v *viper.Viper) error {
		if f := fs.Lookup(flag); f != nil {
			configFile := f.Value.String()
			if len(configFile) > 0 {
				v.SetConfigFile(configFile)
			}
		}

		return nil
	}
}

func StdOptions(applicationName string, fs *pflag.FlagSet) option {
	return func(v *viper.Viper) error {
		err := AddConfigPaths(
			fmt.Sprintf("/etc/%s", applicationName),
			fmt.Sprintf("$HOME/.%s", applicationName),
			".",
		)(v)

		if err == nil {
			err = SetEnvPrefix(applicationName)(v)
		}

		if err == nil {
			err = AutomaticEnv(v)
		}

		if err == nil {
			err = SetConfigName(applicationName)(v)
		}

		if err == nil {
			err = BindPFlags(fs)(v)
		}

		return err
	}
}

func New(o ...option) (*viper.Viper, error) {
	return Configure(viper.New(), o...)
}

func Configure(v *viper.Viper, o ...option) (*viper.Viper, error) {
	if v != nil {
		for _, f := range o {
			if err := f(v); err != nil {
				return nil, err
			}
		}
	}

	return v, nil
}
<|endoftext|>


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

package mailer

import (
	"crypto/tls"
	"fmt"
	"io"
	"net"
	"net/smtp"
	"os"
	"os/exec"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"

	"github.com/jaytaylor/html2text"
	"gopkg.in/gomail.v2"
)

// Message mail body and log info
type Message struct {
	Info string // Message information for log purpose.
	*gomail.Message
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, body string) *Message {
	log.Trace("NewMessageFrom (body):\n%s", body)

	msg := gomail.NewMessage()
	msg.SetHeader("From", from)
	msg.SetHeader("To", to...)
	msg.SetHeader("Subject", subject)
	msg.SetDateHeader("Date", time.Now())

	plainBody, err := html2text.FromString(body)
	if err != nil || setting.MailService.SendAsPlainText {
		if strings.Contains(body[:100], "<html>") {
			log.Warn("Mail contains HTML but configured to send as plain text.")
		}
		msg.SetBody("text/plain", plainBody)
	} else {
		msg.SetBody("text/plain", plainBody)
		msg.AddAlternative("text/html", body)
	}

	return &Message{
		Message: msg,
	}
}

// NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message {
	return NewMessageFrom(to, setting.MailService.From, subject, body)
}

type loginAuth struct {
	username, password string
}

// LoginAuth SMTP AUTH LOGIN Auth Handler
func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

// Start start SMTP login auth
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte{}, nil
}

// Next next step of SMTP login auth
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, fmt.Errorf("unknown fromServer: %s", string(fromServer))
		}
	}
	return nil, nil
}

// Sender SMTP mail sender
type smtpSender struct {
}

// Send send email
func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
	opts := setting.MailService

	host, port, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return err
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}

	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}
	}

	conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
	if err != nil {
		return err
	}
	defer conn.Close()

	isSecureConn := false
	// Start TLS directly if the port ends with 465 (SMTPS protocol)
	if strings.HasSuffix(port, "465") {
		conn = tls.Client(conn, tlsconfig)
		isSecureConn = true
	}

	client, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("NewClient: %v", err)
	}

	if !opts.DisableHelo {
		hostname := opts.HeloHostname
		if len(hostname) == 0 {
			hostname, err = os.Hostname()
			if err != nil {
				return err
			}
		}

		if err = client.Hello(hostname); err != nil {
			return fmt.Errorf("Hello: %v", err)
		}
	}

	// If not using SMTPS, always use STARTTLS if available
	hasStartTLS, _ := client.Extension("STARTTLS")
	if !isSecureConn && hasStartTLS {
		if err = client.StartTLS(tlsconfig); err != nil {
			return fmt.Errorf("StartTLS: %v", err)
		}
	}

	canAuth, options := client.Extension("AUTH")
	if canAuth && len(opts.User) > 0 {
		var auth smtp.Auth

		if strings.Contains(options, "CRAM-MD5") {
			auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
		} else if strings.Contains(options, "PLAIN") {
			auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
		} else if strings.Contains(options, "LOGIN") {
			// Patch for AUTH LOGIN
			auth = LoginAuth(opts.User, opts.Passwd)
		}

		if auth != nil {
			if err = client.Auth(auth); err != nil {
				return fmt.Errorf("Auth: %v", err)
			}
		}
	}

	if err = client.Mail(from); err != nil {
		return fmt.Errorf("Mail: %v", err)
	}

	for _, rec := range to {
		if err = client.Rcpt(rec); err != nil {
			return fmt.Errorf("Rcpt: %v", err)
		}
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("Data: %v", err)
	} else if _, err = msg.WriteTo(w); err != nil {
		return fmt.Errorf("WriteTo: %v", err)
	} else if err = w.Close(); err != nil {
		return fmt.Errorf("Close: %v", err)
	}

	return client.Quit()
}

// Sender sendmail mail sender
type sendmailSender struct {
}

// Send send email
func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
	var err error
	var closeError error
	var waitError error

	args := []string{"-F", from, "-i"}
	args = append(args, to...)
	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
	cmd := exec.Command(setting.MailService.SendmailPath, args...)
	pipe, err := cmd.StdinPipe()

	if err != nil {
		return err
	}

	if err = cmd.Start(); err != nil {
		return err
	}

	_, err = msg.WriteTo(pipe)

	// we MUST close the pipe or sendmail will hang waiting for more of the message
	// Also we should wait on our sendmail command even if something fails
	closeError = pipe.Close()
	waitError = cmd.Wait()
	if err != nil {
		return err
	} else if closeError != nil {
		return closeError
	} else {
		return waitError
	}
}

func processMailQueue() {
	for {
		select {
		case msg := <-mailQueue:
			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
			if err := gomail.Send(Sender, msg.Message); err != nil {
				log.Error(3, "Failed to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
			} else {
				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
			}
		}
	}
}

var mailQueue chan *Message

// Sender sender for sending mail synchronously
var Sender gomail.Sender

// NewContext start mail queue service
func NewContext() {
	// Need to check if mailQueue is nil because in during reinstall (user had installed
	// before but swithed install lock off), this function will be called again
	// while mail queue is already processing tasks, and produces a race condition.
	if setting.MailService == nil || mailQueue != nil {
		return
	}

	if setting.MailService.UseSendmail {
		Sender = &sendmailSender{}
	} else {
		Sender = &smtpSender{}
	}

	mailQueue = make(chan *Message, setting.MailService.QueueLength)
	go processMailQueue()
}

// SendAsync send mail asynchronous
func SendAsync(msg *Message) {
	go func() {
		mailQueue <- msg
	}()
}
<commit_msg>Fix slice out of bounds error in mailer (#2479)<commit_after>// Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2017 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package mailer

import (
	"crypto/tls"
	"fmt"
	"io"
	"net"
	"net/smtp"
	"os"
	"os/exec"
	"strings"
	"time"

	"code.gitea.io/gitea/modules/base"
	"code.gitea.io/gitea/modules/log"
	"code.gitea.io/gitea/modules/setting"

	"github.com/jaytaylor/html2text"
	"gopkg.in/gomail.v2"
)

// Message mail body and log info
type Message struct {
	Info string // Message information for log purpose.
	*gomail.Message
}

// NewMessageFrom creates new mail message object with custom From header.
func NewMessageFrom(to []string, from, subject, body string) *Message {
	log.Trace("NewMessageFrom (body):\n%s", body)

	msg := gomail.NewMessage()
	msg.SetHeader("From", from)
	msg.SetHeader("To", to...)
	msg.SetHeader("Subject", subject)
	msg.SetDateHeader("Date", time.Now())

	plainBody, err := html2text.FromString(body)
	if err != nil || setting.MailService.SendAsPlainText {
		if strings.Contains(base.TruncateString(body, 100), "<html>"){
			log.Warn("Mail contains HTML but configured to send as plain text.")
		}
		msg.SetBody("text/plain", plainBody)
	} else {
		msg.SetBody("text/plain", plainBody)
		msg.AddAlternative("text/html", body)
	}

	return &Message{
		Message: msg,
	}
}

// NewMessage creates new mail message object with default From header.
func NewMessage(to []string, subject, body string) *Message {
	return NewMessageFrom(to, setting.MailService.From, subject, body)
}

type loginAuth struct {
	username, password string
}

// LoginAuth SMTP AUTH LOGIN Auth Handler
func LoginAuth(username, password string) smtp.Auth {
	return &loginAuth{username, password}
}

// Start start SMTP login auth
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
	return "LOGIN", []byte{}, nil
}

// Next next step of SMTP login auth
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
	if more {
		switch string(fromServer) {
		case "Username:":
			return []byte(a.username), nil
		case "Password:":
			return []byte(a.password), nil
		default:
			return nil, fmt.Errorf("unknown fromServer: %s", string(fromServer))
		}
	}
	return nil, nil
}

// Sender SMTP mail sender
type smtpSender struct {
}

// Send send email
func (s *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
	opts := setting.MailService

	host, port, err := net.SplitHostPort(opts.Host)
	if err != nil {
		return err
	}

	tlsconfig := &tls.Config{
		InsecureSkipVerify: opts.SkipVerify,
		ServerName:         host,
	}

	if opts.UseCertificate {
		cert, err := tls.LoadX509KeyPair(opts.CertFile, opts.KeyFile)
		if err != nil {
			return err
		}
		tlsconfig.Certificates = []tls.Certificate{cert}
	}

	conn, err := net.Dial("tcp", net.JoinHostPort(host, port))
	if err != nil {
		return err
	}
	defer conn.Close()

	isSecureConn := false
	// Start TLS directly if the port ends with 465 (SMTPS protocol)
	if strings.HasSuffix(port, "465") {
		conn = tls.Client(conn, tlsconfig)
		isSecureConn = true
	}

	client, err := smtp.NewClient(conn, host)
	if err != nil {
		return fmt.Errorf("NewClient: %v", err)
	}

	if !opts.DisableHelo {
		hostname := opts.HeloHostname
		if len(hostname) == 0 {
			hostname, err = os.Hostname()
			if err != nil {
				return err
			}
		}

		if err = client.Hello(hostname); err != nil {
			return fmt.Errorf("Hello: %v", err)
		}
	}

	// If not using SMTPS, always use STARTTLS if available
	hasStartTLS, _ := client.Extension("STARTTLS")
	if !isSecureConn && hasStartTLS {
		if err = client.StartTLS(tlsconfig); err != nil {
			return fmt.Errorf("StartTLS: %v", err)
		}
	}

	canAuth, options := client.Extension("AUTH")
	if canAuth && len(opts.User) > 0 {
		var auth smtp.Auth

		if strings.Contains(options, "CRAM-MD5") {
			auth = smtp.CRAMMD5Auth(opts.User, opts.Passwd)
		} else if strings.Contains(options, "PLAIN") {
			auth = smtp.PlainAuth("", opts.User, opts.Passwd, host)
		} else if strings.Contains(options, "LOGIN") {
			// Patch for AUTH LOGIN
			auth = LoginAuth(opts.User, opts.Passwd)
		}

		if auth != nil {
			if err = client.Auth(auth); err != nil {
				return fmt.Errorf("Auth: %v", err)
			}
		}
	}

	if err = client.Mail(from); err != nil {
		return fmt.Errorf("Mail: %v", err)
	}

	for _, rec := range to {
		if err = client.Rcpt(rec); err != nil {
			return fmt.Errorf("Rcpt: %v", err)
		}
	}

	w, err := client.Data()
	if err != nil {
		return fmt.Errorf("Data: %v", err)
	} else if _, err = msg.WriteTo(w); err != nil {
		return fmt.Errorf("WriteTo: %v", err)
	} else if err = w.Close(); err != nil {
		return fmt.Errorf("Close: %v", err)
	}

	return client.Quit()
}

// Sender sendmail mail sender
type sendmailSender struct {
}

// Send send email
func (s *sendmailSender) Send(from string, to []string, msg io.WriterTo) error {
	var err error
	var closeError error
	var waitError error

	args := []string{"-F", from, "-i"}
	args = append(args, to...)
	log.Trace("Sending with: %s %v", setting.MailService.SendmailPath, args)
	cmd := exec.Command(setting.MailService.SendmailPath, args...)
	pipe, err := cmd.StdinPipe()

	if err != nil {
		return err
	}

	if err = cmd.Start(); err != nil {
		return err
	}

	_, err = msg.WriteTo(pipe)

	// we MUST close the pipe or sendmail will hang waiting for more of the message
	// Also we should wait on our sendmail command even if something fails
	closeError = pipe.Close()
	waitError = cmd.Wait()
	if err != nil {
		return err
	} else if closeError != nil {
		return closeError
	} else {
		return waitError
	}
}

func processMailQueue() {
	for {
		select {
		case msg := <-mailQueue:
			log.Trace("New e-mail sending request %s: %s", msg.GetHeader("To"), msg.Info)
			if err := gomail.Send(Sender, msg.Message); err != nil {
				log.Error(3, "Failed to send emails %s: %s - %v", msg.GetHeader("To"), msg.Info, err)
			} else {
				log.Trace("E-mails sent %s: %s", msg.GetHeader("To"), msg.Info)
			}
		}
	}
}

var mailQueue chan *Message

// Sender sender for sending mail synchronously
var Sender gomail.Sender

// NewContext start mail queue service
func NewContext() {
	// Need to check if mailQueue is nil because in during reinstall (user had installed
	// before but swithed install lock off), this function will be called again
	// while mail queue is already processing tasks, and produces a race condition.
	if setting.MailService == nil || mailQueue != nil {
		return
	}

	if setting.MailService.UseSendmail {
		Sender = &sendmailSender{}
	} else {
		Sender = &smtpSender{}
	}

	mailQueue = make(chan *Message, setting.MailService.QueueLength)
	go processMailQueue()
}

// SendAsync send mail asynchronous
func SendAsync(msg *Message) {
	go func() {
		mailQueue <- msg
	}()
}
<|endoftext|>


<commit_before>package wallet

import (
	"errors"
	"sort"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errDefragNotNeeded = errors.New("defragging not needed, wallet is already sufficiently defragged")
)

// fundDefragger is a private function which funds the defragger transaction.
// This helper func is needed because the lock on the wallet cannot be dropped
// throughout scanning the outputs to determine if defragmentation is necessary
// and then proceeding to actually defrag.
func (tb *transactionBuilder) fundDefragger(fee types.Currency) (types.Currency, error) {
	// Sanity check
	if build.DEBUG && defragThreshold <= defragBatchSize+defragStartIndex {
		panic("constants are incorrect, defragThreshold needs to be larger than the sum of defragBatchSize and defragStartIndex")
	}

	tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock()

	// Only defrag if the wallet is unlocked.
	if !tb.wallet.unlocked {
		return types.Currency{}, errDefragNotNeeded
	}

	// Collect a set of outputs for defragging.
	var so sortedOutputs
	var num int
	for scoid, sco := range tb.wallet.siacoinOutputs {
		// Skip over any outputs that aren't actually spendable.
		if err := tb.wallet.checkOutput(scoid, sco); err != nil {
			continue
		}
		so.ids = append(so.ids, scoid)
		so.outputs = append(so.outputs, sco)
		num++
	}

	// Only defrag if there are enough outputs to merit defragging.
	if num <= defragThreshold {
		return types.Currency{}, errDefragNotNeeded
	}

	// Sort the outputs by size.
	sort.Sort(sort.Reverse(so))

	// Use all of the smaller outputs to fund the transaction, tracking the
	// total number of coins used to fund the transaction.
	var amount types.Currency
	parentTxn := types.Transaction{}
	var spentScoids []types.SiacoinOutputID
	for i := defragStartIndex; i < defragStartIndex+defragBatchSize; i++ {
		scoid := so.ids[i]
		sco := so.outputs[i]

		// Add a siacoin input for this output.
		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].UnlockConditions
		sci := types.SiacoinInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
		spentScoids = append(spentScoids, scoid)

		// Add the output to the total fund
		amount = amount.Add(sco.Value)
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return types.Currency{}, err
	}
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Sign all of the inputs to the parent trancstion.
	for _, sci := range parentTxn.SiacoinInputs {
		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
		if err != nil {
			return types.Currency{}, err
		}
	}
	// Mark the parent output as spent. Must be done after the transaction is
	// finished because otherwise the txid and output id will change.
	tb.wallet.spentOutputs[types.OutputID(parentTxn.SiacoinOutputID(0))] = tb.wallet.consensusSetHeight

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentUnlockConditions,
	}
	tb.newParents = append(tb.newParents, len(tb.parents))
	tb.parents = append(tb.parents, parentTxn)
	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)

	// Mark all outputs that were spent as spent.
	for _, scoid := range spentScoids {
		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight
	}
	return amount, nil
}

// threadedDefragWallet computes the sum of the 15 largest outputs in the wallet and
// sends that sum to itself, effectively defragmenting the wallet. This defrag
// operation is only performed if the wallet has greater than defragThreshold
// outputs.
func (w *Wallet) threadedDefragWallet() {
	err := w.tg.Add()
	if err != nil {
		return
	}
	defer w.tg.Done()

	// grab a new address from the wallet
	w.mu.Lock()
	addr, err := w.nextPrimarySeedAddress()
	w.mu.Unlock()
	if err != nil {
		w.log.Println("Error getting an address for defragmentation: ", err)
		return
	}

	// Create a transaction builder.
	fee := defragFee()
	tbuilder := w.registerTransaction(types.Transaction{}, nil)
	// Fund it using a defragging specific method.
	amount, err := tbuilder.fundDefragger(fee)
	if err != nil {
		if err != errDefragNotNeeded {
			w.log.Println("Error while trying to fund the defragging transaction", err)
		}
		return
	}
	// Add the miner fee.
	tbuilder.AddMinerFee(fee)
	// Add the refund.
	tbuilder.AddSiacoinOutput(types.SiacoinOutput{
		Value:      amount.Sub(fee),
		UnlockHash: addr.UnlockHash(),
	})
	// Sign the transaction.
	txns, err := tbuilder.Sign(true)
	if err != nil {
		w.log.Println("Error signing transaction set in defrag transaction: ", err)
		return
	}
	// Submit the defrag to the transaction pool.
	err = w.tpool.AcceptTransactionSet(txns)
	if err != nil {
		w.log.Println("Error accepting transaction set in defrag transaction: ", err)
	}
}
<commit_msg>check if a defrag is needed before grabbing an address<commit_after>package wallet

import (
	"errors"
	"sort"

	"github.com/NebulousLabs/Sia/build"
	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errDefragNotNeeded = errors.New("defragging not needed, wallet is already sufficiently defragged")
)

// fundDefragger is a private function which funds the defragger transaction.
// This helper func is needed because the lock on the wallet cannot be dropped
// throughout scanning the outputs to determine if defragmentation is necessary
// and then proceeding to actually defrag.
func (tb *transactionBuilder) fundDefragger(fee types.Currency) (types.Currency, error) {
	tb.wallet.mu.Lock()
	defer tb.wallet.mu.Unlock()

	// Collect a set of outputs for defragging.
	var so sortedOutputs
	for scoid, sco := range tb.wallet.siacoinOutputs {
		// Skip over any outputs that aren't actually spendable.
		if err := tb.wallet.checkOutput(scoid, sco); err != nil {
			continue
		}
		so.ids = append(so.ids, scoid)
		so.outputs = append(so.outputs, sco)
	}

	// Only defrag if there are enough outputs to merit defragging.
	if len(so.ids) <= defragThreshold {
		return types.Currency{}, errDefragNotNeeded
	}
	// Sanity check - the defrag threshold needs to be higher than the batch
	// size plus the start index.
	if build.DEBUG && defragThreshold <= defragBatchSize+defragStartIndex {
		panic("constants are incorrect, defragThreshold needs to be larger than the sum of defragBatchSize and defragStartIndex")
	}

	// Sort the outputs by size.
	sort.Sort(sort.Reverse(so))

	// Skip over the 'defragStartIndex' largest outputs, so that the user can
	// still reasonably use their wallet while the defrag is happening.
	var amount types.Currency
	parentTxn := types.Transaction{}
	var spentScoids []types.SiacoinOutputID
	for i := defragStartIndex; i < defragStartIndex+defragBatchSize; i++ {
		scoid := so.ids[i]
		sco := so.outputs[i]

		// Add a siacoin input for this output.
		outputUnlockConditions := tb.wallet.keys[sco.UnlockHash].UnlockConditions
		sci := types.SiacoinInput{
			ParentID:         scoid,
			UnlockConditions: outputUnlockConditions,
		}
		parentTxn.SiacoinInputs = append(parentTxn.SiacoinInputs, sci)
		spentScoids = append(spentScoids, scoid)

		// Add the output to the total fund
		amount = amount.Add(sco.Value)
	}

	// Create and add the output that will be used to fund the standard
	// transaction.
	parentUnlockConditions, err := tb.wallet.nextPrimarySeedAddress()
	if err != nil {
		return types.Currency{}, err
	}
	exactOutput := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: parentUnlockConditions.UnlockHash(),
	}
	parentTxn.SiacoinOutputs = append(parentTxn.SiacoinOutputs, exactOutput)

	// Sign all of the inputs to the parent trancstion.
	for _, sci := range parentTxn.SiacoinInputs {
		_, err := addSignatures(&parentTxn, types.FullCoveredFields, sci.UnlockConditions, crypto.Hash(sci.ParentID), tb.wallet.keys[sci.UnlockConditions.UnlockHash()])
		if err != nil {
			return types.Currency{}, err
		}
	}
	// Mark the parent output as spent. Must be done after the transaction is
	// finished because otherwise the txid and output id will change.
	tb.wallet.spentOutputs[types.OutputID(parentTxn.SiacoinOutputID(0))] = tb.wallet.consensusSetHeight

	// Add the exact output.
	newInput := types.SiacoinInput{
		ParentID:         parentTxn.SiacoinOutputID(0),
		UnlockConditions: parentUnlockConditions,
	}
	tb.newParents = append(tb.newParents, len(tb.parents))
	tb.parents = append(tb.parents, parentTxn)
	tb.siacoinInputs = append(tb.siacoinInputs, len(tb.transaction.SiacoinInputs))
	tb.transaction.SiacoinInputs = append(tb.transaction.SiacoinInputs, newInput)

	// Mark all outputs that were spent as spent.
	for _, scoid := range spentScoids {
		tb.wallet.spentOutputs[types.OutputID(scoid)] = tb.wallet.consensusSetHeight
	}
	return amount, nil
}

// threadedDefragWallet computes the sum of the 15 largest outputs in the wallet and
// sends that sum to itself, effectively defragmenting the wallet. This defrag
// operation is only performed if the wallet has greater than defragThreshold
// outputs.
func (w *Wallet) threadedDefragWallet() {
	err := w.tg.Add()
	if err != nil {
		return
	}
	defer w.tg.Done()

	// Check that a defrag makes sense.
	w.mu.Lock()
	unlocked := w.unlocked
	var usableOutputs int
	for scoid, sco := range w.siacoinOutputs {
		// Only count the output if it's usable.
		if err := w.checkOutput(scoid, sco); err == nil {
			usableOutputs++
		}
	}
	w.mu.Unlock()

	// Can't defrag if the wallet is locked.
	if !unlocked {
		return
	}
	// No need to defrag if the number of outputs is below the defrag limit.
	if usableOutputs < defragThreshold {
		return
	}

	// grab a new address from the wallet
	w.mu.Lock()
	addr, err := w.nextPrimarySeedAddress()
	w.mu.Unlock()
	if err != nil {
		// User may have locekd the wallet between the above check and the
		// request for the address.
		w.log.Debugln("Error getting an address for defragmentation: ", err)
		return
	}

	// Create a transaction builder.
	fee := defragFee()
	tbuilder := w.registerTransaction(types.Transaction{}, nil)
	// Fund it using a defragging specific method.
	amount, err := tbuilder.fundDefragger(fee)
	if err != nil {
		if err != errDefragNotNeeded {
			w.log.Println("Error while trying to fund the defragging transaction", err)
		}
		return
	}
	// Add the miner fee.
	tbuilder.AddMinerFee(fee)
	// Add the refund.
	tbuilder.AddSiacoinOutput(types.SiacoinOutput{
		Value:      amount.Sub(fee),
		UnlockHash: addr.UnlockHash(),
	})
	// Sign the transaction.
	txns, err := tbuilder.Sign(true)
	if err != nil {
		w.log.Println("Error signing transaction set in defrag transaction: ", err)
		return
	}
	// Submit the defrag to the transaction pool.
	err = w.tpool.AcceptTransactionSet(txns)
	if err != nil {
		w.log.Println("Error accepting transaction set in defrag transaction: ", err)
	}
}
<|endoftext|>


<commit_before>package wallet

import (
	"errors"
	"fmt"
	"os"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/consensus"
	"github.com/NebulousLabs/Sia/sync"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// AgeDelay indicates how long the wallet will wait before allowing the
	// user to double-spend a transaction under standard circumstances. The
	// rationale is that most transactions are meant to be submitted to the
	// blockchain immediately, and ones that take more than AgeDelay blocks
	// have probably failed in some way.
	AgeDelay = 80
)

// A Wallet uses the state and transaction pool to track the unconfirmed
// balance of a user. All of the keys are stored in 'saveDir'/wallet.dat.
//
// One feature of the wallet is preventing accidental double spends. The wallet
// will block an output from being spent if it has been spent in the last
// 'AgeDelay' blocks. This is managed by tracking a global age for the wallet
// and then an age for each output, set to the age of the wallet that the
// output was most recently spent. If the wallet is 'AgeDelay' blocks older
// than an output, then the output can be spent again.
//
// A second feature of the wallet is the transaction builder, which is a series
// of functions that can be used to build independent transactions for use with
// untrusted parties. The transactions can be cobbled together piece by piece
// and then signed. When using the transaction builder, the wallet will always
// have exact outputs (by creating another transaction first if needed) and
// thus the transaction does not need to be spent for the transaction builder
// to be able to use any refunds.
type Wallet struct {
	state            *consensus.State
	tpool            modules.TransactionPool
	unconfirmedDiffs []modules.SiacoinOutputDiff

	// Location of the wallet directory, for saving and loading keys.
	saveDir string

	// A key contains all the information necessary to spend a particular
	// address, as well as all the known outputs that use the address.
	//
	// age is a tool to determine whether or not an output can be spent. When
	// an output is spent by the wallet, the age of the output is marked equal
	// to the age of the wallet. It will not be spent again until the age is
	// `AgeDelay` less than the wallet. The wallet ages by 1 every block. The
	// wallet can also be manually aged, which is a convenient and efficient
	// way of resetting spent outputs. Transactions are not intended to be
	// broadcast for a while can be given an age that is much greater than the
	// wallet.
	//
	// Timelocked keys is a list of addresses found in `keys` that can't be
	// spent until a certain height. The wallet will use `timelockedKeys` to
	// mark keys as unspendable until the timelock has lifted.
	//
	// Visible keys will be displayed to the user.
	age              int
	keys             map[types.UnlockHash]*key
	timelockedKeys   map[types.BlockHeight][]types.UnlockHash
	visibleAddresses map[types.UnlockHash]struct{}

	// transactions is a list of transactions that are currently being built by
	// the wallet. Each transaction has a unique id, which is enforced by the
	// transactionCounter.
	transactionCounter int
	transactions       map[string]*openTransaction

	subscribers []chan struct{}

	mu *sync.RWMutex
}

// New creates a new wallet, loading any known addresses from the input file
// name and then using the file to save in the future.
func New(state *consensus.State, tpool modules.TransactionPool, saveDir string) (w *Wallet, err error) {
	if state == nil {
		err = errors.New("wallet cannot use a nil state")
		return
	}
	if tpool == nil {
		err = errors.New("wallet cannot use a nil transaction pool")
		return
	}

	w = &Wallet{
		state: state,
		tpool: tpool,

		saveDir: saveDir,

		age:              AgeDelay + 100,
		keys:             make(map[types.UnlockHash]*key),
		timelockedKeys:   make(map[types.BlockHeight][]types.UnlockHash),
		visibleAddresses: make(map[types.UnlockHash]struct{}),

		transactions: make(map[string]*openTransaction),

		mu: sync.New(modules.SafeMutexDelay, 1),
	}

	// Create the wallet folder.
	err = os.MkdirAll(saveDir, 0700)
	if err != nil {
		return
	}

	// Try to load a previously saved wallet file. If it doesn't exist, assume
	// that we're creating a new wallet file.
	// TODO: log warning if no file found?
	err = w.load()
	if os.IsNotExist(err) {
		err = nil
	}
	if err != nil {
		err = fmt.Errorf("couldn't load wallet file %s: %v", saveDir, err)
		// TODO: try to recover from wallet.backup?
		return
	}

	w.tpool.TransactionPoolSubscribe(w)

	return
}

func (w *Wallet) Close() error {
	id := w.mu.RLock()
	defer w.mu.RUnlock(id)
	return w.save()
}

// SpendCoins creates a transaction sending 'amount' to 'dest'. The transaction
// is submitted to the transaction pool and is also returned.
func (w *Wallet) SpendCoins(amount types.Currency, dest types.UnlockHash) (t types.Transaction, err error) {
	// Create and send the transaction.
	output := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: dest,
	}
	id, err := w.RegisterTransaction(t)
	if err != nil {
		return
	}
	_, err = w.FundTransaction(id, amount)
	if err != nil {
		return
	}
	_, _, err = w.AddOutput(id, output)
	if err != nil {
		return
	}
	t, err = w.SignTransaction(id, true)
	if err != nil {
		return
	}
	err = w.tpool.AcceptTransaction(t)
	if err != nil {
		return
	}

	return
}
<commit_msg>make a new address even if load() fails<commit_after>package wallet

import (
	"errors"
	"fmt"
	"os"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/consensus"
	"github.com/NebulousLabs/Sia/sync"
	"github.com/NebulousLabs/Sia/types"
)

const (
	// AgeDelay indicates how long the wallet will wait before allowing the
	// user to double-spend a transaction under standard circumstances. The
	// rationale is that most transactions are meant to be submitted to the
	// blockchain immediately, and ones that take more than AgeDelay blocks
	// have probably failed in some way.
	AgeDelay = 80
)

// A Wallet uses the state and transaction pool to track the unconfirmed
// balance of a user. All of the keys are stored in 'saveDir'/wallet.dat.
//
// One feature of the wallet is preventing accidental double spends. The wallet
// will block an output from being spent if it has been spent in the last
// 'AgeDelay' blocks. This is managed by tracking a global age for the wallet
// and then an age for each output, set to the age of the wallet that the
// output was most recently spent. If the wallet is 'AgeDelay' blocks older
// than an output, then the output can be spent again.
//
// A second feature of the wallet is the transaction builder, which is a series
// of functions that can be used to build independent transactions for use with
// untrusted parties. The transactions can be cobbled together piece by piece
// and then signed. When using the transaction builder, the wallet will always
// have exact outputs (by creating another transaction first if needed) and
// thus the transaction does not need to be spent for the transaction builder
// to be able to use any refunds.
type Wallet struct {
	state            *consensus.State
	tpool            modules.TransactionPool
	unconfirmedDiffs []modules.SiacoinOutputDiff

	// Location of the wallet directory, for saving and loading keys.
	saveDir string

	// A key contains all the information necessary to spend a particular
	// address, as well as all the known outputs that use the address.
	//
	// age is a tool to determine whether or not an output can be spent. When
	// an output is spent by the wallet, the age of the output is marked equal
	// to the age of the wallet. It will not be spent again until the age is
	// `AgeDelay` less than the wallet. The wallet ages by 1 every block. The
	// wallet can also be manually aged, which is a convenient and efficient
	// way of resetting spent outputs. Transactions are not intended to be
	// broadcast for a while can be given an age that is much greater than the
	// wallet.
	//
	// Timelocked keys is a list of addresses found in `keys` that can't be
	// spent until a certain height. The wallet will use `timelockedKeys` to
	// mark keys as unspendable until the timelock has lifted.
	//
	// Visible keys will be displayed to the user.
	age              int
	keys             map[types.UnlockHash]*key
	timelockedKeys   map[types.BlockHeight][]types.UnlockHash
	visibleAddresses map[types.UnlockHash]struct{}

	// transactions is a list of transactions that are currently being built by
	// the wallet. Each transaction has a unique id, which is enforced by the
	// transactionCounter.
	transactionCounter int
	transactions       map[string]*openTransaction

	subscribers []chan struct{}

	mu *sync.RWMutex
}

// New creates a new wallet, loading any known addresses from the input file
// name and then using the file to save in the future.
func New(state *consensus.State, tpool modules.TransactionPool, saveDir string) (w *Wallet, err error) {
	if state == nil {
		err = errors.New("wallet cannot use a nil state")
		return
	}
	if tpool == nil {
		err = errors.New("wallet cannot use a nil transaction pool")
		return
	}

	w = &Wallet{
		state: state,
		tpool: tpool,

		saveDir: saveDir,

		age:              AgeDelay + 100,
		keys:             make(map[types.UnlockHash]*key),
		timelockedKeys:   make(map[types.BlockHeight][]types.UnlockHash),
		visibleAddresses: make(map[types.UnlockHash]struct{}),

		transactions: make(map[string]*openTransaction),

		mu: sync.New(modules.SafeMutexDelay, 1),
	}

	// Create the wallet folder.
	err = os.MkdirAll(saveDir, 0700)
	if err != nil {
		return
	}

	// Try to load a previously saved wallet file. If it doesn't exist, assume
	// that we're creating a new wallet file.
	// TODO: log warning if no file found?
	err = w.load()
	if os.IsNotExist(err) {
		err = nil
		// No wallet file exists... make a visible address for the user.
		_, _, err = w.coinAddress(true)
		if err != nil {
			return nil, err
		}
	}
	if err != nil {
		err = fmt.Errorf("couldn't load wallet file %s: %v", saveDir, err)
		// TODO: try to recover from wallet.backup?
		return
	}

	w.tpool.TransactionPoolSubscribe(w)

	return
}

func (w *Wallet) Close() error {
	id := w.mu.RLock()
	defer w.mu.RUnlock(id)
	return w.save()
}

// SpendCoins creates a transaction sending 'amount' to 'dest'. The transaction
// is submitted to the transaction pool and is also returned.
func (w *Wallet) SpendCoins(amount types.Currency, dest types.UnlockHash) (t types.Transaction, err error) {
	// Create and send the transaction.
	output := types.SiacoinOutput{
		Value:      amount,
		UnlockHash: dest,
	}
	id, err := w.RegisterTransaction(t)
	if err != nil {
		return
	}
	_, err = w.FundTransaction(id, amount)
	if err != nil {
		return
	}
	_, _, err = w.AddOutput(id, output)
	if err != nil {
		return
	}
	t, err = w.SignTransaction(id, true)
	if err != nil {
		return
	}
	err = w.tpool.AcceptTransaction(t)
	if err != nil {
		return
	}

	return
}
<|endoftext|>


<commit_before>package bot

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
)

// interface Gobot defines the API for plugins
type Gobot interface {
	Chatbot
	BotLogger
}

// Robot is passed to the plugin to enable convenience functions Say and Reply
type Robot struct {
	User    string // The user who sent the message; this can be modified for replying to an arbitrary user
	Channel string // The channel where the message was received, or "" for a direct message. This can be modified to send a message to an arbitrary channel.
	Format  string // The outgoing message format, one of "fixed", "variable"
	Gobot
}

// TODO: implement Say and Reply convenience functions

// PluginHelp specifies keywords and help text for the 'bot help system
type PluginHelp struct {
	Keywords []string // match words for 'help XXX'
	Helptext []string // help string to give for the keywords, conventionally starting with (bot) for commands or (hear) when the bot needn't be addressed directly
}

// InputMatchers specify the command or message to match and what to pass to the plugin
type InputMatcher struct {
	Regex   string         // The regular expression string to match - bot adds ^\w* & \w*$
	Command string         // The name of the command to pass to the plugin with it's arguments
	re      *regexp.Regexp // The compiled regular expression. If the regex doesn't compile, the 'bot will log an error
}

// Plugin specifies the structure of a plugin configuration - plugins should include an example
type Plugin struct {
	Name           string          // the name of the plugin
	PluginType     string          // "go" or "external", determines how commands are interpreted
	PluginPath     string          // Path to the external executable that expects <channel> <user> <command> <arg> <arg> from regex matches - for Plugtype=shell only
	AllowDirect    bool            // Whether or not the plugin responds to direct messages
	Channels       []string        // Channels where the plugin is active - rifraf like "memes" should probably only be in random, but it's configurable. If empty uses DefaultChannels
	Help           []PluginHelp    // All the keyword sets / help texts for this plugin
	CommandMatches []InputMatcher  // Input matchers for messages that need to be directed to the 'bot
	MessageMatches []InputMatcher  // Input matchers for messages the 'bot hears even when it's not being spoken to
	Config         json.RawMessage // Plugin Configuration - the plugin needs to decode this
}

// initialize sends the "start" command to every plugin
func (b *Bot) initializePlugins() {
	bot := Robot{
		User:    b.name,
		Channel: "",
		Format:  "variable",
		Gobot:   b,
	}
	for _, handler := range goPluginHandlers {
		go handler(bot, "", b.name, "start")
	}
}

// goPluginHandlers maps from plugin names to handler functions; populated during package initialization and never written to again.
var goPluginHandlers map[string]func(bot Robot, channel, user, command string, args ...string) error = make(map[string]func(bot Robot, channel, user, command string, args ...string) error)

// stopRegistrations is set "true" when the bot is created to prevent registration outside of init functions
var stopRegistrations bool = false

// RegisterPlugin allows plugins to register a handler function in a func init().
// When the bot initializes, it will call each plugin's handler with a command
// "start", empty channel, the bot's username, and no arguments, so the plugin
// can store this information for, e.g., scheduled jobs.
func RegisterPlugin(name string, handler func(bot Robot, channel, user, command string, args ...string) error) {
	if stopRegistrations {
		return
	}
	goPluginHandlers[name] = handler
}

// handle checks the message against plugin commands and full-message matches,
// then dispatches it to all applicable handlers.
func (b *Bot) handleMessage(isCommand bool, channel, user, messagetext string) {
	b.RLock()
	bot := Robot{
		User:    user,
		Channel: channel,
		Format:  "variable",
		Gobot:   b,
	}
	for _, plugin := range b.plugins {
		if len(plugin.Channels) > 0 {
			ok := false
			if len(channel) > 0 {
				for _, pchannel := range plugin.Channels {
					if pchannel == channel {
						ok = true
					}
				}
			} else {
				b.Log(Debug, fmt.Sprintf("Checking whether direct messages allowed for %s, AllowDirect is %b", plugin.Name, plugin.AllowDirect))
				if plugin.AllowDirect {
					ok = true
				}
			}
			if !ok {
				b.Log(Trace, fmt.Sprintf("Plugin %s ignoring message in channel %s, not in list", plugin.Name, channel))
				continue
			}
			var matchers []InputMatcher
			if isCommand {
				matchers = plugin.CommandMatches
			} else {
				matchers = plugin.MessageMatches
			}
			for _, matcher := range matchers {
				b.Log(Trace, fmt.Sprintf("Checking \"%s\" against \"%s\"", messagetext, matcher.Regex))
				matches := matcher.re.FindAllStringSubmatch(messagetext, -1)
				if matches != nil {
					b.Log(Debug, fmt.Sprintf("Dispatching command %s to plugin %s", matcher.Command, plugin.Name))
					switch plugin.PluginType {
					case "go":
						go goPluginHandlers[plugin.Name](bot, channel, user, matcher.Command, matches[0][1:]...)
						//case "external":
					case "external":
						var fullPath string // full path to the executable
						if len(plugin.PluginPath) == 0 {
							b.Log(Error, "PluginPath empty for external plugin:", plugin.Name)
						}
						if byte(plugin.PluginPath[0]) == byte("/"[0]) {
							fullPath = plugin.PluginPath
						} else {
							_, err := os.Stat(b.localPath + "/" + plugin.PluginPath)
							if err != nil {
								_, err := os.Stat(b.installPath + "/" + plugin.PluginPath)
								if err != nil {
									b.Log(Error, fmt.Errorf("Couldn't locate external plugin %s: %v", plugin.Name, err))
									continue
								}
								fullPath = b.installPath + "/" + plugin.PluginPath
								b.Log(Debug, "Using stock external plugin:", fullPath)
							} else {
								fullPath = b.localPath + "/" + plugin.PluginPath
								b.Log(Debug, "Using local external plugin:", fullPath)
							}
						}
						args := make([]string, 0, 3+len(matches[0])-1)
						args = append(args, channel, user, matcher.Command)
						args = append(args, matches[0][1:]...)
						b.Log(Trace, fmt.Sprintf("Calling \"%s\" with args: %q", fullPath, args))
						// cmd := exec.Command(fullPath, channel, user, matcher.Command, matches[0][1:]...)
						cmd := exec.Command(fullPath, args...)
						cmd.Stdout = nil
						stderr, err := cmd.StderrPipe()
						if err != nil {
							b.Log(Error, fmt.Errorf("Creating stderr pipe for external command \"%s\": %v", fullPath, err))
							continue
						}
						go func() {
							if err := cmd.Start(); err != nil {
								b.Log(Error, fmt.Errorf("Starting command \"%s\": %v", fullPath, err))
								return
							}
							defer func() {
								if err := cmd.Wait(); err != nil {
									b.Log(Error, fmt.Errorf("Waiting on external command \"%s\": %v", fullPath, err))
								}
							}()
							stdErrBytes, err := ioutil.ReadAll(stderr)
							if err != nil {
								b.Log(Error, fmt.Errorf("Reading from stderr for external command \"%s\": %v", fullPath, err))
								return
							}
							stdErrString := string(stdErrBytes)
							if len(stdErrString) > 0 {
								b.Log(Warn, fmt.Errorf("Output from stderr of external command \"%s\": %s", fullPath, stdErrString))
							}
						}()
					default:
						b.Log(Error, fmt.Sprintf("Invalid plugin type \"%s\" for plugin \"%s\"", plugin.PluginType, plugin.Name))
					}
				}
			}
		}
	}
	b.RUnlock()
}

// loadPluginConfig() loads the configuration for all the plugins from
// $GOPHER_LOCALDIR/plugins/<pluginname>.json
func (b *Bot) loadPluginConfig() error {
	i := 0

	// Copy some data from the bot under lock
	b.RLock()
	// Get a list of all plugins from the package goPluginHandlers var and
	// the list of external plugins
	nump := len(goPluginHandlers) + len(b.externalPlugins)
	pnames := make([]string, nump)

	for _, plug := range b.externalPlugins {
		pnames[i] = plug
		i++
	}
	pchan := make([]string, 0, len(b.channels))
	pchan = append(pchan, b.channels...)
	b.RUnlock()

	for plug, _ := range goPluginHandlers {
		pnames[i] = plug
		i++
	}
	plist := make([]Plugin, nump)

	i = 0
	for _, plug := range pnames {
		pc, err := b.getConfigFile("plugins/" + plug + ".json")
		if err != nil {
			return fmt.Errorf("Loading configuration for plugin %s: %v", plug, err)
		}
		var plugin Plugin
		if err := json.Unmarshal(pc, &plugin); err != nil {
			return fmt.Errorf("Unmarshalling JSON for plugin %s: %v", plug, err)
		}
		b.Log(Info, "Loaded configuration for plugin", plug)
		// Use bot default plugin channels if none defined
		if len(plugin.Channels) == 0 && len(pchan) > 0 {
			plugin.Channels = pchan
		}
		b.Log(Trace, fmt.Sprintf("Plugin %s will be active in channels %q", plug, plugin.Channels))
		// Compile the regex's
		for i, _ := range plugin.CommandMatches {
			command := &plugin.CommandMatches[i]
			re, err := regexp.Compile(`^\s*` + command.Regex + `\s*$`)
			if err != nil {
				return fmt.Errorf("Compiling command regular expression %s for plugin %s: %v", command.Regex, plug, err)
			}
			command.re = re
		}
		for i, _ := range plugin.MessageMatches {
			// Note that full message regexes don't get the beginning and end anchors added
			message := &plugin.CommandMatches[i]
			re, err := regexp.Compile(message.Regex)
			if err != nil {
				return fmt.Errorf("Compiling message regular expression %s for plugin %s: %v", message.Regex, plug, err)
			}
			message.re = re
		}
		plugin.Name = plug
		// Store this plugin's config in the temporary list
		plist[i] = plugin
		i++
	}

	b.Lock()
	b.plugins = plist
	b.Unlock()

	return nil
}
<commit_msg>Generate pluginID<commit_after>package bot

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"os"
	"os/exec"
	"regexp"
	"time"
)

// interface Gobot defines the API for plugins
type Gobot interface {
	Chatbot
	BotLogger
}

// Robot is passed to the plugin to enable convenience functions Say and Reply
type Robot struct {
	User     string // The user who sent the message; this can be modified for replying to an arbitrary user
	Channel  string // The channel where the message was received, or "" for a direct message. This can be modified to send a message to an arbitrary channel.
	Format   string // The outgoing message format, one of "fixed", "variable"
	pluginID string // Pass the ID in for later identificaton of the plugin
	Gobot
}

// TODO: implement Say and Reply convenience functions

// PluginHelp specifies keywords and help text for the 'bot help system
type PluginHelp struct {
	Keywords []string // match words for 'help XXX'
	Helptext []string // help string to give for the keywords, conventionally starting with (bot) for commands or (hear) when the bot needn't be addressed directly
}

// InputMatchers specify the command or message to match and what to pass to the plugin
type InputMatcher struct {
	Regex   string         // The regular expression string to match - bot adds ^\w* & \w*$
	Command string         // The name of the command to pass to the plugin with it's arguments
	re      *regexp.Regexp // The compiled regular expression. If the regex doesn't compile, the 'bot will log an error
}

// Plugin specifies the structure of a plugin configuration - plugins should include an example
type Plugin struct {
	Name           string          // the name of the plugin
	PluginType     string          // "go" or "external", determines how commands are interpreted
	PluginPath     string          // Path to the external executable that expects <channel> <user> <command> <arg> <arg> from regex matches - for Plugtype=shell only
	AllowDirect    bool            // Whether or not the plugin responds to direct messages
	Channels       []string        // Channels where the plugin is active - rifraf like "memes" should probably only be in random, but it's configurable. If empty uses DefaultChannels
	Help           []PluginHelp    // All the keyword sets / help texts for this plugin
	CommandMatches []InputMatcher  // Input matchers for messages that need to be directed to the 'bot
	MessageMatches []InputMatcher  // Input matchers for messages the 'bot hears even when it's not being spoken to
	Config         json.RawMessage // Plugin Configuration - the plugin needs to decode this
	pluginID       string          // 32-char random ID for identifying plugins in callbacks
}

// initialize sends the "start" command to every plugin
func (b *Bot) initializePlugins() {
	bot := Robot{
		User:    b.name,
		Channel: "",
		Format:  "variable",
		Gobot:   b,
	}
	for _, handler := range goPluginHandlers {
		go handler(bot, "", b.name, "start")
	}
}

// goPluginHandlers maps from plugin names to handler functions; populated during package initialization and never written to again.
var goPluginHandlers map[string]func(bot Robot, channel, user, command string, args ...string) error = make(map[string]func(bot Robot, channel, user, command string, args ...string) error)

// stopRegistrations is set "true" when the bot is created to prevent registration outside of init functions
var stopRegistrations bool = false

// RegisterPlugin allows plugins to register a handler function in a func init().
// When the bot initializes, it will call each plugin's handler with a command
// "start", empty channel, the bot's username, and no arguments, so the plugin
// can store this information for, e.g., scheduled jobs.
func RegisterPlugin(name string, handler func(bot Robot, channel, user, command string, args ...string) error) {
	if stopRegistrations {
		return
	}
	goPluginHandlers[name] = handler
}

// handle checks the message against plugin commands and full-message matches,
// then dispatches it to all applicable handlers.
func (b *Bot) handleMessage(isCommand bool, channel, user, messagetext string) {
	b.RLock()
	bot := Robot{
		User:    user,
		Channel: channel,
		Format:  "variable",
		Gobot:   b,
	}
	for _, plugin := range b.plugins {
		if len(plugin.Channels) > 0 {
			ok := false
			if len(channel) > 0 {
				for _, pchannel := range plugin.Channels {
					if pchannel == channel {
						ok = true
					}
				}
			} else {
				b.Log(Debug, fmt.Sprintf("Checking whether direct messages allowed for %s, AllowDirect is %b", plugin.Name, plugin.AllowDirect))
				if plugin.AllowDirect {
					ok = true
				}
			}
			if !ok {
				b.Log(Trace, fmt.Sprintf("Plugin %s ignoring message in channel %s, not in list", plugin.Name, channel))
				continue
			}
			var matchers []InputMatcher
			if isCommand {
				matchers = plugin.CommandMatches
			} else {
				matchers = plugin.MessageMatches
			}
			for _, matcher := range matchers {
				b.Log(Trace, fmt.Sprintf("Checking \"%s\" against \"%s\"", messagetext, matcher.Regex))
				matches := matcher.re.FindAllStringSubmatch(messagetext, -1)
				if matches != nil {
					b.Log(Debug, fmt.Sprintf("Dispatching command %s to plugin %s", matcher.Command, plugin.Name))
					switch plugin.PluginType {
					case "go":
						go goPluginHandlers[plugin.Name](bot, channel, user, matcher.Command, matches[0][1:]...)
						//case "external":
					case "external":
						var fullPath string // full path to the executable
						if len(plugin.PluginPath) == 0 {
							b.Log(Error, "PluginPath empty for external plugin:", plugin.Name)
						}
						if byte(plugin.PluginPath[0]) == byte("/"[0]) {
							fullPath = plugin.PluginPath
						} else {
							_, err := os.Stat(b.localPath + "/" + plugin.PluginPath)
							if err != nil {
								_, err := os.Stat(b.installPath + "/" + plugin.PluginPath)
								if err != nil {
									b.Log(Error, fmt.Errorf("Couldn't locate external plugin %s: %v", plugin.Name, err))
									continue
								}
								fullPath = b.installPath + "/" + plugin.PluginPath
								b.Log(Debug, "Using stock external plugin:", fullPath)
							} else {
								fullPath = b.localPath + "/" + plugin.PluginPath
								b.Log(Debug, "Using local external plugin:", fullPath)
							}
						}
						args := make([]string, 0, 3+len(matches[0])-1)
						args = append(args, channel, user, matcher.Command)
						args = append(args, matches[0][1:]...)
						b.Log(Trace, fmt.Sprintf("Calling \"%s\" with args: %q", fullPath, args))
						// cmd := exec.Command(fullPath, channel, user, matcher.Command, matches[0][1:]...)
						cmd := exec.Command(fullPath, args...)
						cmd.Stdout = nil
						stderr, err := cmd.StderrPipe()
						if err != nil {
							b.Log(Error, fmt.Errorf("Creating stderr pipe for external command \"%s\": %v", fullPath, err))
							continue
						}
						go func() {
							if err := cmd.Start(); err != nil {
								b.Log(Error, fmt.Errorf("Starting command \"%s\": %v", fullPath, err))
								return
							}
							defer func() {
								if err := cmd.Wait(); err != nil {
									b.Log(Error, fmt.Errorf("Waiting on external command \"%s\": %v", fullPath, err))
								}
							}()
							stdErrBytes, err := ioutil.ReadAll(stderr)
							if err != nil {
								b.Log(Error, fmt.Errorf("Reading from stderr for external command \"%s\": %v", fullPath, err))
								return
							}
							stdErrString := string(stdErrBytes)
							if len(stdErrString) > 0 {
								b.Log(Warn, fmt.Errorf("Output from stderr of external command \"%s\": %s", fullPath, stdErrString))
							}
						}()
					default:
						b.Log(Error, fmt.Sprintf("Invalid plugin type \"%s\" for plugin \"%s\"", plugin.PluginType, plugin.Name))
					}
				}
			}
		}
	}
	b.RUnlock()
}

// loadPluginConfig() loads the configuration for all the plugins from
// $GOPHER_LOCALDIR/plugins/<pluginname>.json
func (b *Bot) loadPluginConfig() error {
	i := 0

	// Seed the pseudo-random number generator, for plugin IDs
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	// Copy some data from the bot under lock
	b.RLock()
	// Get a list of all plugins from the package goPluginHandlers var and
	// the list of external plugins
	nump := len(goPluginHandlers) + len(b.externalPlugins)
	pnames := make([]string, nump)

	for _, plug := range b.externalPlugins {
		pnames[i] = plug
		i++
	}
	pchan := make([]string, 0, len(b.channels))
	pchan = append(pchan, b.channels...)
	b.RUnlock()

	for plug, _ := range goPluginHandlers {
		pnames[i] = plug
		i++
	}
	plist := make([]Plugin, nump)

	i = 0
	for _, plug := range pnames {
		pc, err := b.getConfigFile("plugins/" + plug + ".json")
		if err != nil {
			return fmt.Errorf("Loading configuration for plugin %s: %v", plug, err)
		}
		var plugin Plugin
		if err := json.Unmarshal(pc, &plugin); err != nil {
			return fmt.Errorf("Unmarshalling JSON for plugin %s: %v", plug, err)
		}
		b.Log(Info, "Loaded configuration for plugin", plug)
		// Use bot default plugin channels if none defined
		if len(plugin.Channels) == 0 && len(pchan) > 0 {
			plugin.Channels = pchan
		}
		b.Log(Trace, fmt.Sprintf("Plugin %s will be active in channels %q", plug, plugin.Channels))
		// Compile the regex's
		for i, _ := range plugin.CommandMatches {
			command := &plugin.CommandMatches[i]
			re, err := regexp.Compile(`^\s*` + command.Regex + `\s*$`)
			if err != nil {
				return fmt.Errorf("Compiling command regular expression %s for plugin %s: %v", command.Regex, plug, err)
			}
			command.re = re
		}
		for i, _ := range plugin.MessageMatches {
			// Note that full message regexes don't get the beginning and end anchors added
			message := &plugin.CommandMatches[i]
			re, err := regexp.Compile(message.Regex)
			if err != nil {
				return fmt.Errorf("Compiling message regular expression %s for plugin %s: %v", message.Regex, plug, err)
			}
			message.re = re
		}
		plugin.Name = plug
		// Generate the random id
		p := make([]byte, 16)
		_, rerr := r.Read(p)
		if rerr != nil {
			log.Fatal("Couldn't generate plugin id:", err)
		}
		plugin.pluginID = fmt.Sprintf("%x", p)
		// Store this plugin's config in the temporary list
		b.Log(Info, fmt.Sprintf("Recorded plugin %s with ID %s", plugin.Name, plugin.pluginID))
		plist[i] = plugin
		i++
	}

	b.Lock()
	b.plugins = plist
	b.Unlock()

	return nil
}
<|endoftext|>