<commit_before>package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func main() {
	start := time.Now()
	// 処理
	var i float64
	i = 0
	for {
		i++
		end := time.Now()
		diff := float64(end.Sub(start) / 1000 / 1000 / 1000)
		m := int(i / diff)
		out := 0
		if m < 0 {
		} else {
			out = m
		}
		fmt.Printf("\r")
		fmt.Printf("\r%v回試行  %v秒経過 %v回/秒", i, diff, out)
		if submain() {
			break
		}
	}
	fmt.Printf("\n")
}

func submain() bool {
	/*
		fmt.Print("started.")
		// チャネル
		sleep1_finished := make(chan bool)

		go func() {
			// 0.2秒かかるコマンド
			fmt.Print("sleep1 started.")
			time.Sleep(200 * 1000 * 1000 * time.Nanosecond)
			fmt.Print("sleep1 finished.")
			sleep1_finished <- true
		}()

		// 終わるまで待つ
		<- sleep1_finished
	*/

	// 136枚の中から14枚返したい
	list := shuffled_cards()

	// 出力
	result := solve(list)
	string_output(list)
	return result
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(list []int) {
	for i := len(list); i > 1; i-- {
		j := rand.Intn(i) // 0　.. i-1 の乱数発生
		list[i-1], list[j] = list[j], list[i-1]
	}
}

func shuffled_cards() []int {
	rand.Seed(time.Now().UnixNano())

	// データ要素数指定、および初期データ作成
	size := 136
	list := make([]int, size, size)
	for i := 0; i < size; i++ {
		list[i] = i / 4
	}

	// シャッフル
	shuffle(list)

	return list[:14]
}

// 牌の出力
func string_output(list []int) {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// 126976 is 'ton'
		// https://codepoints.net/U+1F000
		b = append(b, string(list[j]+126976)...) // ...が必要
		b = append(b, string(32)...)             // ...が必要
	}
	fmt.Printf("%v", string(b))
}

// solve
// 字マンソーピンのリストをつくる
func solve(list []int) bool {
	if is_chitoitsu(list) {
		return true
	}
	matrix := [][]int{{}, {}, {}, {}}
	for _, value := range list {
		group(matrix, value)
	}
	//fmt.Printf("%v", matrix)

	return group_scan(matrix)
}

// スート分類してくれる
func group(m [][]int, i int) {
	switch {
	case i < 7:
		m[0] = append(m[0], i)
	case i < 7+(9*1):
		m[1] = append(m[1], i-7)
	case i < 7+(9*2):
		m[2] = append(m[2], i-7-(9*1))
	case i < 7+(9*3):
		m[3] = append(m[3], i-7-(9*2))
	}
}

func group_scan(m [][]int) bool {
	if !valid_mod3(m) {
		return false
	}
	if !valid_33332(m) {
		return false
	}
	return true
}

func valid_mod3(m [][]int) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func valid_33332(m [][]int) bool {
	for i := 0; i < 4; i++ {
		if !valid_suit_group(m[i], i) {
			return false
		}
	}
	return true
}

func valid_suit_group(a []int, i int) bool {
	// 第二引数は字牌のとき0

	//ソート
	sort.Ints(a)
	if len(a)%3 == 2 {
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		if len(pair_numbers) == 0 {
			return false
		}
		//ペア候補毎に繰り返し処理
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			rest := []int{}
			c := 2
			for _, w := range a {
				// ペア候補以外は新スライスに入れる
				// ペア候補は３枚目以降は新スライスに入れる
				if w != v || c <= 0 {
					rest = append(rest, w)
				}
				if w == v {
					c--
				}
			}
			if valid_3cards(rest, i) {
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		return valid_3cards(a, i)
	}
	// 来ないはず
	return false
}

func valid_3cards(a []int, i int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	// 第二引数は字牌のとき0
	ok := false
	for {
		a, ok = remove_kotsu(a)
		if ok {
			continue
		}
		if i > 0 {
			a, ok = remove_shuntsu(a)
			if ok {
				continue
			}
		}
		return len(a) == 0
	}
}

func remove_kotsu(a []int) ([]int, bool) {
	// 刻子を除去できればtrue
	// a is sorted
	retval := a
	if len(a) < 3 {
		return retval, false
	}
	if a[0] == a[1] && a[0] == a[2] {
		retval = a[3:]
		return retval, true
	}
	return retval, false
}

func remove_shuntsu(a []int) ([]int, bool) {
	// 順子を除去できればtrue
	// a is sorted
	rest := []int{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	return rest, found
}

func pairable_numbers(sorted []int) []int {
	//カウンタ
	retval := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			retval = append(retval, v)
		} else {
			b = v
		}
	}
	return retval
}

func is_chitoitsu(list []int) bool {
	//カウンタ
	c := map[int]int{}

	//コピー
	l := list

	for _, v := range l {
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<commit_msg>到達しないはずの場所にpanic<commit_after>package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"
)

func main() {
	start := time.Now()
	// 処理
	var i float64
	i = 0
	for {
		i++
		end := time.Now()
		diff := float64(end.Sub(start) / 1000 / 1000 / 1000)
		m := int(i / diff)
		out := 0
		if m < 0 {
		} else {
			out = m
		}
		fmt.Printf("\r")
		fmt.Printf("\r%v回試行  %v秒経過 %v回/秒", i, diff, out)
		if submain() {
			break
		}
	}
	fmt.Printf("\n")
}

func submain() bool {
	/*
		fmt.Print("started.")
		// チャネル
		sleep1_finished := make(chan bool)

		go func() {
			// 0.2秒かかるコマンド
			fmt.Print("sleep1 started.")
			time.Sleep(200 * 1000 * 1000 * time.Nanosecond)
			fmt.Print("sleep1 finished.")
			sleep1_finished <- true
		}()

		// 終わるまで待つ
		<- sleep1_finished
	*/

	// 136枚の中から14枚返したい
	list := shuffled_cards()

	// 出力
	result := solve(list)
	string_output(list)
	return result
}

// http://d.hatena.ne.jp/hake/20150930/p1
func shuffle(list []int) {
	for i := len(list); i > 1; i-- {
		j := rand.Intn(i) // 0　.. i-1 の乱数発生
		list[i-1], list[j] = list[j], list[i-1]
	}
}

func shuffled_cards() []int {
	rand.Seed(time.Now().UnixNano())

	// データ要素数指定、および初期データ作成
	size := 136
	list := make([]int, size, size)
	for i := 0; i < size; i++ {
		list[i] = i / 4
	}

	// シャッフル
	shuffle(list)

	return list[:14]
}

// 牌の出力
func string_output(list []int) {
	// http://qiita.com/ruiu/items/2bb83b29baeae2433a79
	// サイズ0、内部バッファの長さ69の[]byteの値を割り当てる
	b := make([]byte, 0, 70)

	// bに文字列を追加
	for j := 0; j < 14; j++ {
		// 126976 is 'ton'
		// https://codepoints.net/U+1F000
		b = append(b, string(list[j]+126976)...) // ...が必要
		b = append(b, string(32)...)             // ...が必要
	}
	fmt.Printf("%v", string(b))
}

// solve
// 字マンソーピンのリストをつくる
func solve(list []int) bool {
	if is_chitoitsu(list) {
		return true
	}
	matrix := [][]int{{}, {}, {}, {}}
	for _, value := range list {
		group(matrix, value)
	}
	//fmt.Printf("%v", matrix)

	return group_scan(matrix)
}

// スート分類してくれる
func group(m [][]int, i int) {
	switch {
	case i < 7:
		m[0] = append(m[0], i)
	case i < 7+(9*1):
		m[1] = append(m[1], i-7)
	case i < 7+(9*2):
		m[2] = append(m[2], i-7-(9*1))
	case i < 7+(9*3):
		m[3] = append(m[3], i-7-(9*2))
	}
}

func group_scan(m [][]int) bool {
	if !valid_mod3(m) {
		return false
	}
	if !valid_33332(m) {
		return false
	}
	return true
}

func valid_mod3(m [][]int) bool {
	//スートのサイズを3で割った時
	//あまりが2であるすーとグループが1つであること
	c := 0
	for _, a := range m {
		switch len(a) % 3 {
		case 0:
			// noop
		case 1:
			return false
		case 2:
			c++
		}
	}
	return c == 1
}

func valid_33332(m [][]int) bool {
	for i := 0; i < 4; i++ {
		if !valid_suit_group(m[i], i) {
			return false
		}
	}
	return true
}

func valid_suit_group(a []int, i int) bool {
	// 第二引数は字牌のとき0

	//ソート
	sort.Ints(a)
	if len(a)%3 == 2 {
		//ペアを探す
		pair_numbers := pairable_numbers(a)
		//ペア候補がなかったらぬける
		if len(pair_numbers) == 0 {
			return false
		}
		//ペア候補毎に繰り返し処理
		for _, v := range pair_numbers {
			//ペアとなる２枚を除去
			rest := []int{}
			c := 2
			for _, w := range a {
				// ペア候補以外は新スライスに入れる
				// ペア候補は３枚目以降は新スライスに入れる
				if w != v || c <= 0 {
					rest = append(rest, w)
				}
				if w == v {
					c--
				}
			}
			if valid_3cards(rest, i) {
				return true
			} else {
				continue
			}
		}
		return false
	} else if len(a)%3 == 0 {
		return valid_3cards(a, i)
	}
	// 到達しないはず
	panic("到達しないはず")
}

func valid_3cards(a []int, i int) bool {
	// 刻子や順子のみで構成されている場合true
	// a is sorted
	// a.size % 3 is0
	// 第二引数は字牌のとき0
	ok := false
	for {
		a, ok = remove_kotsu(a)
		if ok {
			continue
		}
		if i > 0 {
			a, ok = remove_shuntsu(a)
			if ok {
				continue
			}
		}
		return len(a) == 0
	}
}

func remove_kotsu(a []int) ([]int, bool) {
	// 刻子を除去できればtrue
	// a is sorted
	retval := a
	if len(a) < 3 {
		return retval, false
	}
	if a[0] == a[1] && a[0] == a[2] {
		retval = a[3:]
		return retval, true
	}
	return retval, false
}

func remove_shuntsu(a []int) ([]int, bool) {
	// 順子を除去できればtrue
	// a is sorted
	rest := []int{}
	first := -1
	second := -1
	found := false
	for _, v := range a {
		if found {
			rest = append(rest, v)
			continue
		}
		if first == -1 {
			first = v
		} else if second == -1 && first+1 == v {
			second = v
		} else if second != -1 && first+2 == v {
			//flush
			first = -1
			second = -1
			found = true
		} else {
			rest = append(rest, v)
		}
	}
	return rest, found
}

func pairable_numbers(sorted []int) []int {
	//カウンタ
	retval := []int{}
	a := 999 // 2つ前
	b := 999 // 1つ前
	for _, v := range sorted {
		if b == v && a != v {
			retval = append(retval, v)
		} else {
			b = v
		}
	}
	return retval
}

func is_chitoitsu(list []int) bool {
	//カウンタ
	c := map[int]int{}

	//コピー
	l := list

	for _, v := range l {
		count, ok := c[v]
		if ok {
			if count == 1 {
				c[v] = 2
			} else {
				// c[v] == 2
				return false
			}
		} else {
			c[v] = 1
		}
		//8個チェック
		if len(c) >= 8 {
			return false
		}
	}
	return true
}
<|endoftext|>


<commit_before>package netutil

import (
	"fmt"
	"net"

	"encoding/binary"

	"sync"

	"time"

	"sync/atomic"

	"compress/flate"

	"os"

	"io"

	"github.com/pkg/errors"
	"github.com/xiaonanln/goNewlessPool"
	"github.com/xiaonanln/goworld/consts"
	"github.com/xiaonanln/goworld/gwlog"
	"github.com/xiaonanln/goworld/opmon"
)

const ( // Three different level of packet size
	PACKET_SIZE_SMALL = 1024
	PACKET_SIZE_BIG   = 1024 * 64
	PACKET_SIZE_HUGE  = 1024 * 1024 * 4
)

const (
	MAX_PACKET_SIZE    = 25 * 1024 * 1024
	SIZE_FIELD_SIZE    = 4
	PREPAYLOAD_SIZE    = SIZE_FIELD_SIZE
	MAX_PAYLOAD_LENGTH = MAX_PACKET_SIZE - PREPAYLOAD_SIZE
)

var (
	NETWORK_ENDIAN      = binary.LittleEndian
	errRecvAgain        = _ErrRecvAgain{}
	compressWritersPool = newless_pool.NewNewlessPool()
)

func init() {
	for i := 0; i < consts.COMPRESS_WRITER_POOL_SIZE; i++ {
		cw, err := flate.NewWriter(os.Stderr, flate.BestSpeed)
		if err != nil {
			gwlog.Fatal("create flate compressor failed: %v", err)
		}

		compressWritersPool.Put(cw)
	}

	gwlog.Info("%d compress writer created.", consts.COMPRESS_WRITER_POOL_SIZE)
}

type _ErrRecvAgain struct{}

func (err _ErrRecvAgain) Error() string {
	return "recv again"
}

func (err _ErrRecvAgain) Temporary() bool {
	return true
}

func (err _ErrRecvAgain) Timeout() bool {
	return false
}

type PacketConnection struct {
	conn               Connection
	compressed         bool
	pendingPackets     []*Packet
	pendingPacketsLock sync.Mutex
	sendBuffer         *SendBuffer // each PacketConnection uses 1 SendBuffer for sending packets

	// buffers and infos for receiving a packet
	payloadLenBuf         [SIZE_FIELD_SIZE]byte
	payloadLenBytesRecved int
	recvCompressed        bool
	recvTotalPayloadLen   uint32
	recvedPayloadLen      uint32
	recvingPacket         *Packet

	compressReader io.ReadCloser
}

func NewPacketConnection(conn Connection, compressed bool) *PacketConnection {
	pc := &PacketConnection{
		conn:       (conn),
		sendBuffer: NewSendBuffer(),
		compressed: compressed,
	}

	pc.compressReader = flate.NewReader(os.Stdin) // reader is always needed
	return pc
}

func (pc *PacketConnection) NewPacket() *Packet {
	return allocPacket()
}

func (pc *PacketConnection) SendPacket(packet *Packet) error {
	if consts.DEBUG_PACKETS {
		gwlog.Debug("%s SEND PACKET %p: msgtype=%v, payload(%d)=%v", pc, packet,
			PACKET_ENDIAN.Uint16(packet.bytes[PREPAYLOAD_SIZE:PREPAYLOAD_SIZE+2]),
			packet.GetPayloadLen(),
			packet.bytes[PREPAYLOAD_SIZE+2:PREPAYLOAD_SIZE+packet.GetPayloadLen()])
	}
	if atomic.LoadInt64(&packet.refcount) <= 0 {
		gwlog.Panicf("sending packet with refcount=%d", packet.refcount)
	}

	packet.AddRefCount(1)
	pc.pendingPacketsLock.Lock()
	pc.pendingPackets = append(pc.pendingPackets, packet)
	pc.pendingPacketsLock.Unlock()
	return nil
}

func (pc *PacketConnection) Flush() (err error) {
	pc.pendingPacketsLock.Lock()
	if len(pc.pendingPackets) == 0 { // no packets to send, common to happen, so handle efficiently
		pc.pendingPacketsLock.Unlock()
		return
	}
	packets := make([]*Packet, 0, len(pc.pendingPackets))
	packets, pc.pendingPackets = pc.pendingPackets, packets
	pc.pendingPacketsLock.Unlock()

	// flush should only be called in one goroutine
	op := opmon.StartOperation("FlushPackets")
	defer op.Finish(time.Millisecond * 100)

	var cw *flate.Writer
	if pc.compressed {
		_cw := compressWritersPool.TryGet() // try to get a usable compress writer, might fail
		if _cw != nil {
			cw = _cw.(*flate.Writer)
			defer compressWritersPool.Put(cw)
		} else {
			gwlog.Warn("Fail to get compressor, packet is not compressed")
		}
	}

	if len(packets) == 1 {
		// only 1 packet to send, just send it directly, no need to use send buffer
		packet := packets[0]
		if cw != nil {
			packet.compress(cw)
		}
		err = WriteAll(pc.conn, packet.data())
		packet.Release()
		if err == nil {
			err = pc.conn.Flush()
		}
		return
	}

	sendBuffer := pc.sendBuffer // the send buffer

send_packets_loop:
	for _, packet := range packets {
		if cw != nil {
			packet.compress(cw)
		}

		packetData := packet.data()
		if len(packetData) > sendBuffer.FreeSpace() {
			// can not append data to send buffer, so clear send buffer first
			if err = sendBuffer.WriteTo(pc.conn); err != nil {
				return err
			}

			if len(packetData) >= SEND_BUFFER_SIZE {
				// packet is too large, impossible to put to send buffer
				err = WriteAll(pc.conn, packetData)
				packet.Release()

				if err != nil {
					return
				}
				continue send_packets_loop
			}
		}

		// now we are sure that len(packetData) <= sendBuffer.FreeSize()
		n, _ := sendBuffer.Write(packetData)
		if n != len(packetData) {
			gwlog.Panicf("packet is not fully written")
		}
		packet.Release()
	}

	// now we send all data in the send buffer
	err = sendBuffer.WriteTo(pc.conn)
	if err == nil {
		err = pc.conn.Flush()
	}
	return
}

func (pc *PacketConnection) SetRecvDeadline(deadline time.Time) error {
	return pc.conn.SetReadDeadline(deadline)
}

func (pc *PacketConnection) RecvPacket() (*Packet, error) {
	if pc.payloadLenBytesRecved < SIZE_FIELD_SIZE {
		// receive more of payload len bytes
		n, err := pc.conn.Read(pc.payloadLenBuf[pc.payloadLenBytesRecved:])
		pc.payloadLenBytesRecved += n
		if pc.payloadLenBytesRecved < SIZE_FIELD_SIZE {
			if err == nil {
				err = errRecvAgain
			}
			return nil, err // packet not finished yet
		}

		pc.recvTotalPayloadLen = NETWORK_ENDIAN.Uint32(pc.payloadLenBuf[:])
		//pc.recvCompressed = false
		if pc.recvCompressed {
			gwlog.Panicf("should be false")
		}
		if pc.recvTotalPayloadLen&COMPRESSED_BIT_MASK != 0 {
			pc.recvTotalPayloadLen &= PAYLOAD_LEN_MASK
			pc.recvCompressed = true
		}

		if pc.recvTotalPayloadLen == 0 || pc.recvTotalPayloadLen > MAX_PAYLOAD_LENGTH {
			err := errors.Errorf("invalid payload length: %v", pc.recvTotalPayloadLen)
			pc.resetRecvStates()
			pc.Close()
			return nil, err
		}

		pc.recvedPayloadLen = 0
		pc.recvingPacket = NewPacket()
		pc.recvingPacket.assureCapacity(pc.recvTotalPayloadLen)
	}

	// now all bytes of payload len is received, start receiving payload
	n, err := pc.conn.Read(pc.recvingPacket.bytes[PREPAYLOAD_SIZE+pc.recvedPayloadLen : PREPAYLOAD_SIZE+pc.recvTotalPayloadLen])
	pc.recvedPayloadLen += uint32(n)

	if pc.recvedPayloadLen == pc.recvTotalPayloadLen {
		// full packet received, return the packet
		packet := pc.recvingPacket
		packet.setPayloadLenCompressed(pc.recvTotalPayloadLen, pc.recvCompressed)
		pc.resetRecvStates()
		packet.decompress(pc.compressReader)

		return packet, nil
	}

	if err == nil {
		err = errRecvAgain
	}
	return nil, err
}
func (pc *PacketConnection) resetRecvStates() {
	pc.payloadLenBytesRecved = 0
	pc.recvTotalPayloadLen = 0
	pc.recvedPayloadLen = 0
	pc.recvingPacket = nil
	pc.recvCompressed = false
}

func (pc *PacketConnection) Close() error {
	return pc.conn.Close()
}

func (pc *PacketConnection) RemoteAddr() net.Addr {
	return pc.conn.RemoteAddr()
}

func (pc *PacketConnection) LocalAddr() net.Addr {
	return pc.conn.LocalAddr()
}

func (pc *PacketConnection) String() string {
	return fmt.Sprintf("[%s >>> %s]", pc.LocalAddr(), pc.RemoteAddr())
}
<commit_msg>use new newless pool<commit_after>package netutil

import (
	"fmt"
	"net"

	"encoding/binary"

	"sync"

	"time"

	"sync/atomic"

	"compress/flate"

	"os"

	"io"

	"github.com/pkg/errors"
	"github.com/xiaonanln/go-xnsyncutil/xnsyncutil"
	"github.com/xiaonanln/goworld/consts"
	"github.com/xiaonanln/goworld/gwlog"
	"github.com/xiaonanln/goworld/opmon"
)

const ( // Three different level of packet size
	PACKET_SIZE_SMALL = 1024
	PACKET_SIZE_BIG   = 1024 * 64
	PACKET_SIZE_HUGE  = 1024 * 1024 * 4
)

const (
	MAX_PACKET_SIZE    = 25 * 1024 * 1024
	SIZE_FIELD_SIZE    = 4
	PREPAYLOAD_SIZE    = SIZE_FIELD_SIZE
	MAX_PAYLOAD_LENGTH = MAX_PACKET_SIZE - PREPAYLOAD_SIZE
)

var (
	NETWORK_ENDIAN      = binary.LittleEndian
	errRecvAgain        = _ErrRecvAgain{}
	compressWritersPool = xnsyncutil.NewNewlessPool()
)

func init() {
	for i := 0; i < consts.COMPRESS_WRITER_POOL_SIZE; i++ {
		cw, err := flate.NewWriter(os.Stderr, flate.BestSpeed)
		if err != nil {
			gwlog.Fatal("create flate compressor failed: %v", err)
		}

		compressWritersPool.Put(cw)
	}

	gwlog.Info("%d compress writer created.", consts.COMPRESS_WRITER_POOL_SIZE)
}

type _ErrRecvAgain struct{}

func (err _ErrRecvAgain) Error() string {
	return "recv again"
}

func (err _ErrRecvAgain) Temporary() bool {
	return true
}

func (err _ErrRecvAgain) Timeout() bool {
	return false
}

type PacketConnection struct {
	conn               Connection
	compressed         bool
	pendingPackets     []*Packet
	pendingPacketsLock sync.Mutex
	sendBuffer         *SendBuffer // each PacketConnection uses 1 SendBuffer for sending packets

	// buffers and infos for receiving a packet
	payloadLenBuf         [SIZE_FIELD_SIZE]byte
	payloadLenBytesRecved int
	recvCompressed        bool
	recvTotalPayloadLen   uint32
	recvedPayloadLen      uint32
	recvingPacket         *Packet

	compressReader io.ReadCloser
}

func NewPacketConnection(conn Connection, compressed bool) *PacketConnection {
	pc := &PacketConnection{
		conn:       (conn),
		sendBuffer: NewSendBuffer(),
		compressed: compressed,
	}

	pc.compressReader = flate.NewReader(os.Stdin) // reader is always needed
	return pc
}

func (pc *PacketConnection) NewPacket() *Packet {
	return allocPacket()
}

func (pc *PacketConnection) SendPacket(packet *Packet) error {
	if consts.DEBUG_PACKETS {
		gwlog.Debug("%s SEND PACKET %p: msgtype=%v, payload(%d)=%v", pc, packet,
			PACKET_ENDIAN.Uint16(packet.bytes[PREPAYLOAD_SIZE:PREPAYLOAD_SIZE+2]),
			packet.GetPayloadLen(),
			packet.bytes[PREPAYLOAD_SIZE+2:PREPAYLOAD_SIZE+packet.GetPayloadLen()])
	}
	if atomic.LoadInt64(&packet.refcount) <= 0 {
		gwlog.Panicf("sending packet with refcount=%d", packet.refcount)
	}

	packet.AddRefCount(1)
	pc.pendingPacketsLock.Lock()
	pc.pendingPackets = append(pc.pendingPackets, packet)
	pc.pendingPacketsLock.Unlock()
	return nil
}

func (pc *PacketConnection) Flush() (err error) {
	pc.pendingPacketsLock.Lock()
	if len(pc.pendingPackets) == 0 { // no packets to send, common to happen, so handle efficiently
		pc.pendingPacketsLock.Unlock()
		return
	}
	packets := make([]*Packet, 0, len(pc.pendingPackets))
	packets, pc.pendingPackets = pc.pendingPackets, packets
	pc.pendingPacketsLock.Unlock()

	// flush should only be called in one goroutine
	op := opmon.StartOperation("FlushPackets")
	defer op.Finish(time.Millisecond * 100)

	var cw *flate.Writer
	if pc.compressed {
		_cw := compressWritersPool.TryGet() // try to get a usable compress writer, might fail
		if _cw != nil {
			cw = _cw.(*flate.Writer)
			defer compressWritersPool.Put(cw)
		} else {
			gwlog.Warn("Fail to get compressor, packet is not compressed")
		}
	}

	if len(packets) == 1 {
		// only 1 packet to send, just send it directly, no need to use send buffer
		packet := packets[0]
		if cw != nil {
			packet.compress(cw)
		}
		err = WriteAll(pc.conn, packet.data())
		packet.Release()
		if err == nil {
			err = pc.conn.Flush()
		}
		return
	}

	sendBuffer := pc.sendBuffer // the send buffer

send_packets_loop:
	for _, packet := range packets {
		if cw != nil {
			packet.compress(cw)
		}

		packetData := packet.data()
		if len(packetData) > sendBuffer.FreeSpace() {
			// can not append data to send buffer, so clear send buffer first
			if err = sendBuffer.WriteTo(pc.conn); err != nil {
				return err
			}

			if len(packetData) >= SEND_BUFFER_SIZE {
				// packet is too large, impossible to put to send buffer
				err = WriteAll(pc.conn, packetData)
				packet.Release()

				if err != nil {
					return
				}
				continue send_packets_loop
			}
		}

		// now we are sure that len(packetData) <= sendBuffer.FreeSize()
		n, _ := sendBuffer.Write(packetData)
		if n != len(packetData) {
			gwlog.Panicf("packet is not fully written")
		}
		packet.Release()
	}

	// now we send all data in the send buffer
	err = sendBuffer.WriteTo(pc.conn)
	if err == nil {
		err = pc.conn.Flush()
	}
	return
}

func (pc *PacketConnection) SetRecvDeadline(deadline time.Time) error {
	return pc.conn.SetReadDeadline(deadline)
}

func (pc *PacketConnection) RecvPacket() (*Packet, error) {
	if pc.payloadLenBytesRecved < SIZE_FIELD_SIZE {
		// receive more of payload len bytes
		n, err := pc.conn.Read(pc.payloadLenBuf[pc.payloadLenBytesRecved:])
		pc.payloadLenBytesRecved += n
		if pc.payloadLenBytesRecved < SIZE_FIELD_SIZE {
			if err == nil {
				err = errRecvAgain
			}
			return nil, err // packet not finished yet
		}

		pc.recvTotalPayloadLen = NETWORK_ENDIAN.Uint32(pc.payloadLenBuf[:])
		//pc.recvCompressed = false
		if pc.recvCompressed {
			gwlog.Panicf("should be false")
		}
		if pc.recvTotalPayloadLen&COMPRESSED_BIT_MASK != 0 {
			pc.recvTotalPayloadLen &= PAYLOAD_LEN_MASK
			pc.recvCompressed = true
		}

		if pc.recvTotalPayloadLen == 0 || pc.recvTotalPayloadLen > MAX_PAYLOAD_LENGTH {
			err := errors.Errorf("invalid payload length: %v", pc.recvTotalPayloadLen)
			pc.resetRecvStates()
			pc.Close()
			return nil, err
		}

		pc.recvedPayloadLen = 0
		pc.recvingPacket = NewPacket()
		pc.recvingPacket.assureCapacity(pc.recvTotalPayloadLen)
	}

	// now all bytes of payload len is received, start receiving payload
	n, err := pc.conn.Read(pc.recvingPacket.bytes[PREPAYLOAD_SIZE+pc.recvedPayloadLen : PREPAYLOAD_SIZE+pc.recvTotalPayloadLen])
	pc.recvedPayloadLen += uint32(n)

	if pc.recvedPayloadLen == pc.recvTotalPayloadLen {
		// full packet received, return the packet
		packet := pc.recvingPacket
		packet.setPayloadLenCompressed(pc.recvTotalPayloadLen, pc.recvCompressed)
		pc.resetRecvStates()
		packet.decompress(pc.compressReader)

		return packet, nil
	}

	if err == nil {
		err = errRecvAgain
	}
	return nil, err
}
func (pc *PacketConnection) resetRecvStates() {
	pc.payloadLenBytesRecved = 0
	pc.recvTotalPayloadLen = 0
	pc.recvedPayloadLen = 0
	pc.recvingPacket = nil
	pc.recvCompressed = false
}

func (pc *PacketConnection) Close() error {
	return pc.conn.Close()
}

func (pc *PacketConnection) RemoteAddr() net.Addr {
	return pc.conn.RemoteAddr()
}

func (pc *PacketConnection) LocalAddr() net.Addr {
	return pc.conn.LocalAddr()
}

func (pc *PacketConnection) String() string {
	return fmt.Sprintf("[%s >>> %s]", pc.LocalAddr(), pc.RemoteAddr())
}
<|endoftext|>


<commit_before>package plugin

import (
	. "github.com/mickael-kerjean/filestash/server/common"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_admin"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_ldap"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_passthrough"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_saml"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dropbox"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ftp"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_gdrive"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_git"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ldap"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_local"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nop"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_s3"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_samba"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_sftp"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_webdav"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_onlyoffice"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_syncthing"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_light"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_search_stateless"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_scanner"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tor"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
)

func init() {
	Log.Debug("Plugin loader")
}
<commit_msg>plugin (add): add temporary file backend to default list<commit_after>package plugin

import (
	. "github.com/mickael-kerjean/filestash/server/common"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_admin"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_ldap"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_passthrough"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_saml"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dropbox"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ftp"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_gdrive"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_git"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_ldap"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_local"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_mysql"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_nop"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_s3"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_samba"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_sftp"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_tmp"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_webdav"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_editor_onlyoffice"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_console"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_handler_syncthing"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_image_light"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_search_stateless"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_scanner"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_security_svg"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_http"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_starter_tor"
	_ "github.com/mickael-kerjean/filestash/server/plugin/plg_video_transcoder"
)

func init() {
	Log.Debug("Plugin loader")
}
<|endoftext|>


<commit_before>// Copyright (C) 2018 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Service js provides JavaScript scripting for the sarif network.
package js

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/sarifsystems/sarif/pkg/content"
	"github.com/sarifsystems/sarif/pkg/schema"
	"github.com/sarifsystems/sarif/sarif"
	"github.com/sarifsystems/sarif/services"
)

var Module = &services.Module{
	Name:        "js",
	Version:     "1.0",
	NewInstance: NewService,
}

type Config struct {
	ScriptDir string `json:"script_dir"`
}

type Dependencies struct {
	Config services.Config
	Client *sarif.Client
	Broker *sarif.Broker
}

type Service struct {
	cfg    Config
	Broker *sarif.Broker
	*sarif.Client

	Scripts   map[string]string
	Machines  map[string]*Machine
	Listeners map[string][]string
}

func NewService(deps *Dependencies) *Service {
	s := &Service{
		Broker:    deps.Broker,
		Client:    deps.Client,
		Scripts:   make(map[string]string),
		Machines:  make(map[string]*Machine),
		Listeners: make(map[string][]string),
	}
	s.cfg.ScriptDir = deps.Config.Dir() + "/js"
	deps.Config.Get(&s.cfg)

	s.createMachine("default")
	return s
}

func (s *Service) Enable() error {
	s.Subscribe("js/do", "", s.handleDo)
	s.Subscribe("js/start", "", s.handleStart)
	s.Subscribe("js/stop", "", s.handleStop)
	s.Subscribe("js/status", "", s.handleStatus)
	s.Subscribe("js/put", "", s.handlePut)
	s.Subscribe("js/get", "", s.handleGet)
	s.Subscribe("js/attach", "", s.handleAttach)

	if err := s.readAvailableScripts(); err != nil {
		return err
	}
	for _, f := range s.Scripts {
		s.createMachineFromScript(f)
	}
	return nil
}

func (s *Service) readAvailableScripts() error {
	if s.cfg.ScriptDir == "" {
		return nil
	}

	dir, err := os.Open(s.cfg.ScriptDir)
	if err != nil {
		if os.IsNotExist(err) {
			s.Log("warn", err.Error())
			return nil
		}
		return err
	}

	files, err := dir.Readdirnames(0)
	if err != nil {
		return err
	}

	for _, f := range files {
		if !strings.HasSuffix(f, ".js") {
			continue
		}
		name := strings.TrimSuffix(f, ".js")
		s.Scripts[name] = f
	}
	return nil
}

func readSource(filename string) ([]byte, error) {
	if filename == "" || filename == "-" {
		return ioutil.ReadAll(os.Stdin)
	}
	return ioutil.ReadFile(filename)
}

func (s *Service) createMachineFromScript(f string) (*Machine, error) {
	s.Log("info", "loading "+f)
	m, err := s.createMachine(strings.TrimSuffix(f, ".js"))
	if err != nil {
		return m, err
	}

	fname := s.cfg.ScriptDir + "/" + f
	_, err = m.Modules.Require(fname, s.cfg.ScriptDir)
	return m, err
}

func (s *Service) createMachine(name string) (*Machine, error) {
	if name == "" {
		name = sarif.GenerateId()
	}
	if _, ok := s.Machines[name]; ok {
		return nil, errors.New("Machine " + name + " already exists")
	}

	c := sarif.NewClient(s.DeviceId + "/" + name)
	c.Connect(s.Broker.NewLocalConn())

	m := NewMachine(c)
	m.Modules.AddPath(s.cfg.ScriptDir)
	s.Machines[name] = m
	if listeners, ok := s.Listeners[name]; ok {
		for _, l := range listeners {
			m.Attach(l)
		}
	}
	if err := m.Enable(); err != nil {
		return m, err
	}
	return m, nil
}

func (s *Service) destroyMachine(name string) error {
	m, ok := s.Machines[name]
	if !ok {
		return errors.New("Machine " + name + " does not exist")
	}
	delete(s.Machines, name)
	return m.Disable()
}

func (s *Service) getOrCreateMachine(name string) (*Machine, error) {
	if m, ok := s.Machines[name]; ok {
		return m, nil
	}
	return s.createMachine(name)
}

func (s *Service) handleDo(msg sarif.Message) {
	machine := strings.TrimLeft(strings.TrimPrefix(msg.Action, "js/do"), "/")
	if machine == "" {
		machine = "default"
	}
	m, err := s.getOrCreateMachine(machine)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}
	out, err, p := m.Do(msg.Text)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	reply := sarif.CreateMessage("js/done", p)
	reply.Text = out
	s.Reply(msg, reply)
}

type MsgMachineStatus struct {
	Machine string `json:"machine,omitempty"`
	Status  string `json:"status,omitempty"`
	Out     string `json:"out,omitempty"`
}

func (p MsgMachineStatus) String() string {
	s := "Machine " + p.Machine + " is " + p.Status + "."
	if p.Out != "" {
		s += "\n\n" + p.Out
	}
	return s
}

func (s *Service) handleStart(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/start"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	m, err := s.createMachineFromScript(name)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	s.Reply(msg, sarif.CreateMessage("js/started", &MsgMachineStatus{
		name,
		"up",
		m.FlushOut(),
	}))
}

func (s *Service) handleStop(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/stop"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	if err := s.destroyMachine(name); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	s.Reply(msg, sarif.CreateMessage("js/stopped", &MsgMachineStatus{
		name,
		"down",
		"",
	}))
}

type MsgMachineAllStatus struct {
	Up     int               `json:"up"`
	Status map[string]string `json:"status"`
}

func (s MsgMachineAllStatus) Text() string {
	return fmt.Sprintf("%d/%d machines running.", s.Up, len(s.Status))
}

func (s *Service) handleStatus(msg sarif.Message) {
	name := msg.ActionSuffix("js/status")

	if name != "" {
		status := "not_found"
		if _, ok := s.Machines[name]; ok {
			status = "up"
		} else if _, ok := s.Scripts[name]; ok {
			status = "down"
		}
		s.Reply(msg, sarif.CreateMessage("js/status", &MsgMachineStatus{
			name,
			status,
			"",
		}))
		return
	}

	status := MsgMachineAllStatus{
		Status: make(map[string]string),
	}
	for name := range s.Scripts {
		if _, ok := s.Machines[name]; ok {
			status.Up++
			status.Status[name] = "up"
		} else {
			status.Status[name] = "down"
		}
	}
	s.Reply(msg, sarif.CreateMessage("js/status", status))
}

type ContentPayload struct {
	Content schema.Content `json:"content"`
}

func (p ContentPayload) Text() string {
	return "This message contains content."
}

func (s *Service) handlePut(msg sarif.Message) {
	gen := false
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/put"), "/")
	if name == "" {
		name, gen = sarif.GenerateId(), true
	}
	if _, ok := s.Machines[name]; ok {
		s.destroyMachine(name)
	}

	m, err := s.createMachine(name)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	var ctp ContentPayload
	if err := msg.DecodePayload(&ctp); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}
	text := msg.Text
	if ctp.Content.Url != "" {
		ct, err := content.Get(ctp.Content)
		if err != nil {
			s.ReplyBadRequest(msg, err)
		}
		text = string(ct.Data)
	}

	out, err, _ := m.Do(text)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		s.destroyMachine(name)
		return
	}

	if !gen {
		f, err := os.Create(s.cfg.ScriptDir + "/" + name + ".js")
		if err == nil {
			_, err = f.Write([]byte(text))
			defer f.Close()
		}
		if err != nil {
			s.ReplyInternalError(msg, err)
			s.destroyMachine(name)
			return
		}
	}

	s.Reply(msg, sarif.CreateMessage("js/status", &MsgMachineStatus{
		name,
		"up",
		out,
	}))
}

func (s *Service) handleGet(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/get"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	f, err := os.Open(s.cfg.ScriptDir + "/" + name + ".js")
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}
	defer f.Close()
	src, err := ioutil.ReadAll(f)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	ct := content.PutData([]byte(src))
	ct.PutAction = "js/put/" + name
	ct.Name = name + ".js"
	s.Reply(msg, sarif.CreateMessage("js/script", ContentPayload{ct}))
}

func (s *Service) handleAttach(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/attach"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	if m, ok := s.Machines[name]; ok {
		m.Attach(msg.Source)
	}

	s.Listeners[name] = append(s.Listeners[name], msg.Source)
	s.Reply(msg, sarif.CreateMessage("js/attached", nil))
}
<commit_msg>JS: Different module path.<commit_after>// Copyright (C) 2018 Constantin Schomburg <me@cschomburg.com>
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

// Service js provides JavaScript scripting for the sarif network.
package js

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/sarifsystems/sarif/pkg/content"
	"github.com/sarifsystems/sarif/pkg/schema"
	"github.com/sarifsystems/sarif/sarif"
	"github.com/sarifsystems/sarif/services"
)

var Module = &services.Module{
	Name:        "js",
	Version:     "1.0",
	NewInstance: NewService,
}

type Config struct {
	ScriptDir string `json:"script_dir"`
}

type Dependencies struct {
	Config services.Config
	Client *sarif.Client
	Broker *sarif.Broker
}

type Service struct {
	cfg    Config
	Broker *sarif.Broker
	*sarif.Client

	Scripts   map[string]string
	Machines  map[string]*Machine
	Listeners map[string][]string
}

func NewService(deps *Dependencies) *Service {
	s := &Service{
		Broker:    deps.Broker,
		Client:    deps.Client,
		Scripts:   make(map[string]string),
		Machines:  make(map[string]*Machine),
		Listeners: make(map[string][]string),
	}
	s.cfg.ScriptDir = deps.Config.Dir() + "/js"
	deps.Config.Get(&s.cfg)

	s.createMachine("default")
	return s
}

func (s *Service) Enable() error {
	s.Subscribe("js/do", "", s.handleDo)
	s.Subscribe("js/start", "", s.handleStart)
	s.Subscribe("js/stop", "", s.handleStop)
	s.Subscribe("js/status", "", s.handleStatus)
	s.Subscribe("js/put", "", s.handlePut)
	s.Subscribe("js/get", "", s.handleGet)
	s.Subscribe("js/attach", "", s.handleAttach)

	if err := s.readAvailableScripts(); err != nil {
		return err
	}
	for _, f := range s.Scripts {
		s.createMachineFromScript(f)
	}
	return nil
}

func (s *Service) readAvailableScripts() error {
	if s.cfg.ScriptDir == "" {
		return nil
	}

	dir, err := os.Open(s.cfg.ScriptDir)
	if err != nil {
		if os.IsNotExist(err) {
			s.Log("warn", err.Error())
			return nil
		}
		return err
	}

	files, err := dir.Readdirnames(0)
	if err != nil {
		return err
	}

	for _, f := range files {
		if !strings.HasSuffix(f, ".js") {
			continue
		}
		name := strings.TrimSuffix(f, ".js")
		s.Scripts[name] = f
	}
	return nil
}

func readSource(filename string) ([]byte, error) {
	if filename == "" || filename == "-" {
		return ioutil.ReadAll(os.Stdin)
	}
	return ioutil.ReadFile(filename)
}

func (s *Service) createMachineFromScript(f string) (*Machine, error) {
	s.Log("info", "loading "+f)
	m, err := s.createMachine(strings.TrimSuffix(f, ".js"))
	if err != nil {
		return m, err
	}

	fname := s.cfg.ScriptDir + "/" + f
	_, err = m.Modules.Require(fname, s.cfg.ScriptDir)
	return m, err
}

func (s *Service) createMachine(name string) (*Machine, error) {
	if name == "" {
		name = sarif.GenerateId()
	}
	if _, ok := s.Machines[name]; ok {
		return nil, errors.New("Machine " + name + " already exists")
	}

	c := sarif.NewClient(s.DeviceId + "/" + name)
	c.Connect(s.Broker.NewLocalConn())

	m := NewMachine(c)
	m.Modules.AddPath(s.cfg.ScriptDir + "/node_modules")
	m.Modules.AddPath(s.cfg.ScriptDir + "/modules")
	s.Machines[name] = m
	if listeners, ok := s.Listeners[name]; ok {
		for _, l := range listeners {
			m.Attach(l)
		}
	}
	if err := m.Enable(); err != nil {
		return m, err
	}
	return m, nil
}

func (s *Service) destroyMachine(name string) error {
	m, ok := s.Machines[name]
	if !ok {
		return errors.New("Machine " + name + " does not exist")
	}
	delete(s.Machines, name)
	return m.Disable()
}

func (s *Service) getOrCreateMachine(name string) (*Machine, error) {
	if m, ok := s.Machines[name]; ok {
		return m, nil
	}
	return s.createMachine(name)
}

func (s *Service) handleDo(msg sarif.Message) {
	machine := strings.TrimLeft(strings.TrimPrefix(msg.Action, "js/do"), "/")
	if machine == "" {
		machine = "default"
	}
	m, err := s.getOrCreateMachine(machine)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}
	out, err, p := m.Do(msg.Text)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	reply := sarif.CreateMessage("js/done", p)
	reply.Text = out
	s.Reply(msg, reply)
}

type MsgMachineStatus struct {
	Machine string `json:"machine,omitempty"`
	Status  string `json:"status,omitempty"`
	Out     string `json:"out,omitempty"`
}

func (p MsgMachineStatus) String() string {
	s := "Machine " + p.Machine + " is " + p.Status + "."
	if p.Out != "" {
		s += "\n\n" + p.Out
	}
	return s
}

func (s *Service) handleStart(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/start"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	m, err := s.createMachineFromScript(name)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	s.Reply(msg, sarif.CreateMessage("js/started", &MsgMachineStatus{
		name,
		"up",
		m.FlushOut(),
	}))
}

func (s *Service) handleStop(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/stop"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	if err := s.destroyMachine(name); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}

	s.Reply(msg, sarif.CreateMessage("js/stopped", &MsgMachineStatus{
		name,
		"down",
		"",
	}))
}

type MsgMachineAllStatus struct {
	Up     int               `json:"up"`
	Status map[string]string `json:"status"`
}

func (s MsgMachineAllStatus) Text() string {
	return fmt.Sprintf("%d/%d machines running.", s.Up, len(s.Status))
}

func (s *Service) handleStatus(msg sarif.Message) {
	name := msg.ActionSuffix("js/status")

	if name != "" {
		status := "not_found"
		if _, ok := s.Machines[name]; ok {
			status = "up"
		} else if _, ok := s.Scripts[name]; ok {
			status = "down"
		}
		s.Reply(msg, sarif.CreateMessage("js/status", &MsgMachineStatus{
			name,
			status,
			"",
		}))
		return
	}

	status := MsgMachineAllStatus{
		Status: make(map[string]string),
	}
	for name := range s.Scripts {
		if _, ok := s.Machines[name]; ok {
			status.Up++
			status.Status[name] = "up"
		} else {
			status.Status[name] = "down"
		}
	}
	s.Reply(msg, sarif.CreateMessage("js/status", status))
}

type ContentPayload struct {
	Content schema.Content `json:"content"`
}

func (p ContentPayload) Text() string {
	return "This message contains content."
}

func (s *Service) handlePut(msg sarif.Message) {
	gen := false
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/put"), "/")
	if name == "" {
		name, gen = sarif.GenerateId(), true
	}
	if _, ok := s.Machines[name]; ok {
		s.destroyMachine(name)
	}

	m, err := s.createMachine(name)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	var ctp ContentPayload
	if err := msg.DecodePayload(&ctp); err != nil {
		s.ReplyBadRequest(msg, err)
		return
	}
	text := msg.Text
	if ctp.Content.Url != "" {
		ct, err := content.Get(ctp.Content)
		if err != nil {
			s.ReplyBadRequest(msg, err)
		}
		text = string(ct.Data)
	}

	out, err, _ := m.Do(text)
	if err != nil {
		s.ReplyBadRequest(msg, err)
		s.destroyMachine(name)
		return
	}

	if !gen {
		f, err := os.Create(s.cfg.ScriptDir + "/" + name + ".js")
		if err == nil {
			_, err = f.Write([]byte(text))
			defer f.Close()
		}
		if err != nil {
			s.ReplyInternalError(msg, err)
			s.destroyMachine(name)
			return
		}
	}

	s.Reply(msg, sarif.CreateMessage("js/status", &MsgMachineStatus{
		name,
		"up",
		out,
	}))
}

func (s *Service) handleGet(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/get"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	f, err := os.Open(s.cfg.ScriptDir + "/" + name + ".js")
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}
	defer f.Close()
	src, err := ioutil.ReadAll(f)
	if err != nil {
		s.ReplyInternalError(msg, err)
		return
	}

	ct := content.PutData([]byte(src))
	ct.PutAction = "js/put/" + name
	ct.Name = name + ".js"
	s.Reply(msg, sarif.CreateMessage("js/script", ContentPayload{ct}))
}

func (s *Service) handleAttach(msg sarif.Message) {
	name := strings.TrimPrefix(strings.TrimPrefix(msg.Action, "js/attach"), "/")
	if name == "" {
		s.ReplyBadRequest(msg, errors.New("No machine name given!"))
		return
	}

	if m, ok := s.Machines[name]; ok {
		m.Attach(msg.Source)
	}

	s.Listeners[name] = append(s.Listeners[name], msg.Source)
	s.Reply(msg, sarif.CreateMessage("js/attached", nil))
}
<|endoftext|>


<commit_before>package kubernetes

import (
	"time"

	"github.com/weaveworks/scope/report"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/labels"
)

// These constants are keys used in node metadata
const (
	ServiceID      = "kubernetes_service_id"
	ServiceName    = "kubernetes_service_name"
	ServiceCreated = "kubernetes_service_created"
)

// Service represents a Kubernetes service
type Service interface {
	ID() string
	Name() string
	Namespace() string
	GetNode() report.Node
	Selector() labels.Selector
}

type service struct {
	*api.Service
}

// NewService creates a new Service
func NewService(s *api.Service) Service {
	return &service{Service: s}
}

func (s *service) ID() string {
	return s.ObjectMeta.Namespace + "/" + s.ObjectMeta.Name
}

func (s *service) Name() string {
	return s.ObjectMeta.Name
}

func (s *service) Namespace() string {
	return s.ObjectMeta.Namespace
}

func (s *service) Selector() labels.Selector {
	return labels.SelectorFromSet(labels.Set(s.Spec.Selector))
}

func (s *service) GetNode() report.Node {
	return report.MakeNodeWith(map[string]string{
		ServiceID:      s.ID(),
		ServiceName:    s.Name(),
		ServiceCreated: s.ObjectMeta.CreationTimestamp.Format(time.RFC822),
		Namespace:      s.Namespace(),
	})
}
<commit_msg>nil selector for a k8s service means nothing, not everything<commit_after>package kubernetes

import (
	"time"

	"github.com/weaveworks/scope/report"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/labels"
)

// These constants are keys used in node metadata
const (
	ServiceID      = "kubernetes_service_id"
	ServiceName    = "kubernetes_service_name"
	ServiceCreated = "kubernetes_service_created"
)

// Service represents a Kubernetes service
type Service interface {
	ID() string
	Name() string
	Namespace() string
	GetNode() report.Node
	Selector() labels.Selector
}

type service struct {
	*api.Service
}

// NewService creates a new Service
func NewService(s *api.Service) Service {
	return &service{Service: s}
}

func (s *service) ID() string {
	return s.ObjectMeta.Namespace + "/" + s.ObjectMeta.Name
}

func (s *service) Name() string {
	return s.ObjectMeta.Name
}

func (s *service) Namespace() string {
	return s.ObjectMeta.Namespace
}

func (s *service) Selector() labels.Selector {
	if s.Spec.Selector == nil {
		return labels.Nothing()
	}
	return labels.SelectorFromSet(labels.Set(s.Spec.Selector))
}

func (s *service) GetNode() report.Node {
	return report.MakeNodeWith(map[string]string{
		ServiceID:      s.ID(),
		ServiceName:    s.Name(),
		ServiceCreated: s.ObjectMeta.CreationTimestamp.Format(time.RFC822),
		Namespace:      s.Namespace(),
	})
}
<|endoftext|>


<commit_before>package keys

type ParsedInternalKey struct {
	UserKey  []byte
	Kind     Kind
	Sequence Sequence
}

func (k *ParsedInternalKey) Append(dst []byte) []byte {
	var buf [TagBytes]byte
	CombineTag(buf[:], k.Sequence, k.Kind)
	dst = append(dst, k.UserKey...)
	return append(dst, buf[:]...)
}

func (k *ParsedInternalKey) Parse(key []byte) bool {
	i := len(key) - TagBytes
	if i < 0 {
		return false
	}
	k.UserKey = key[:i:i]
	k.Sequence, k.Kind = ExtractTag(key[i:])
	return k.Kind <= maxKind
}

func (k *ParsedInternalKey) Tag() Tag {
	return PackTag(k.Sequence, k.Kind)
}
<commit_msg>Document keys.ParsedInternalKey<commit_after>package keys

// ParsedInternalKey is a parsed or splited internal representation.
type ParsedInternalKey struct {
	UserKey  []byte
	Kind     Kind
	Sequence Sequence
}

// Append appends this internal key to destination buffer.
func (k *ParsedInternalKey) Append(dst []byte) []byte {
	var buf [TagBytes]byte
	CombineTag(buf[:], k.Sequence, k.Kind)
	dst = append(dst, k.UserKey...)
	return append(dst, buf[:]...)
}

// Parse parses input key as internal key and returns true for valid internal
// key. It is illegal to access other fields and methods after returning false.
func (k *ParsedInternalKey) Parse(key []byte) bool {
	i := len(key) - TagBytes
	if i < 0 {
		return false
	}
	k.UserKey = key[:i:i]
	k.Sequence, k.Kind = ExtractTag(key[i:])
	return k.Kind <= maxKind
}

// Tag returns tag of this internal key.
func (k *ParsedInternalKey) Tag() Tag {
	return PackTag(k.Sequence, k.Kind)
}
<|endoftext|>


<commit_before><commit_msg>Return from func instead of exit 0 on EOF in cat<commit_after><|endoftext|>


<commit_before><commit_msg>add API listening log info<commit_after><|endoftext|>


<commit_before><commit_msg>reverted back to log using fields instead of proto marshaller<commit_after><|endoftext|>


<commit_before><commit_msg>add NewUnsupportedFormatErr so an UnsupportedFormatErr can be created by lib users<commit_after><|endoftext|>


<commit_before>// Copyright 2016 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package command

import (
	"fmt"
	"log"
	"math"
	"os"
	"runtime/debug"
	"strings"
	"time"

	flag "github.com/spf13/pflag"

	"github.com/Netflix/chaosmonkey"
	"github.com/Netflix/chaosmonkey/clock"
	"github.com/Netflix/chaosmonkey/config"
	"github.com/Netflix/chaosmonkey/config/param"
	"github.com/Netflix/chaosmonkey/deps"
	"github.com/Netflix/chaosmonkey/mysql"
	"github.com/Netflix/chaosmonkey/schedstore"
	"github.com/Netflix/chaosmonkey/schedule"
	"github.com/Netflix/chaosmonkey/spinnaker"
)

// Version is the version number
const Version = "2.0.0"

func printVersion() {
	fmt.Printf("%s\n", Version)
}

var (
	// configPaths is where Chaos Monkey will look for a chaosmonkey.toml
	// configuration file
	configPaths = [...]string{".", "/apps/chaosmonkey", "/etc", "/etc/chaosmonkey"}
)

// Usage prints usage
func Usage() {
	usage := `
Chaos Monkey

Usage:
	chaosmonkey <command> ...

command: migrate | schedule | terminate | fetch-schedule | outage | config  | email | eligible | intest

migrate
-------
Applies database migration to the database defined in the configuraton file.

schedule [--max-apps=<N>] [--apps=foo,bar,baz] [--no-record-schedule]
--------------------------------------------------------------------
Generates a schedule of terminations for the day and installs the
terminations as local cron jobs that call "chaosmonkey terminate ..."

--apps=foo,bar,baz     Optionally specify an explicit list of apps to schedule.
                       This is primarily used for debugging.

--max-apps=<N>         Optionally specify the maximum number of apps that Chaos Monkey
					   will schedule. This is primarily used for debugging.

--no-record-schedule   Do not record the schedule with the database.
                       This is primarily used for debugging.

terminate <app> <account> [--region=<region>] [--stack=<stack>] [--cluster=<cluster>] [--leashed]
-----------------------------------------------------------------------------------------------------------------
Terminates an instance from a given app and account.

Optionally specify a region, stack, cluster.

The --leashed flag forces chaosmonkey to run in leashed mode. When leashed,
Chaos Monkey will check if an instance should be terminated, but will not
actually terminate it.

fetch-schedule
--------------
Queries the database to see if there is an existing schedule of
terminations for today. If so, downloads the schedule and sets up cron jobs to
implement the schedule.

outage
------
Output "true" if there is an ongoing outage, otherwise "false". Used for debugging.

config [<app>]
------------
Query Spinnaker for the config for a specific app and dump it to
standard out. This is only used for debugging.

If no app is specified, dump the Monkey-level configuration options to standard out.

Examples:

	chaosmonkey config chaosguineapig

	chaosmonkey config

eligible <app> <account> [--region=<region>] [--stack=<stack>] [--cluster=<cluster>]
-------------------------------------------------------------------------------------

Dump a list of instance-ids that are eligible for termination for a given app, account,
and optionally region, stack, and cluster.

intest
------

Outputs "true" on standard out if running within a test environment, otherwise outputs "false"

account <name>
--------------

Look up an cloud account ID by name.

Example:

	chaosmonkey account test

provider <name>
---------------

Look up the cloud provider by account name.

Example:

	chaosmonkey provider test

`
	fmt.Printf(usage)
}

func init() {
	// Prepend the pid to log statements
	log.SetPrefix(fmt.Sprintf("[%5d] ", os.Getpid()))
}

// Execute is the main entry point for the chaosmonkey cli.
func Execute() {
	regionPtr := flag.String("region", "", "region of termination group")
	stackPtr := flag.String("stack", "", "stack of termination group")
	clusterPtr := flag.String("cluster", "", "cluster of termination group")
	appsPtr := flag.String("apps", "", "comma-separated list of apps to schedule for termination")
	noRecordSchedulePtr := flag.Bool("no-record-schedule", false, "do not record schedule")
	versionPtr := flag.BoolP("version", "v", false, "show version")
	flag.Usage = Usage

	// These flags, if specified, override config values
	maxAppsFlag := "max-apps"
	leashedFlag := "leashed"
	flag.Int(maxAppsFlag, math.MaxInt32, "max number of apps to examine for termination")
	flag.Bool(leashedFlag, false, "force leashed mode")

	flag.Parse()
	if len(flag.Args()) == 0 {
		if *versionPtr {
			printVersion()
			os.Exit(0)
		}

		flag.Usage()
		os.Exit(1)
	}

	cmd := flag.Arg(0)

	cfg, err := getConfig()

	if err != nil {
		log.Fatalf("FATAL: failed to load config: %v", err)
	}

	// Associate config values with falgs
	err = cfg.BindPFlag(param.MaxApps, flag.Lookup(maxAppsFlag))
	if err != nil {
		log.Fatalf("FATAL: failed to bind flag: --%s: %v", maxAppsFlag, err)
	}
	err = cfg.BindPFlag(param.Leashed, flag.Lookup(leashedFlag))
	if err != nil {
		log.Fatalf("FATAL: failed to bind flag: --%s: %v", leashedFlag, err)
	}

	spin, err := spinnaker.NewFromConfig(cfg)

	if err != nil {
		log.Fatalf("FATAL: spinnaker.New failed: %+v", err)
	}

	outage, err := deps.GetOutage(cfg)
	if err != nil {
		log.Fatalf("FATAL: deps.GetOutage fail: %+v", err)
	}

	sql, err := mysql.NewFromConfig(cfg)
	if err != nil {
		log.Fatalf("FATAL: could not initialize mysql connection: %+v", err)
	}

	// Ensure mysql object gets closed
	defer func() {
		_ = sql.Close()
	}()

	switch cmd {
	case "migrate":
		Migrate(sql)
	case "schedule":
		log.Println("chaosmonkey schedule starting")
		defer log.Println("chaosmonkey schedule done")

		var apps []string
		if *appsPtr != "" {
			// User explicitly specified list of apps on the command line
			apps = strings.Split(*appsPtr, ",")
		} else {
			// User did not explicitly specify list of apps, get 'em all
			var err error
			apps, err = spin.AppNames()
			if err != nil {
				log.Fatalf("FATAL: could not retrieve list of app names: %v", err)
			}
		}

		var schedStore schedstore.SchedStore

		schedStore = sql
		if *noRecordSchedulePtr {
			schedStore = nullSchedStore{}
		}

		Schedule(spin, schedStore, cfg, spin, apps)
	case "fetch-schedule":
		FetchSchedule(sql, cfg)
	case "terminate":
		if len(flag.Args()) != 3 {
			flag.Usage()
			os.Exit(1)
		}
		app := flag.Arg(1)
		account := flag.Arg(2)
		trackers, err := deps.GetTrackers(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not create trackers: %+v", err)
		}

		errCounter, err := deps.GetErrorCounter(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not create error counter: %+v", err)
		}

		env, err := deps.GetEnv(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not determine environment: %+v", err)
		}

		defer logOnPanic(errCounter) // Handler in case of panic
		deps := deps.Deps{
			MonkeyCfg:  cfg,
			Checker:    sql,
			ConfGetter: spin,
			Cl:         clock.New(),
			Dep:        spin,
			T:          spin,
			Trackers:   trackers,
			Ou:         outage,
			ErrCounter: errCounter,
			Env:        env,
		}
		Terminate(deps, app, account, *regionPtr, *stackPtr, *clusterPtr)
	case "outage":
		Outage(outage)
	case "config":
		if len(flag.Args()) != 2 {
			DumpMonkeyConfig(cfg)
			return
		}
		app := flag.Arg(1)
		DumpConfig(spin, app)
	case "eligible":
		if len(flag.Args()) != 3 {
			flag.Usage()
			os.Exit(1)
		}
		app := flag.Arg(1)
		account := flag.Arg(2)
		Eligible(spin, spin, app, account, *regionPtr, *stackPtr, *clusterPtr)
	case "intest":
		env, err := deps.GetEnv(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not determine environment: %+v", err)
		}
		fmt.Println(env.InTest())
	case "account":
		if len(flag.Args()) != 2 {
			flag.Usage()
			os.Exit(1)
		}

		account := flag.Arg(1)
		id, err := spin.AccountID(account)
		if err != nil {
			fmt.Printf("ERROR: Could not retrieve id for account: %s. Reason: %v\n", account, err)
			return
		}
		fmt.Println(id)
	case "provider":
		if len(flag.Args()) != 2 {
			flag.Usage()
			os.Exit(1)
		}
		account := flag.Arg(1)
		provider, err := spin.CloudProvider(account)
		if err != nil {
			fmt.Printf("ERROR: Could not retrieve provider for account: %s. Reason: %v\n", account, err)
			return
		}
		fmt.Println(provider)

	default:
		flag.Usage()
		os.Exit(1)
	}
}

func init() {
	// All logs to stdout
	log.SetOutput(os.Stdout)
}

// logOnPanic increments an error metric and logs if a panic happens
func logOnPanic(errCounter chaosmonkey.ErrorCounter) {
	if e := recover(); e != nil {
		log.Printf("FATAL: panic: %s: %s", e, debug.Stack())
		err := errCounter.Increment()
		if err != nil {
			log.Printf("failed to increment error counter: %s", err)
		}
	}
}

// return configuration info
func getConfig() (*config.Monkey, error) {
	cfg, err := config.Load(configPaths[:])
	if err != nil {
		return nil, err
	}

	return cfg, nil
}

// nullSchedStore is a no-op implementation of api.SchedStore
type nullSchedStore struct{}

// Retrieve implements api.SchedStore.Retrieve
func (n nullSchedStore) Retrieve(date time.Time) (*schedule.Schedule, error) {
	return nil, fmt.Errorf("nullSchedStore does not support Retrieve function")
}

// Publish implements api.SchedStore.Publish
func (n nullSchedStore) Publish(date time.Time, sched *schedule.Schedule) error {
	return nil
}
<commit_msg>Bump version number<commit_after>// Copyright 2016 Netflix, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package command

import (
	"fmt"
	"log"
	"math"
	"os"
	"runtime/debug"
	"strings"
	"time"

	flag "github.com/spf13/pflag"

	"github.com/Netflix/chaosmonkey"
	"github.com/Netflix/chaosmonkey/clock"
	"github.com/Netflix/chaosmonkey/config"
	"github.com/Netflix/chaosmonkey/config/param"
	"github.com/Netflix/chaosmonkey/deps"
	"github.com/Netflix/chaosmonkey/mysql"
	"github.com/Netflix/chaosmonkey/schedstore"
	"github.com/Netflix/chaosmonkey/schedule"
	"github.com/Netflix/chaosmonkey/spinnaker"
)

// Version is the version number
const Version = "2.0.1"

func printVersion() {
	fmt.Printf("%s\n", Version)
}

var (
	// configPaths is where Chaos Monkey will look for a chaosmonkey.toml
	// configuration file
	configPaths = [...]string{".", "/apps/chaosmonkey", "/etc", "/etc/chaosmonkey"}
)

// Usage prints usage
func Usage() {
	usage := `
Chaos Monkey

Usage:
	chaosmonkey <command> ...

command: migrate | schedule | terminate | fetch-schedule | outage | config  | email | eligible | intest

migrate
-------
Applies database migration to the database defined in the configuraton file.

schedule [--max-apps=<N>] [--apps=foo,bar,baz] [--no-record-schedule]
--------------------------------------------------------------------
Generates a schedule of terminations for the day and installs the
terminations as local cron jobs that call "chaosmonkey terminate ..."

--apps=foo,bar,baz     Optionally specify an explicit list of apps to schedule.
                       This is primarily used for debugging.

--max-apps=<N>         Optionally specify the maximum number of apps that Chaos Monkey
					   will schedule. This is primarily used for debugging.

--no-record-schedule   Do not record the schedule with the database.
                       This is primarily used for debugging.

terminate <app> <account> [--region=<region>] [--stack=<stack>] [--cluster=<cluster>] [--leashed]
-----------------------------------------------------------------------------------------------------------------
Terminates an instance from a given app and account.

Optionally specify a region, stack, cluster.

The --leashed flag forces chaosmonkey to run in leashed mode. When leashed,
Chaos Monkey will check if an instance should be terminated, but will not
actually terminate it.

fetch-schedule
--------------
Queries the database to see if there is an existing schedule of
terminations for today. If so, downloads the schedule and sets up cron jobs to
implement the schedule.

outage
------
Output "true" if there is an ongoing outage, otherwise "false". Used for debugging.

config [<app>]
------------
Query Spinnaker for the config for a specific app and dump it to
standard out. This is only used for debugging.

If no app is specified, dump the Monkey-level configuration options to standard out.

Examples:

	chaosmonkey config chaosguineapig

	chaosmonkey config

eligible <app> <account> [--region=<region>] [--stack=<stack>] [--cluster=<cluster>]
-------------------------------------------------------------------------------------

Dump a list of instance-ids that are eligible for termination for a given app, account,
and optionally region, stack, and cluster.

intest
------

Outputs "true" on standard out if running within a test environment, otherwise outputs "false"

account <name>
--------------

Look up an cloud account ID by name.

Example:

	chaosmonkey account test

provider <name>
---------------

Look up the cloud provider by account name.

Example:

	chaosmonkey provider test

`
	fmt.Printf(usage)
}

func init() {
	// Prepend the pid to log statements
	log.SetPrefix(fmt.Sprintf("[%5d] ", os.Getpid()))
}

// Execute is the main entry point for the chaosmonkey cli.
func Execute() {
	regionPtr := flag.String("region", "", "region of termination group")
	stackPtr := flag.String("stack", "", "stack of termination group")
	clusterPtr := flag.String("cluster", "", "cluster of termination group")
	appsPtr := flag.String("apps", "", "comma-separated list of apps to schedule for termination")
	noRecordSchedulePtr := flag.Bool("no-record-schedule", false, "do not record schedule")
	versionPtr := flag.BoolP("version", "v", false, "show version")
	flag.Usage = Usage

	// These flags, if specified, override config values
	maxAppsFlag := "max-apps"
	leashedFlag := "leashed"
	flag.Int(maxAppsFlag, math.MaxInt32, "max number of apps to examine for termination")
	flag.Bool(leashedFlag, false, "force leashed mode")

	flag.Parse()
	if len(flag.Args()) == 0 {
		if *versionPtr {
			printVersion()
			os.Exit(0)
		}

		flag.Usage()
		os.Exit(1)
	}

	cmd := flag.Arg(0)

	cfg, err := getConfig()

	if err != nil {
		log.Fatalf("FATAL: failed to load config: %v", err)
	}

	// Associate config values with falgs
	err = cfg.BindPFlag(param.MaxApps, flag.Lookup(maxAppsFlag))
	if err != nil {
		log.Fatalf("FATAL: failed to bind flag: --%s: %v", maxAppsFlag, err)
	}
	err = cfg.BindPFlag(param.Leashed, flag.Lookup(leashedFlag))
	if err != nil {
		log.Fatalf("FATAL: failed to bind flag: --%s: %v", leashedFlag, err)
	}

	spin, err := spinnaker.NewFromConfig(cfg)

	if err != nil {
		log.Fatalf("FATAL: spinnaker.New failed: %+v", err)
	}

	outage, err := deps.GetOutage(cfg)
	if err != nil {
		log.Fatalf("FATAL: deps.GetOutage fail: %+v", err)
	}

	sql, err := mysql.NewFromConfig(cfg)
	if err != nil {
		log.Fatalf("FATAL: could not initialize mysql connection: %+v", err)
	}

	// Ensure mysql object gets closed
	defer func() {
		_ = sql.Close()
	}()

	switch cmd {
	case "migrate":
		Migrate(sql)
	case "schedule":
		log.Println("chaosmonkey schedule starting")
		defer log.Println("chaosmonkey schedule done")

		var apps []string
		if *appsPtr != "" {
			// User explicitly specified list of apps on the command line
			apps = strings.Split(*appsPtr, ",")
		} else {
			// User did not explicitly specify list of apps, get 'em all
			var err error
			apps, err = spin.AppNames()
			if err != nil {
				log.Fatalf("FATAL: could not retrieve list of app names: %v", err)
			}
		}

		var schedStore schedstore.SchedStore

		schedStore = sql
		if *noRecordSchedulePtr {
			schedStore = nullSchedStore{}
		}

		Schedule(spin, schedStore, cfg, spin, apps)
	case "fetch-schedule":
		FetchSchedule(sql, cfg)
	case "terminate":
		if len(flag.Args()) != 3 {
			flag.Usage()
			os.Exit(1)
		}
		app := flag.Arg(1)
		account := flag.Arg(2)
		trackers, err := deps.GetTrackers(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not create trackers: %+v", err)
		}

		errCounter, err := deps.GetErrorCounter(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not create error counter: %+v", err)
		}

		env, err := deps.GetEnv(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not determine environment: %+v", err)
		}

		defer logOnPanic(errCounter) // Handler in case of panic
		deps := deps.Deps{
			MonkeyCfg:  cfg,
			Checker:    sql,
			ConfGetter: spin,
			Cl:         clock.New(),
			Dep:        spin,
			T:          spin,
			Trackers:   trackers,
			Ou:         outage,
			ErrCounter: errCounter,
			Env:        env,
		}
		Terminate(deps, app, account, *regionPtr, *stackPtr, *clusterPtr)
	case "outage":
		Outage(outage)
	case "config":
		if len(flag.Args()) != 2 {
			DumpMonkeyConfig(cfg)
			return
		}
		app := flag.Arg(1)
		DumpConfig(spin, app)
	case "eligible":
		if len(flag.Args()) != 3 {
			flag.Usage()
			os.Exit(1)
		}
		app := flag.Arg(1)
		account := flag.Arg(2)
		Eligible(spin, spin, app, account, *regionPtr, *stackPtr, *clusterPtr)
	case "intest":
		env, err := deps.GetEnv(cfg)
		if err != nil {
			log.Fatalf("FATAL: could not determine environment: %+v", err)
		}
		fmt.Println(env.InTest())
	case "account":
		if len(flag.Args()) != 2 {
			flag.Usage()
			os.Exit(1)
		}

		account := flag.Arg(1)
		id, err := spin.AccountID(account)
		if err != nil {
			fmt.Printf("ERROR: Could not retrieve id for account: %s. Reason: %v\n", account, err)
			return
		}
		fmt.Println(id)
	case "provider":
		if len(flag.Args()) != 2 {
			flag.Usage()
			os.Exit(1)
		}
		account := flag.Arg(1)
		provider, err := spin.CloudProvider(account)
		if err != nil {
			fmt.Printf("ERROR: Could not retrieve provider for account: %s. Reason: %v\n", account, err)
			return
		}
		fmt.Println(provider)

	default:
		flag.Usage()
		os.Exit(1)
	}
}

func init() {
	// All logs to stdout
	log.SetOutput(os.Stdout)
}

// logOnPanic increments an error metric and logs if a panic happens
func logOnPanic(errCounter chaosmonkey.ErrorCounter) {
	if e := recover(); e != nil {
		log.Printf("FATAL: panic: %s: %s", e, debug.Stack())
		err := errCounter.Increment()
		if err != nil {
			log.Printf("failed to increment error counter: %s", err)
		}
	}
}

// return configuration info
func getConfig() (*config.Monkey, error) {
	cfg, err := config.Load(configPaths[:])
	if err != nil {
		return nil, err
	}

	return cfg, nil
}

// nullSchedStore is a no-op implementation of api.SchedStore
type nullSchedStore struct{}

// Retrieve implements api.SchedStore.Retrieve
func (n nullSchedStore) Retrieve(date time.Time) (*schedule.Schedule, error) {
	return nil, fmt.Errorf("nullSchedStore does not support Retrieve function")
}

// Publish implements api.SchedStore.Publish
func (n nullSchedStore) Publish(date time.Time, sched *schedule.Schedule) error {
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Radu Berinde (radu@cockroachlabs.com)

package distsql

import (
	"sync"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/sql/parser"
	"github.com/cockroachdb/cockroach/sql/sqlbase"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

// tableReader is the start of a computation flow; it performs KV operations to
// retrieve rows for a table, runs a filter expression, and passes rows with the
// desired column values to an output rowReceiver.
// See docs/RFCS/distributed_sql.md
type tableReader struct {
	desc                 sqlbase.TableDescriptor
	spans                sqlbase.Spans
	outputCols           []int
	hardLimit, softLimit int64

	output rowReceiver

	filter exprHelper

	txn     *client.Txn
	fetcher sqlbase.RowFetcher
	// Last row returned by the rowFetcher; it has one entry per table column.
	row      sqlbase.EncDatumRow
	rowAlloc sqlbase.EncDatumRowAlloc
}

var _ processor = &tableReader{}

// newTableReader creates a tableReader.
func newTableReader(
	spec *TableReaderSpec, txn *client.Txn, output rowReceiver, evalCtx *parser.EvalContext,
) (*tableReader, error) {
	tr := &tableReader{
		desc:      spec.Table,
		txn:       txn,
		output:    output,
		hardLimit: spec.HardLimit,
		softLimit: spec.SoftLimit,
	}

	if tr.hardLimit != 0 && tr.hardLimit < tr.softLimit {
		return nil, util.Errorf("soft limit %d larger than hard limit %d", tr.softLimit,
			tr.hardLimit)
	}

	numCols := len(tr.desc.Columns)

	tr.outputCols = make([]int, len(spec.OutputColumns))
	for i, v := range spec.OutputColumns {
		tr.outputCols[i] = int(v)
	}

	// Figure out which columns we need: the output columns plus any other
	// columns used by the filter expression.
	valNeededForCol := make([]bool, numCols)
	for _, c := range tr.outputCols {
		if c < 0 || c >= numCols {
			return nil, util.Errorf("invalid column index %d", c)
		}
		valNeededForCol[c] = true
	}

	if spec.Filter.Expr != "" {
		types := make([]sqlbase.ColumnType_Kind, numCols)
		for i := range types {
			types[i] = tr.desc.Columns[i].Type.Kind
		}
		if err := tr.filter.init(spec.Filter, types, evalCtx); err != nil {
			return nil, err
		}
		for c := 0; c < numCols; c++ {
			valNeededForCol[c] = valNeededForCol[c] || tr.filter.vars.IndexedVarUsed(c)
		}
	}

	if spec.IndexIdx > uint32(len(tr.desc.Indexes)) {
		return nil, util.Errorf("invalid IndexIdx %d", spec.IndexIdx)
	}

	var index *sqlbase.IndexDescriptor
	var isSecondaryIndex bool

	if spec.IndexIdx > 0 {
		index = &tr.desc.Indexes[spec.IndexIdx-1]
		isSecondaryIndex = true
	} else {
		index = &tr.desc.PrimaryIndex
	}

	colIdxMap := make(map[sqlbase.ColumnID]int, len(tr.desc.Columns))
	for i, c := range tr.desc.Columns {
		colIdxMap[c.ID] = i
	}
	err := tr.fetcher.Init(&tr.desc, colIdxMap, index, spec.Reverse, isSecondaryIndex,
		tr.desc.Columns, valNeededForCol)
	if err != nil {
		return nil, err
	}

	tr.spans = make(sqlbase.Spans, len(spec.Spans))
	for i, s := range spec.Spans {
		tr.spans[i] = sqlbase.Span{Start: s.Span.Key, End: s.Span.EndKey}
	}

	return tr, nil
}

// getLimitHint calculates the row limit hint for the row fetcher.
func (tr *tableReader) getLimitHint() int64 {
	softLimit := tr.softLimit
	if tr.hardLimit != 0 {
		if tr.filter.expr == nil {
			return tr.hardLimit
		}
		// If we have a filter, we don't know how many rows will pass the filter
		// so the hard limit is actually a "soft" limit at the row fetcher.
		if softLimit == 0 {
			softLimit = tr.hardLimit
		}
	}
	// If the limit is soft, we request a multiple of the limit.
	// If the limit is 0 (no limit), we must return 0.
	return softLimit * 2
}

// nextRow processes table rows until it finds a row that passes the filter.
// Returns a nil row when there are no more rows.
func (tr *tableReader) nextRow() (sqlbase.EncDatumRow, error) {
	for {
		fetcherRow, err := tr.fetcher.NextRow()
		if err != nil || fetcherRow == nil {
			return nil, err
		}

		// TODO(radu): we are defeating the purpose of EncDatum here - we
		// should modify RowFetcher to return EncDatums directly and avoid
		// the cost of decoding/reencoding.
		for i := range fetcherRow {
			if fetcherRow[i] != nil {
				tr.row[i].SetDatum(tr.desc.Columns[i].Type.Kind, fetcherRow[i])
			}
		}
		passesFilter, err := tr.filter.evalFilter(tr.row)
		if err != nil {
			return nil, err
		}
		if passesFilter {
			break
		}
	}
	outRow := tr.rowAlloc.AllocRow(len(tr.outputCols))
	for i, col := range tr.outputCols {
		outRow[i] = tr.row[col]
	}
	return outRow, nil
}

// Run is part of the processor interface.
func (tr *tableReader) Run(wg *sync.WaitGroup) {
	if wg != nil {
		defer wg.Done()
	}
	if log.V(1) {
		log.Infof("TableReader filter: %s\n", tr.filter.expr)
	}

	if err := tr.fetcher.StartScan(tr.txn, tr.spans, tr.getLimitHint()); err != nil {
		tr.output.Close(err)
		return
	}
	tr.row = make(sqlbase.EncDatumRow, len(tr.desc.Columns))
	var rowIdx int64
	for {
		outRow, err := tr.nextRow()
		if err != nil || outRow == nil {
			tr.output.Close(err)
			return
		}
		// Push the row to the output rowReceiver; stop if they don't need more
		// rows.
		if !tr.output.PushRow(outRow) {
			tr.output.Close(nil)
			return
		}
		rowIdx++
		if tr.hardLimit != 0 && rowIdx == tr.hardLimit {
			// We sent tr.hardLimit rows.
			tr.output.Close(nil)
			return
		}
	}
}
<commit_msg>sql/distsql: move basic row fetching code into readerBase<commit_after>// Copyright 2016 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.
//
// Author: Radu Berinde (radu@cockroachlabs.com)

package distsql

import (
	"sync"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/sql/parser"
	"github.com/cockroachdb/cockroach/sql/sqlbase"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

// readerBase implements basic code shared by tableReader and joinReader.
type readerBase struct {
	desc    sqlbase.TableDescriptor
	txn     *client.Txn
	fetcher sqlbase.RowFetcher

	filter exprHelper

	outputCols []int

	// Last row returned by the rowFetcher; it has one entry per table column.
	row      sqlbase.EncDatumRow
	rowAlloc sqlbase.EncDatumRowAlloc
}

func (rb *readerBase) init(
	desc *sqlbase.TableDescriptor,
	indexIdx int,
	txn *client.Txn,
	filter Expression,
	evalCtx *parser.EvalContext,
	outputCols []uint32,
	reverseScan bool,
) error {
	rb.desc = *desc
	rb.txn = txn

	rb.outputCols = make([]int, len(outputCols))
	for i, v := range outputCols {
		rb.outputCols[i] = int(v)
	}

	numCols := len(rb.desc.Columns)

	// Figure out which columns we need: the output columns plus any other
	// columns used by the filter expression.
	valNeededForCol := make([]bool, numCols)
	for _, c := range rb.outputCols {
		if c < 0 || c >= numCols {
			return util.Errorf("invalid column index %d", c)
		}
		valNeededForCol[c] = true
	}

	if filter.Expr != "" {
		types := make([]sqlbase.ColumnType_Kind, numCols)
		for i := range types {
			types[i] = rb.desc.Columns[i].Type.Kind
		}
		if err := rb.filter.init(filter, types, evalCtx); err != nil {
			return err
		}
		for c := 0; c < numCols; c++ {
			valNeededForCol[c] = valNeededForCol[c] || rb.filter.vars.IndexedVarUsed(c)
		}
	}

	// indexIdx is 0 for the primary index, or 1 to <num-indexes> for a
	// secondary index.
	if indexIdx < 0 || indexIdx > len(rb.desc.Indexes) {
		return util.Errorf("invalid indexIdx %d", indexIdx)
	}

	var index *sqlbase.IndexDescriptor
	var isSecondaryIndex bool

	if indexIdx > 0 {
		index = &rb.desc.Indexes[indexIdx-1]
		isSecondaryIndex = true
	} else {
		index = &rb.desc.PrimaryIndex
	}

	colIdxMap := make(map[sqlbase.ColumnID]int, len(rb.desc.Columns))
	for i, c := range rb.desc.Columns {
		colIdxMap[c.ID] = i
	}
	err := rb.fetcher.Init(&rb.desc, colIdxMap, index, reverseScan, isSecondaryIndex,
		tr.desc.Columns, valNeededForCol)
	if err != nil {
		return err
	}
	rb.row = make(sqlbase.EncDatumRow, len(rb.desc.Columns))
	return nil
}

// nextRow processes table rows until it finds a row that passes the filter.
// Returns a nil row when there are no more rows.
func (rb *readerBase) nextRow() (sqlbase.EncDatumRow, error) {
	for {
		fetcherRow, err := rb.fetcher.NextRow()
		if err != nil || fetcherRow == nil {
			return nil, err
		}

		// TODO(radu): we are defeating the purpose of EncDatum here - we
		// should modify RowFetcher to return EncDatums directly and avoid
		// the cost of decoding/reencoding.
		for i := range fetcherRow {
			if fetcherRow[i] != nil {
				rb.row[i].SetDatum(rb.desc.Columns[i].Type.Kind, fetcherRow[i])
			}
		}
		passesFilter, err := rb.filter.evalFilter(rb.row)
		if err != nil {
			return nil, err
		}
		if passesFilter {
			break
		}
	}
	outRow := rb.rowAlloc.AllocRow(len(rb.outputCols))
	for i, col := range rb.outputCols {
		outRow[i] = rb.row[col]
	}
	return outRow, nil
}

// tableReader is the start of a computation flow; it performs KV operations to
// retrieve rows for a table, runs a filter expression, and passes rows with the
// desired column values to an output rowReceiver.
// See docs/RFCS/distributed_sql.md
type tableReader struct {
	readerBase

	spans                sqlbase.Spans
	hardLimit, softLimit int64

	output rowReceiver
}

var _ processor = &tableReader{}

// newTableReader creates a tableReader.
func newTableReader(
	spec *TableReaderSpec, txn *client.Txn, output rowReceiver, evalCtx *parser.EvalContext,
) (*tableReader, error) {
	tr := &tableReader{
		output:    output,
		hardLimit: spec.HardLimit,
		softLimit: spec.SoftLimit,
	}

	if tr.hardLimit != 0 && tr.hardLimit < tr.softLimit {
		return nil, util.Errorf("soft limit %d larger than hard limit %d", tr.softLimit,
			tr.hardLimit)
	}

	err := tr.readerBase.init(&spec.Table, int(spec.IndexIdx), txn, spec.Filter, evalCtx,
		spec.OutputColumns, spec.Reverse)
	if err != nil {
		return nil, err
	}

	tr.spans = make(sqlbase.Spans, len(spec.Spans))
	for i, s := range spec.Spans {
		tr.spans[i] = sqlbase.Span{Start: s.Span.Key, End: s.Span.EndKey}
	}

	return tr, nil
}

// getLimitHint calculates the row limit hint for the row fetcher.
func (tr *tableReader) getLimitHint() int64 {
	softLimit := tr.softLimit
	if tr.hardLimit != 0 {
		if tr.filter.expr == nil {
			return tr.hardLimit
		}
		// If we have a filter, we don't know how many rows will pass the filter
		// so the hard limit is actually a "soft" limit at the row fetcher.
		if softLimit == 0 {
			softLimit = tr.hardLimit
		}
	}
	// If the limit is soft, we request a multiple of the limit.
	// If the limit is 0 (no limit), we must return 0.
	return softLimit * 2
}

// Run is part of the processor interface.
func (tr *tableReader) Run(wg *sync.WaitGroup) {
	if wg != nil {
		defer wg.Done()
	}
	if log.V(1) {
		log.Infof("TableReader filter: %s\n", tr.filter.expr)
	}

	if err := tr.fetcher.StartScan(tr.txn, tr.spans, tr.getLimitHint()); err != nil {
		tr.output.Close(err)
		return
	}
	var rowIdx int64
	for {
		outRow, err := tr.nextRow()
		if err != nil || outRow == nil {
			tr.output.Close(err)
			return
		}
		// Push the row to the output rowReceiver; stop if they don't need more
		// rows.
		if !tr.output.PushRow(outRow) {
			tr.output.Close(nil)
			return
		}
		rowIdx++
		if tr.hardLimit != 0 && rowIdx == tr.hardLimit {
			// We sent tr.hardLimit rows.
			tr.output.Close(nil)
			return
		}
	}
}
<|endoftext|>


<commit_before>package slackbot

import (
	"encoding/json"
	"fmt"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/justinian/dice"
)

type Command struct {
	Name string
	Desc string
	Func func(*Instance, *Message, []string) error
}

var COMMANDS []*Command

func init() {
	COMMANDS = []*Command{
		&Command{"apollo", "Tell a random quote from Apollo's source code.", cmd_apollo},
		&Command{"catfact", "Tell a random cat fact.", cmd_catfact},
		&Command{"duck", "Quack.", cmd_duck},
		&Command{"goon", "Tell a random quote from Goon's source code.", cmd_goon},
		&Command{"help", "Show a list of commands.", cmd_help},
		&Command{"mute", "Mute my messages to this channel, for a while.", cmd_mute},
		&Command{"pun", "Tell a random pun.", cmd_pun},
		&Command{"roll", "Throw a dice roll.", cmd_roll},
		&Command{"status", "Show my current status.", cmd_status},
		&Command{"vote", "start/stop a vote or vote yes/no during a vote.", cmd_vote},
		&Command{"wiki", "Quote a page from our SS13 wiki.", cmd_wiki},
	}
}

func cmd_help(i *Instance, m *Message, args []string) error {
	tmp := "Available commands:\n"
	for _, c := range COMMANDS {
		tmp += fmt.Sprintf("- `%s\t%s`\n", c.Name, c.Desc)
	}
	i.UserMsg(m.User, tmp)
	return nil
}

func cmd_duck(i *Instance, m *Message, args []string) error {
	var quotes = []string{
		"Quack",
		"Wenk",
		"O RLY?",
		"Kraaawk!",
		"Bwaak",
		"Chirp",
	}
	i.ChannelMsg(m.Channel, quotes[rand.Intn(len(quotes))])
	return nil
}

func cmd_vote(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	if args[0] == "start" {
		i.StartVote(m.Channel)
		i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has started a new vote! Tell me if you would like to `vote yes` or `vote no` on it.", i.Users[m.User].Name))
	} else if args[0] == "stop" {
		votes := i.StopVote(m.Channel)
		var result string
		if votes > 0 {
			result = "*YES* has won"
		} else if votes < 0 {
			result = "*NO* has won"
		} else {
			result = "It's a tie! No one won"
		}
		i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has stopped the vote! The result is...\n%s (score: %d)!", i.Users[m.User].Name, result, votes))
	} else if args[0] == "yes" {
		i.Vote(+1, m.Channel)
	} else if args[0] == "no" {
		i.Vote(-1, m.Channel)
	}
	return nil
}

func cmd_mute(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	dur, e := time.ParseDuration(args[0])
	if e != nil {
		return fmt.Errorf("Couldn't parse a duration.")
	}
	if dur.Minutes() < 1 {
		dur = time.Duration(5) * time.Minute
	}
	if dur.Minutes() > 60 {
		dur = time.Duration(60) * time.Minute
	}

	i.ChannelMsg(m.Channel, fmt.Sprintf("I will now shut up for ~%.0f minutes.", dur.Minutes()))
	go func() {
		time.Sleep(5)
		i.Mute(m.Channel, dur)
	}()
	return nil
}

func cmd_status(i *Instance, m *Message, args []string) error {
	tmp := "My current status:\n"
	tmp += fmt.Sprintf("`Running: %v`\n", i.running)
	tmp += fmt.Sprintf("`Version: v%v`\n", VERSION)
	tmp += fmt.Sprintf("`Debug mode: %v`\n", i.Debug)
	tmp += fmt.Sprintf("`Latency: %v`\n", i.Latency)
	tmp += fmt.Sprintf("`My User ID: @%v`\n", i.BotId)
	tmp += fmt.Sprintf("`Tracked channels: %v`\n", len(i.Channels))
	tmp += fmt.Sprintf("`Tracked users: %v`\n", len(i.Users))
	tmp += fmt.Sprintf("`No. of Goon quotes: %v`\n", len(i.goon))
	tmp += fmt.Sprintf("`No. of Apollo quotes: %v`\n", len(i.apollo))
	tmp += fmt.Sprintf("\nI am currently muted on channels:\n")
	if len(i.mutes) < 1 {
		tmp += "- `None`\n"
	} else {
		for k, v := range i.mutes {
			c := i.Channels[k].Name
			t := v.Sub(time.Now())
			tmp += fmt.Sprintf("- `%v (ends in ~%.0f minutes)`\n", c, t.Minutes())
		}
	}
	i.UserMsg(m.User, tmp)
	return nil
}

func cmd_roll(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	ret, res, e := dice.Roll(args[0])
	if e != nil {
		return fmt.Errorf("Bad dice format.")
	}
	i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has rolled: %v, %v\n", i.Users[m.User].Name, ret, res))
	return nil
}

func cmd_wiki(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	u := WIKI_URL + url.QueryEscape(args[0])
	doc, e := goquery.NewDocument(u)
	if e != nil {
		return fmt.Errorf("Couldn't open the wiki for you (%v).", u)
	}

	node := doc.Find("div #mw-content-text > p").First()
	text := strings.TrimSpace(node.Text())
	if node.Length() < 1 || len(text) < 1 {
		return fmt.Errorf("Couldn't quote that page for you (%v).", u)
	}

	i.ChannelMsg(m.Channel, fmt.Sprintf(">>>%s\n`Source: %s`", text, u))
	return nil
}

func cmd_pun(i *Instance, m *Message, args []string) error {
	resp, e := http.Get("http://www.punoftheday.com/cgi-bin/arandompun.pl")
	if e != nil {
		return fmt.Errorf("Sorry, couldn't make up a pun for you.")
	}
	defer resp.Body.Close()
	body, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return fmt.Errorf("Sorry, couldn't make up a pun for you.")
	}
	// Yep this is a fucking mess.
	s := strings.TrimSpace(string(body))
	s = strings.TrimPrefix(s, "document.write('"")
	index := strings.Index(s, """)
	s = html.UnescapeString(s[:index])
	i.ChannelMsg(m.Channel, fmt.Sprintf(">>>%v\n`© 1996-2011 PunoftheDay.com`", s))
	return nil
}

type CatFacts struct {
	Facts   []string
	Success string
}

var LAST_CATFACT_TIME time.Time

func cmd_catfact(i *Instance, m *Message, args []string) error {
	now := time.Now()
	if now.After(LAST_CATFACT_TIME) != true {
		return fmt.Errorf("Sorry, I can only show a single catfact per day (or else I'll get banned!).")
	}
	LAST_CATFACT_TIME = now.Add(24 * time.Hour)

	resp, e := http.Get("http://catfacts-api.appspot.com/api/facts?number=1")
	if e != nil {
		return fmt.Errorf("Sorry, couldn't find any cat facts.")
	}

	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	var cf CatFacts
	e = decoder.Decode(&cf)
	if e != nil {
		return fmt.Errorf("Sorry, couldn't find any cat facts.")
	}

	if len(cf.Facts) > 0 {
		msg := fmt.Sprintf(">>>%v\n`© http://catfacts-api.appspot.com`", cf.Facts[0])
		i.ChannelMsg(m.Channel, msg)
		return nil
	}
	return fmt.Errorf("Sorry, couldn't find any cat facts.")
}

func cmd_goon(i *Instance, m *Message, args []string) error {
	q := i.random_goon_quote()
	msg := fmt.Sprintf(">>>%v\n`%v`", q.Quote, q.File)
	i.ChannelMsg(m.Channel, msg)
	return nil
}
func cmd_apollo(i *Instance, m *Message, args []string) error {
	q := i.random_apollo_quote()
	msg := fmt.Sprintf(">>>%v\n`%v`", q.Quote, q.File)
	i.ChannelMsg(m.Channel, msg)
	return nil
}
<commit_msg>Add channels and users commands.<commit_after>package slackbot

import (
	"encoding/json"
	"fmt"
	"html"
	"io/ioutil"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/justinian/dice"
)

type Command struct {
	Name string
	Desc string
	Func func(*Instance, *Message, []string) error
}

var COMMANDS []*Command

func init() {
	COMMANDS = []*Command{
		&Command{"apollo", "Tell a random quote from Apollo's source code.", cmd_apollo},
		&Command{"catfact", "Tell a random cat fact.", cmd_catfact},
		&Command{"channels", "Show a list of known channels.", cmd_channels},
		&Command{"duck", "Quack.", cmd_duck},
		&Command{"goon", "Tell a random quote from Goon's source code.", cmd_goon},
		&Command{"help", "Show a list of commands.", cmd_help},
		&Command{"mute", "Mute my messages to this channel, for a while.", cmd_mute},
		&Command{"pun", "Tell a random pun.", cmd_pun},
		&Command{"roll", "Throw a dice roll.", cmd_roll},
		&Command{"status", "Show my current status.", cmd_status},
		&Command{"users", "Show a list of known users.", cmd_users},
		&Command{"vote", "start/stop a vote or vote yes/no during a vote.", cmd_vote},
		&Command{"wiki", "Quote a page from our SS13 wiki.", cmd_wiki},
	}
}

func cmd_help(i *Instance, m *Message, args []string) error {
	tmp := "Available commands:\n"
	for _, c := range COMMANDS {
		tmp += fmt.Sprintf("- `%s\t%s`\n", c.Name, c.Desc)
	}
	i.UserMsg(m.User, tmp)
	return nil
}

func cmd_duck(i *Instance, m *Message, args []string) error {
	var quotes = []string{
		"Quack",
		"Wenk",
		"O RLY?",
		"Kraaawk!",
		"Bwaak",
		"Chirp",
	}
	i.ChannelMsg(m.Channel, quotes[rand.Intn(len(quotes))])
	return nil
}

func cmd_vote(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	if args[0] == "start" {
		i.StartVote(m.Channel)
		i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has started a new vote! Tell me if you would like to `vote yes` or `vote no` on it.", i.Users[m.User].Name))
	} else if args[0] == "stop" {
		votes := i.StopVote(m.Channel)
		var result string
		if votes > 0 {
			result = "*YES* has won"
		} else if votes < 0 {
			result = "*NO* has won"
		} else {
			result = "It's a tie! No one won"
		}
		i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has stopped the vote! The result is...\n%s (score: %d)!", i.Users[m.User].Name, result, votes))
	} else if args[0] == "yes" {
		i.Vote(+1, m.Channel)
	} else if args[0] == "no" {
		i.Vote(-1, m.Channel)
	}
	return nil
}

func cmd_mute(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	dur, e := time.ParseDuration(args[0])
	if e != nil {
		return fmt.Errorf("Couldn't parse a duration.")
	}
	if dur.Minutes() < 1 {
		dur = time.Duration(5) * time.Minute
	}
	if dur.Minutes() > 60 {
		dur = time.Duration(60) * time.Minute
	}

	i.ChannelMsg(m.Channel, fmt.Sprintf("I will now shut up for ~%.0f minutes.", dur.Minutes()))
	go func() {
		time.Sleep(5)
		i.Mute(m.Channel, dur)
	}()
	return nil
}

func cmd_status(i *Instance, m *Message, args []string) error {
	tmp := "My current status:\n"
	tmp += fmt.Sprintf("`Running: %v`\n", i.running)
	tmp += fmt.Sprintf("`Version: v%v`\n", VERSION)
	tmp += fmt.Sprintf("`Debug mode: %v`\n", i.Debug)
	tmp += fmt.Sprintf("`Latency: %v`\n", i.Latency)
	tmp += fmt.Sprintf("`My User ID: @%v`\n", i.BotId)
	tmp += fmt.Sprintf("`Tracked channels: %v`\n", len(i.Channels))
	tmp += fmt.Sprintf("`Tracked users: %v`\n", len(i.Users))
	tmp += fmt.Sprintf("`No. of Goon quotes: %v`\n", len(i.goon))
	tmp += fmt.Sprintf("`No. of Apollo quotes: %v`\n", len(i.apollo))
	tmp += fmt.Sprintf("\nI am currently muted on channels:\n")
	if len(i.mutes) < 1 {
		tmp += "- `None`\n"
	} else {
		for k, v := range i.mutes {
			c := i.Channels[k].Name
			t := v.Sub(time.Now())
			tmp += fmt.Sprintf("- `%v (ends in ~%.0f minutes)`\n", c, t.Minutes())
		}
	}
	i.UserMsg(m.User, tmp)
	return nil
}

func cmd_roll(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	ret, res, e := dice.Roll(args[0])
	if e != nil {
		return fmt.Errorf("Bad dice format.")
	}
	i.ChannelMsg(m.Channel, fmt.Sprintf("@%s has rolled: %v, %v\n", i.Users[m.User].Name, ret, res))
	return nil
}

func cmd_wiki(i *Instance, m *Message, args []string) error {
	if len(args) < 1 {
		return fmt.Errorf("Missing argument.")
	}

	u := WIKI_URL + url.QueryEscape(args[0])
	doc, e := goquery.NewDocument(u)
	if e != nil {
		return fmt.Errorf("Couldn't open the wiki for you (%v).", u)
	}

	node := doc.Find("div #mw-content-text > p").First()
	text := strings.TrimSpace(node.Text())
	if node.Length() < 1 || len(text) < 1 {
		return fmt.Errorf("Couldn't quote that page for you (%v).", u)
	}

	i.ChannelMsg(m.Channel, fmt.Sprintf(">>>%s\n`Source: %s`", text, u))
	return nil
}

func cmd_pun(i *Instance, m *Message, args []string) error {
	resp, e := http.Get("http://www.punoftheday.com/cgi-bin/arandompun.pl")
	if e != nil {
		return fmt.Errorf("Sorry, couldn't make up a pun for you.")
	}
	defer resp.Body.Close()
	body, e := ioutil.ReadAll(resp.Body)
	if e != nil {
		return fmt.Errorf("Sorry, couldn't make up a pun for you.")
	}
	// Yep this is a fucking mess.
	s := strings.TrimSpace(string(body))
	s = strings.TrimPrefix(s, "document.write('"")
	index := strings.Index(s, """)
	s = html.UnescapeString(s[:index])
	i.ChannelMsg(m.Channel, fmt.Sprintf(">>>%v\n`© 1996-2011 PunoftheDay.com`", s))
	return nil
}

type CatFacts struct {
	Facts   []string
	Success string
}

var LAST_CATFACT_TIME time.Time

func cmd_catfact(i *Instance, m *Message, args []string) error {
	now := time.Now()
	if now.After(LAST_CATFACT_TIME) != true {
		return fmt.Errorf("Sorry, I can only show a single catfact per day (or else I'll get banned!).")
	}
	LAST_CATFACT_TIME = now.Add(24 * time.Hour)

	resp, e := http.Get("http://catfacts-api.appspot.com/api/facts?number=1")
	if e != nil {
		return fmt.Errorf("Sorry, couldn't find any cat facts.")
	}

	defer resp.Body.Close()
	decoder := json.NewDecoder(resp.Body)
	var cf CatFacts
	e = decoder.Decode(&cf)
	if e != nil {
		return fmt.Errorf("Sorry, couldn't find any cat facts.")
	}

	if len(cf.Facts) > 0 {
		msg := fmt.Sprintf(">>>%v\n`© http://catfacts-api.appspot.com`", cf.Facts[0])
		i.ChannelMsg(m.Channel, msg)
		return nil
	}
	return fmt.Errorf("Sorry, couldn't find any cat facts.")
}

func cmd_goon(i *Instance, m *Message, args []string) error {
	q := i.random_goon_quote()
	msg := fmt.Sprintf(">>>%v\n`%v`", q.Quote, q.File)
	i.ChannelMsg(m.Channel, msg)
	return nil
}
func cmd_apollo(i *Instance, m *Message, args []string) error {
	q := i.random_apollo_quote()
	msg := fmt.Sprintf(">>>%v\n`%v`", q.Quote, q.File)
	i.ChannelMsg(m.Channel, msg)
	return nil
}

func cmd_channels(i *Instance, m *Message, args []string) error {
	tmp := fmt.Sprintf("Known channels (%d):\n", len(i.Channels))

	for id, c := range i.Channels {
		tmp += fmt.Sprintf("`#%s %s - %s`\n", id, c.Name, c.Topic.Value)
	}
	i.UserMsg(m.User, tmp)
	return nil
}

func cmd_users(i *Instance, m *Message, args []string) error {
	tmp := fmt.Sprintf("Known users (%d):\n", len(i.Users))

	for id, u := range i.Users {
		tmp += fmt.Sprintf("`#%s %s - %s`\n", id, u.Name, u.Presence)
	}
	i.UserMsg(m.User, tmp)
	return nil
}
<|endoftext|>