<commit_before>package brontide

import (
	"io"
	"net"
	"time"

	"github.com/roasbeef/btcd/btcec"
)

// Listener is an implementation of a net.Conn which executes an authenticated
// key exchange and message encryption protocol dubbed "Machine" after
// initial connection acceptance. See the Machine struct for additional
// details w.r.t the handshake and encryption scheme used within the
// connection.
type Listener struct {
	localStatic *btcec.PrivateKey

	tcp *net.TCPListener
}

// A compile-time assertion to ensure that Conn meets the net.Listener interface.
var _ net.Listener = (*Listener)(nil)

// NewListener returns a new net.Listener which enforces the Brontide scheme
// during both initial connection establishment and data transfer.
func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener,
	error) {
	addr, err := net.ResolveTCPAddr("tcp", listenAddr)
	if err != nil {
		return nil, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return nil, err
	}

	return &Listener{
		localStatic: localStatic,
		tcp:         l,
	}, nil
}

// Accept waits for and returns the next connection to the listener. All
// incoming connections are authenticated via the three act Brontide
// key-exchange scheme. This function will fail with a non-nil error in the
// case that either the handshake breaks down, or the remote peer doesn't know
// our static public key.
//
// Part of the net.Listener interface.
func (l *Listener) Accept() (net.Conn, error) {
	conn, err := l.tcp.Accept()
	if err != nil {
		return nil, err
	}

	brontideConn := &Conn{
		conn:  conn,
		noise: NewBrontideMachine(false, l.localStatic, nil),
	}

	// We'll ensure that we get ActOne from the remote peer in a timely
	// manner. If they don't respond within 15 seconds, then we'll kill the
	// connection.
	conn.SetReadDeadline(time.Now().Add(time.Second * 15))

	// Attempt to carry out the first act of the handshake protocol. If the
	// connecting node doesn't know our long-term static public key, then
	// this portion will fail with a non-nil error.
	var actOne [ActOneSize]byte
	if _, err := io.ReadFull(conn, actOne[:]); err != nil {
		brontideConn.conn.Close()
		return nil, err
	}
	if err := brontideConn.noise.RecvActOne(actOne); err != nil {
		brontideConn.conn.Close()
		return nil, err
	}

	// Next, progress the handshake processes by sending over our ephemeral
	// key for the session along with an authenticating tag.
	actTwo, err := brontideConn.noise.GenActTwo()
	if err != nil {
		brontideConn.conn.Close()
		return nil, err
	}
	if _, err := conn.Write(actTwo[:]); err != nil {
		brontideConn.conn.Close()
		return nil, err
	}

	// We'll ensure that we get ActTwo from the remote peer in a timely
	// manner. If they don't respond within 15 seconds, then we'll kill the
	// connection.
	conn.SetReadDeadline(time.Now().Add(time.Second * 15))

	// Finally, finish the handshake processes by reading and decrypting
	// the connection peer's static public key. If this succeeds then both
	// sides have mutually authenticated each other.
	var actThree [ActThreeSize]byte
	if _, err := io.ReadFull(conn, actThree[:]); err != nil {
		brontideConn.conn.Close()
		return nil, err
	}
	if err := brontideConn.noise.RecvActThree(actThree); err != nil {
		brontideConn.conn.Close()
		return nil, err
	}

	// We'll reset the deadline as it's no longer critical beyond the
	// initial handshake.
	conn.SetReadDeadline(time.Time{})

	return brontideConn, nil
}

// Close closes the listener.  Any blocked Accept operations will be unblocked
// and return errors.
//
// Part of the net.Listener interface.
func (l *Listener) Close() error {
	return l.tcp.Close()
}

// Addr returns the listener's network address.
//
// Part of the net.Listener interface.
func (l *Listener) Addr() net.Addr {
	return l.tcp.Addr()
}
<commit_msg>brontide/listener: allow parallel handshakes<commit_after>package brontide

import (
	"errors"
	"io"
	"net"
	"time"

	"github.com/roasbeef/btcd/btcec"
)

// defaultHandshakes is the maximum number of handshakes that can be done in
// parallel.
const defaultHandshakes = 1000

// Listener is an implementation of a net.Conn which executes an authenticated
// key exchange and message encryption protocol dubbed "Machine" after
// initial connection acceptance. See the Machine struct for additional
// details w.r.t the handshake and encryption scheme used within the
// connection.
type Listener struct {
	localStatic *btcec.PrivateKey

	tcp *net.TCPListener

	handshakeSema chan struct{}
	conns         chan maybeConn
	quit          chan struct{}
}

// A compile-time assertion to ensure that Conn meets the net.Listener interface.
var _ net.Listener = (*Listener)(nil)

// NewListener returns a new net.Listener which enforces the Brontide scheme
// during both initial connection establishment and data transfer.
func NewListener(localStatic *btcec.PrivateKey, listenAddr string) (*Listener,
	error) {
	addr, err := net.ResolveTCPAddr("tcp", listenAddr)
	if err != nil {
		return nil, err
	}

	l, err := net.ListenTCP("tcp", addr)
	if err != nil {
		return nil, err
	}

	brontideListener := &Listener{
		localStatic:   localStatic,
		tcp:           l,
		handshakeSema: make(chan struct{}, defaultHandshakes),
		conns:         make(chan maybeConn),
		quit:          make(chan struct{}),
	}

	for i := 0; i < defaultHandshakes; i++ {
		brontideListener.handshakeSema <- struct{}{}
	}

	go brontideListener.listen()

	return brontideListener, nil
}

// listen accepts connection from the underlying tcp conn, then performs
// the brontinde handshake procedure asynchronously. A maximum of
// defaultHandshakes will be active at any given time.
//
// NOTE: This method must be run as a goroutine.
func (l *Listener) listen() {
	for {
		select {
		case <-l.handshakeSema:
		case <-l.quit:
			return
		}

		conn, err := l.tcp.Accept()
		if err != nil {
			l.rejectConn(err)
			l.handshakeSema <- struct{}{}
			continue
		}

		go l.doHandshake(conn)
	}
}

// doHandshake asynchronously performs the brontide handshake, so that it does
// not block the main accept loop. This prevents peers that delay writing to the
// connection from block other connection attempts.
func (l *Listener) doHandshake(conn net.Conn) {
	defer func() { l.handshakeSema <- struct{}{} }()

	select {
	case <-l.quit:
		return
	default:
	}

	brontideConn := &Conn{
		conn:  conn,
		noise: NewBrontideMachine(false, l.localStatic, nil),
	}

	// We'll ensure that we get ActOne from the remote peer in a timely
	// manner. If they don't respond within 15 seconds, then we'll kill the
	// connection.
	conn.SetReadDeadline(time.Now().Add(time.Second * 15))

	// Attempt to carry out the first act of the handshake protocol. If the
	// connecting node doesn't know our long-term static public key, then
	// this portion will fail with a non-nil error.
	var actOne [ActOneSize]byte
	if _, err := io.ReadFull(conn, actOne[:]); err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}
	if err := brontideConn.noise.RecvActOne(actOne); err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}

	// Next, progress the handshake processes by sending over our ephemeral
	// key for the session along with an authenticating tag.
	actTwo, err := brontideConn.noise.GenActTwo()
	if err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}
	if _, err := conn.Write(actTwo[:]); err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}

	select {
	case <-l.quit:
		return
	default:
	}

	// We'll ensure that we get ActTwo from the remote peer in a timely
	// manner. If they don't respond within 15 seconds, then we'll kill the
	// connection.
	conn.SetReadDeadline(time.Now().Add(time.Second * 15))

	// Finally, finish the handshake processes by reading and decrypting
	// the connection peer's static public key. If this succeeds then both
	// sides have mutually authenticated each other.
	var actThree [ActThreeSize]byte
	if _, err := io.ReadFull(conn, actThree[:]); err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}
	if err := brontideConn.noise.RecvActThree(actThree); err != nil {
		brontideConn.conn.Close()
		l.rejectConn(err)
		return
	}

	// We'll reset the deadline as it's no longer critical beyond the
	// initial handshake.
	conn.SetReadDeadline(time.Time{})

	l.acceptConn(brontideConn)
}

// maybeConn holds either a brontide connection or an error returned from the
// handshake.
type maybeConn struct {
	conn *Conn
	err  error
}

// acceptConn returns a connection that successfully performed a handshake.
func (l *Listener) acceptConn(conn *Conn) {
	select {
	case l.conns <- maybeConn{conn: conn}:
	case <-l.quit:
	}
}

// rejectConn returns any errors encountered during connection or handshake.
func (l *Listener) rejectConn(err error) {
	select {
	case l.conns <- maybeConn{err: err}:
	case <-l.quit:
	}
}

// Accept waits for and returns the next connection to the listener. All
// incoming connections are authenticated via the three act Brontide
// key-exchange scheme. This function will fail with a non-nil error in the
// case that either the handshake breaks down, or the remote peer doesn't know
// our static public key.
//
// Part of the net.Listener interface.
func (l *Listener) Accept() (net.Conn, error) {
	select {
	case result := <-l.conns:
		return result.conn, result.err
	case <-l.quit:
		return nil, errors.New("brontide connection closed")
	}
}

// Close closes the listener.  Any blocked Accept operations will be unblocked
// and return errors.
//
// Part of the net.Listener interface.
func (l *Listener) Close() error {
	select {
	case <-l.quit:
	default:
		close(l.quit)
	}

	return l.tcp.Close()
}

// Addr returns the listener's network address.
//
// Part of the net.Listener interface.
func (l *Listener) Addr() net.Addr {
	return l.tcp.Addr()
}
<|endoftext|>


<commit_before>package util

import "sync"

// AtomicBool is a boxed-class that provides synchronized access to the
// underlying boolean value
type AtomicBool struct {
	mutex sync.Mutex
	state bool
}

// NewAtomicBool returns a new AtomicBool
func NewAtomicBool(initialState bool) *AtomicBool {
	return &AtomicBool{
		mutex: sync.Mutex{},
		state: initialState}
}

// Get returns the current boolean value synchronously
func (a *AtomicBool) Get() bool {
	a.mutex.Lock()
	defer a.mutex.Unlock()
	return a.state
}

// Set updates the boolean value synchronously
func (a *AtomicBool) Set(newState bool) bool {
	a.mutex.Lock()
	defer a.mutex.Unlock()
	a.state = newState
	return a.state
}
<commit_msg>Update AtomicBool to use atomic memory operation (#1939)<commit_after>package util

import (
	"sync/atomic"
)

func convertBoolToInt32(b bool) int32 {
	if b {
		return 1
	}
	return 0
}

// AtomicBool is a boxed-class that provides synchronized access to the
// underlying boolean value
type AtomicBool struct {
	state int32 // "1" is true, "0" is false
}

// NewAtomicBool returns a new AtomicBool
func NewAtomicBool(initialState bool) *AtomicBool {
	return &AtomicBool{state: convertBoolToInt32(initialState)}
}

// Get returns the current boolean value synchronously
func (a *AtomicBool) Get() bool {
	return atomic.LoadInt32(&a.state) == 1
}

// Set updates the boolean value synchronously
func (a *AtomicBool) Set(newState bool) bool {
	atomic.StoreInt32(&a.state, convertBoolToInt32(newState))
	return newState
}
<|endoftext|>


<commit_before>package scipipe

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	re "regexp"
	str "strings"
	"sync"
)

type ShellProcess struct {
	process
	_OutOnly     bool
	InPorts      map[string]chan *FileTarget
	InPaths      map[string]string
	OutPorts     map[string]chan *FileTarget
	OutPathFuncs map[string]func() string
	ParamPorts   map[string]chan string
	Params       map[string]string
	Prepend      string
	Command      string
	Spawn        bool
}

func NewShellProcess(command string) *ShellProcess {
	return &ShellProcess{
		Command:      command,
		InPorts:      make(map[string]chan *FileTarget),
		InPaths:      make(map[string]string),
		OutPorts:     make(map[string]chan *FileTarget),
		OutPathFuncs: make(map[string]func() string),
		ParamPorts:   make(map[string]chan string),
		Params:       make(map[string]string),
		Spawn:        true,
	}
}

func Sh(cmd string) *ShellProcess {
	return Shell(cmd)
}

func Shell(cmd string) *ShellProcess {
	if !LogExists {
		InitLogAudit()
	}
	p := NewShellProcess(cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

func ShExp(cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *ShellProcess {
	return ShellExpand(cmd, inPaths, outPaths, params)
}

func ShellExpand(cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *ShellProcess {
	cmdExp := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := NewShellProcess(cmdExp)
	p.initPortsFromCmdPattern(cmdExp, params)
	return p
}

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExp string) {
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Println("Params:", params)
	Debug.Println("inPaths:", inPaths)
	Debug.Println("outPaths:", outPaths)
	cmdExp = cmd
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "p" {
			if params != nil && params[name] != "" {
				Debug.Println("Found param:", params[name])
				newstr = params[name]
			}
		} else if typ == "i" {
			if inPaths != nil && inPaths[name] != "" {
				Debug.Println("Found inPath:", inPaths[name])
				newstr = inPaths[name]
			}
		} else if typ == "o" {
			if outPaths != nil && outPaths[name] != "" {
				Debug.Println("Found outPath:", outPaths[name])
				newstr = outPaths[name]
			}
		}
		Debug.Println("Replacing:", whole, "->", newstr)
		cmdExp = str.Replace(cmdExp, whole, newstr, -1)
	}
	if cmd != cmdExp {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExp)
	}
	return
}

func (p *ShellProcess) initPortsFromCmdPattern(cmd string, params map[string]string) {
	// Find in/out port names and Params and set up in struct fields
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		if len(m) < 3 {
			Check(errors.New("Too few matches"))
		}
		typ := m[1]
		name := m[2]
		if typ == "o" {
			p.OutPorts[name] = make(chan *FileTarget, BUFSIZE)
		} else if typ == "p" {
			if params == nil {
				p.ParamPorts[name] = make(chan string, BUFSIZE)
			} else {
				p.Params[name] = params[name]
			}
		}

		// else if typ == "i" {
		// Set up a channel on the inports, even though this is
		// often replaced by another processes output port channel.
		// It might be nice to have it init'ed with a channel
		// anyways, for use cases when we want to send FileTargets
		// on the inport manually.
		// p.InPorts[name] = make(chan *FileTarget, BUFSIZE)
		// }
	}
}

func (p *ShellProcess) Run() {
	Debug.Println("Entering process:", p.Command)
	defer p.closeOutChans()

	wg := new(sync.WaitGroup)
	mx := new(sync.Mutex)
	sendWaitQueue := []map[string]chan int{}
	// Main loop
	for {
		inPortsClosed := p.receiveInputs()
		paramPortsClosed := p.receiveParams()

		if len(p.InPorts) == 0 && paramPortsClosed {
			Debug.Println("Closing loop: No inports, and param ports closed")
			break
		}
		if len(p.ParamPorts) == 0 && inPortsClosed {
			Debug.Println("Closing loop: No param ports, and inports closed")
			break
		}
		if inPortsClosed && paramPortsClosed {
			Debug.Println("Closing loop: Both inports and param ports closed")
			break
		}

		// This is important that it is created anew here, for thread-safety
		outTargets := p.createOutTargets()
		// Format
		cmd := p.formatCommand(p.Command, outTargets)
		cmdForDisplay := str.Replace(cmd, ".tmp", "", -1)

		if p.anyFileExists(outTargets) {
			Warn.Printf("Skipping process, one or more outputs already exist: '%s'\n", cmd)
		} else {
			Audit.Printf("Starting process: %s\n", cmdForDisplay)
			if p.Spawn {
				wg.Add(1)
				beforeSendCh := make(chan int)
				afterSendCh := make(chan int)
				sendWaitQueue = append(sendWaitQueue, map[string](chan int){"before": beforeSendCh, "after": afterSendCh})
				go func() {
					p.executeCommand(cmd)
					p.atomizeTargets(outTargets, mx)
					beforeSendCh <- 1
					p.sendOutputs(outTargets, mx)
					afterSendCh <- 1
					close(beforeSendCh)
					close(afterSendCh)
					wg.Done()
				}()
			} else {
				p.executeCommand(cmd)
				p.atomizeTargets(outTargets, mx)
				p.sendOutputs(outTargets, mx)
			}
			Audit.Printf("Finished process: %s\n", cmdForDisplay)
		}

		// If there are no inports, we know we should exit the loop
		// directly after executing the command, and sending the outputs
		if len(p.InPorts) == 0 && len(p.ParamPorts) == 0 {
			Debug.Printf("Closing after send: No inports or param ports (process '%s')", cmd)
			break
		}
	}
	Debug.Printf("Starting to wait for ordered sends (process '%s')\n", p.Command)
	for i, sendChs := range sendWaitQueue {
		Debug.Printf("sendWaitQueue %d: Waiting to start sending ...\n", i)
		<-sendChs["before"]
		Debug.Printf("sendWaitQueue %d: Now starting to send ...\n", i)
		<-sendChs["after"]
		Debug.Printf("sendWaitQueue %d: Now has sent!\n", i)
	}
	Debug.Printf("Starting to wait (process '%s')\n", p.Command)
	wg.Wait()
	Debug.Printf("Finished waiting (process '%s')\n", p.Command)
	Debug.Println("Exiting process:", p.Command)
}

func (p *ShellProcess) closeOutChans() {
	// Close output channels
	for _, ochan := range p.OutPorts {
		close(ochan)
	}
}

func (p *ShellProcess) receiveInputs() bool {
	inPortsClosed := false
	// Read input targets on in-ports and set up path mappings
	for iname, ichan := range p.InPorts {
		infile, open := <-ichan
		if !open {
			inPortsClosed = true
			continue
		}
		Debug.Println("Receiving file:", infile.GetPath())
		p.InPaths[iname] = infile.GetPath()
	}
	return inPortsClosed
}

func (p *ShellProcess) receiveParams() bool {
	paramPortsClosed := false
	// Read input targets on in-ports and set up path mappings
	for pname, pchan := range p.ParamPorts {
		pval, open := <-pchan
		if !open {
			paramPortsClosed = true
			continue
		}
		Debug.Println("Receiving param:", pname, "with value", pval)
		p.Params[pname] = pval
	}
	return paramPortsClosed
}

func (p *ShellProcess) sendOutputs(outTargets map[string]*FileTarget, mx *sync.Mutex) {
	// Send output targets on out ports
	mx.Lock()
	for oname, ochan := range p.OutPorts {
		Debug.Println("Sending file:", outTargets[oname].GetPath())
		ochan <- outTargets[oname]
	}
	mx.Unlock()
}

func (p *ShellProcess) createOutPaths() (outPaths map[string]string) {
	outPaths = make(map[string]string)
	for oname, ofun := range p.OutPathFuncs {
		outPaths[oname] = ofun()
	}
	return outPaths
}

func (p *ShellProcess) createOutTargets() (outTargets map[string]*FileTarget) {
	outTargets = make(map[string]*FileTarget)
	for oname, opath := range p.createOutPaths() {
		outTargets[oname] = NewFileTarget(opath)
	}
	return
}

func (p *ShellProcess) anyFileExists(targets map[string]*FileTarget) (anyFileExists bool) {
	anyFileExists = false
	for _, tgt := range targets {
		opath := tgt.GetPath()
		otmpPath := tgt.GetTempPath()
		if _, err := os.Stat(opath); err == nil {
			anyFileExists = true
			Debug.Println("Output file exists already:", opath)
		}
		if _, err := os.Stat(otmpPath); err == nil {
			anyFileExists = true
			Warn.Println("Temporary Output file already exists:", otmpPath, ". Check your workflow for correctness!")
		}
	}
	return
}

func (p *ShellProcess) executeCommand(cmd string) {
	Info.Println("Executing cmd:", cmd)
	_, err := exec.Command("bash", "-c", cmd).Output()
	Check(err)
}

func (p *ShellProcess) atomizeTargets(targets map[string]*FileTarget, mx *sync.Mutex) {
	mx.Lock()
	for _, tgt := range targets {
		Debug.Printf("Atomizing file: %s -> %s", tgt.GetTempPath(), tgt.GetPath())
		tgt.Atomize()
	}
	mx.Unlock()
}

func (p *ShellProcess) formatCommand(cmd string, outTargets map[string]*FileTarget) string {
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "o" {
			if outTargets[name] == nil {
				msg := fmt.Sprint("Missing outpath for outport '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = outTargets[name].GetTempPath() // Means important to Atomize afterwards!
			}
		} else if typ == "i" {
			if p.InPaths[name] == "" {
				msg := fmt.Sprint("Missing inpath for inport '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = p.InPaths[name]
			}
		} else if typ == "p" {
			if p.Params[name] == "" {
				msg := fmt.Sprint("Missing param value param '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = p.Params[name]
			}
		}
		if newstr == "" {
			msg := fmt.Sprint("Replace failed for port ", name, " in process '", p.Command, "'")
			Check(errors.New(msg))
		}
		cmd = str.Replace(cmd, whole, newstr, -1)
	}
	// Add prepend string to the command
	if p.Prepend != "" {
		cmd = fmt.Sprintf("%s %s", p.Prepend, cmd)
	}
	return cmd
}

func (p *ShellProcess) GetInPath(inPort string) string {
	var inPath string
	if p.InPaths[inPort] != "" {
		inPath = p.InPaths[inPort]
	} else {
		msg := fmt.Sprint("p.GetInPath(): Missing inpath for inport '", inPort, "' of shell process '", p.Command, "'")
		Check(errors.New(msg))
	}
	return inPath
}

func getPlaceHolderRegex() *re.Regexp {
	r, err := re.Compile("{(o|i|p):([^{}:]+)}")
	Check(err)
	return r
}
<commit_msg>These log lines are about tasks though, not processes<commit_after>package scipipe

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	re "regexp"
	str "strings"
	"sync"
)

type ShellProcess struct {
	process
	_OutOnly     bool
	InPorts      map[string]chan *FileTarget
	InPaths      map[string]string
	OutPorts     map[string]chan *FileTarget
	OutPathFuncs map[string]func() string
	ParamPorts   map[string]chan string
	Params       map[string]string
	Prepend      string
	Command      string
	Spawn        bool
}

func NewShellProcess(command string) *ShellProcess {
	return &ShellProcess{
		Command:      command,
		InPorts:      make(map[string]chan *FileTarget),
		InPaths:      make(map[string]string),
		OutPorts:     make(map[string]chan *FileTarget),
		OutPathFuncs: make(map[string]func() string),
		ParamPorts:   make(map[string]chan string),
		Params:       make(map[string]string),
		Spawn:        true,
	}
}

func Sh(cmd string) *ShellProcess {
	return Shell(cmd)
}

func Shell(cmd string) *ShellProcess {
	if !LogExists {
		InitLogAudit()
	}
	p := NewShellProcess(cmd)
	p.initPortsFromCmdPattern(cmd, nil)
	return p
}

func ShExp(cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *ShellProcess {
	return ShellExpand(cmd, inPaths, outPaths, params)
}

func ShellExpand(cmd string, inPaths map[string]string, outPaths map[string]string, params map[string]string) *ShellProcess {
	cmdExp := expandCommandParamsAndPaths(cmd, params, inPaths, outPaths)
	p := NewShellProcess(cmdExp)
	p.initPortsFromCmdPattern(cmdExp, params)
	return p
}

func expandCommandParamsAndPaths(cmd string, params map[string]string, inPaths map[string]string, outPaths map[string]string) (cmdExp string) {
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	Debug.Println("Params:", params)
	Debug.Println("inPaths:", inPaths)
	Debug.Println("outPaths:", outPaths)
	cmdExp = cmd
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "p" {
			if params != nil && params[name] != "" {
				Debug.Println("Found param:", params[name])
				newstr = params[name]
			}
		} else if typ == "i" {
			if inPaths != nil && inPaths[name] != "" {
				Debug.Println("Found inPath:", inPaths[name])
				newstr = inPaths[name]
			}
		} else if typ == "o" {
			if outPaths != nil && outPaths[name] != "" {
				Debug.Println("Found outPath:", outPaths[name])
				newstr = outPaths[name]
			}
		}
		Debug.Println("Replacing:", whole, "->", newstr)
		cmdExp = str.Replace(cmdExp, whole, newstr, -1)
	}
	if cmd != cmdExp {
		Debug.Printf("Expanded command '%s' into '%s'\n", cmd, cmdExp)
	}
	return
}

func (p *ShellProcess) initPortsFromCmdPattern(cmd string, params map[string]string) {
	// Find in/out port names and Params and set up in struct fields
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		if len(m) < 3 {
			Check(errors.New("Too few matches"))
		}
		typ := m[1]
		name := m[2]
		if typ == "o" {
			p.OutPorts[name] = make(chan *FileTarget, BUFSIZE)
		} else if typ == "p" {
			if params == nil {
				p.ParamPorts[name] = make(chan string, BUFSIZE)
			} else {
				p.Params[name] = params[name]
			}
		}

		// else if typ == "i" {
		// Set up a channel on the inports, even though this is
		// often replaced by another processes output port channel.
		// It might be nice to have it init'ed with a channel
		// anyways, for use cases when we want to send FileTargets
		// on the inport manually.
		// p.InPorts[name] = make(chan *FileTarget, BUFSIZE)
		// }
	}
}

func (p *ShellProcess) Run() {
	Debug.Println("Entering process:", p.Command)
	defer p.closeOutChans()

	wg := new(sync.WaitGroup)
	mx := new(sync.Mutex)
	sendWaitQueue := []map[string]chan int{}
	// Main loop
	for {
		inPortsClosed := p.receiveInputs()
		paramPortsClosed := p.receiveParams()

		if len(p.InPorts) == 0 && paramPortsClosed {
			Debug.Println("Closing loop: No inports, and param ports closed")
			break
		}
		if len(p.ParamPorts) == 0 && inPortsClosed {
			Debug.Println("Closing loop: No param ports, and inports closed")
			break
		}
		if inPortsClosed && paramPortsClosed {
			Debug.Println("Closing loop: Both inports and param ports closed")
			break
		}

		// This is important that it is created anew here, for thread-safety
		outTargets := p.createOutTargets()
		// Format
		cmd := p.formatCommand(p.Command, outTargets)
		cmdForDisplay := str.Replace(cmd, ".tmp", "", -1)

		if p.anyFileExists(outTargets) {
			Warn.Printf("Skipping process, one or more outputs already exist: '%s'\n", cmd)
		} else {
			Audit.Printf("Starting shell task: %s\n", cmdForDisplay)
			if p.Spawn {
				wg.Add(1)
				beforeSendCh := make(chan int)
				afterSendCh := make(chan int)
				sendWaitQueue = append(sendWaitQueue, map[string](chan int){"before": beforeSendCh, "after": afterSendCh})
				go func() {
					p.executeCommand(cmd)
					p.atomizeTargets(outTargets, mx)
					beforeSendCh <- 1
					p.sendOutputs(outTargets, mx)
					afterSendCh <- 1
					close(beforeSendCh)
					close(afterSendCh)
					wg.Done()
				}()
			} else {
				p.executeCommand(cmd)
				p.atomizeTargets(outTargets, mx)
				p.sendOutputs(outTargets, mx)
			}
			Audit.Printf("Finished shell task: %s\n", cmdForDisplay)
		}

		// If there are no inports, we know we should exit the loop
		// directly after executing the command, and sending the outputs
		if len(p.InPorts) == 0 && len(p.ParamPorts) == 0 {
			Debug.Printf("Closing after send: No inports or param ports (process '%s')", cmd)
			break
		}
	}
	Debug.Printf("Starting to wait for ordered sends (process '%s')\n", p.Command)
	for i, sendChs := range sendWaitQueue {
		Debug.Printf("sendWaitQueue %d: Waiting to start sending ...\n", i)
		<-sendChs["before"]
		Debug.Printf("sendWaitQueue %d: Now starting to send ...\n", i)
		<-sendChs["after"]
		Debug.Printf("sendWaitQueue %d: Now has sent!\n", i)
	}
	Debug.Printf("Starting to wait (process '%s')\n", p.Command)
	wg.Wait()
	Debug.Printf("Finished waiting (process '%s')\n", p.Command)
	Debug.Println("Exiting process:", p.Command)
}

func (p *ShellProcess) closeOutChans() {
	// Close output channels
	for _, ochan := range p.OutPorts {
		close(ochan)
	}
}

func (p *ShellProcess) receiveInputs() bool {
	inPortsClosed := false
	// Read input targets on in-ports and set up path mappings
	for iname, ichan := range p.InPorts {
		infile, open := <-ichan
		if !open {
			inPortsClosed = true
			continue
		}
		Debug.Println("Receiving file:", infile.GetPath())
		p.InPaths[iname] = infile.GetPath()
	}
	return inPortsClosed
}

func (p *ShellProcess) receiveParams() bool {
	paramPortsClosed := false
	// Read input targets on in-ports and set up path mappings
	for pname, pchan := range p.ParamPorts {
		pval, open := <-pchan
		if !open {
			paramPortsClosed = true
			continue
		}
		Debug.Println("Receiving param:", pname, "with value", pval)
		p.Params[pname] = pval
	}
	return paramPortsClosed
}

func (p *ShellProcess) sendOutputs(outTargets map[string]*FileTarget, mx *sync.Mutex) {
	// Send output targets on out ports
	mx.Lock()
	for oname, ochan := range p.OutPorts {
		Debug.Println("Sending file:", outTargets[oname].GetPath())
		ochan <- outTargets[oname]
	}
	mx.Unlock()
}

func (p *ShellProcess) createOutPaths() (outPaths map[string]string) {
	outPaths = make(map[string]string)
	for oname, ofun := range p.OutPathFuncs {
		outPaths[oname] = ofun()
	}
	return outPaths
}

func (p *ShellProcess) createOutTargets() (outTargets map[string]*FileTarget) {
	outTargets = make(map[string]*FileTarget)
	for oname, opath := range p.createOutPaths() {
		outTargets[oname] = NewFileTarget(opath)
	}
	return
}

func (p *ShellProcess) anyFileExists(targets map[string]*FileTarget) (anyFileExists bool) {
	anyFileExists = false
	for _, tgt := range targets {
		opath := tgt.GetPath()
		otmpPath := tgt.GetTempPath()
		if _, err := os.Stat(opath); err == nil {
			anyFileExists = true
			Debug.Println("Output file exists already:", opath)
		}
		if _, err := os.Stat(otmpPath); err == nil {
			anyFileExists = true
			Warn.Println("Temporary Output file already exists:", otmpPath, ". Check your workflow for correctness!")
		}
	}
	return
}

func (p *ShellProcess) executeCommand(cmd string) {
	Info.Println("Executing cmd:", cmd)
	_, err := exec.Command("bash", "-c", cmd).Output()
	Check(err)
}

func (p *ShellProcess) atomizeTargets(targets map[string]*FileTarget, mx *sync.Mutex) {
	mx.Lock()
	for _, tgt := range targets {
		Debug.Printf("Atomizing file: %s -> %s", tgt.GetTempPath(), tgt.GetPath())
		tgt.Atomize()
	}
	mx.Unlock()
}

func (p *ShellProcess) formatCommand(cmd string, outTargets map[string]*FileTarget) string {
	r := getPlaceHolderRegex()
	ms := r.FindAllStringSubmatch(cmd, -1)
	for _, m := range ms {
		whole := m[0]
		typ := m[1]
		name := m[2]
		var newstr string
		if typ == "o" {
			if outTargets[name] == nil {
				msg := fmt.Sprint("Missing outpath for outport '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = outTargets[name].GetTempPath() // Means important to Atomize afterwards!
			}
		} else if typ == "i" {
			if p.InPaths[name] == "" {
				msg := fmt.Sprint("Missing inpath for inport '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = p.InPaths[name]
			}
		} else if typ == "p" {
			if p.Params[name] == "" {
				msg := fmt.Sprint("Missing param value param '", name, "' of shell process '", p.Command, "'")
				Check(errors.New(msg))
			} else {
				newstr = p.Params[name]
			}
		}
		if newstr == "" {
			msg := fmt.Sprint("Replace failed for port ", name, " in process '", p.Command, "'")
			Check(errors.New(msg))
		}
		cmd = str.Replace(cmd, whole, newstr, -1)
	}
	// Add prepend string to the command
	if p.Prepend != "" {
		cmd = fmt.Sprintf("%s %s", p.Prepend, cmd)
	}
	return cmd
}

func (p *ShellProcess) GetInPath(inPort string) string {
	var inPath string
	if p.InPaths[inPort] != "" {
		inPath = p.InPaths[inPort]
	} else {
		msg := fmt.Sprint("p.GetInPath(): Missing inpath for inport '", inPort, "' of shell process '", p.Command, "'")
		Check(errors.New(msg))
	}
	return inPath
}

func getPlaceHolderRegex() *re.Regexp {
	r, err := re.Compile("{(o|i|p):([^{}:]+)}")
	Check(err)
	return r
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"
	"time"

	"github.com/slack-go/slack"
)

type UpdaterOptions struct {
	MinDelay   time.Duration // minimum delay between frames // TODO: externalize?
	UpdateFunc func(Update)

	Username  string // override bot username
	IconEmoji string // override bot icon with Emoji
	IconURL   string // override bot icon with URL
}

func (opts UpdaterOptions) slackMsgOptions() slack.MsgOption {
	var msgOpts []slack.MsgOption
	if opts.Username != "" {
		msgOpts = append(msgOpts, slack.MsgOptionUsername(opts.Username))
	}
	if opts.IconEmoji != "" {
		msgOpts = append(msgOpts, slack.MsgOptionIconEmoji(opts.IconEmoji))
	}
	if opts.IconURL != "" {
		msgOpts = append(msgOpts, slack.MsgOptionIconURL(opts.IconURL))
	}
	return slack.MsgOptionCompose(msgOpts...)
}

// Updater posts and updates the "animated" message via the Slack API.
//
// Will consume the required frames chan, posting the initial frame as a Slack
// message to the provided destination Slack channel, and using each subsequent
// frame to update the text of the posted message.
//
// The Slack channel can be an encoded ID, or a name.
//
// The Slack api client should be configured using an authentication token that
// is bearing required OAuth scopes for its destination and options.
//
// Results
//
// This function blocks until the provided frame chan is closed, or it
// encounters a fatal condition. This fatal condition will be returned as a
// non-nil error, an example would be not being able to make the initial post to
// Slack. Subsequent message update errors may be transient and thus are not
// considered fatal errors, and can be monitored or handled via the
// UpdaterOptions.UpdateFunc callback.
//
// Monitoring Realtime Updates
//
// If you wish to monitor or act upon individual updates to the Updater
// completing, you can set an UpdateFunc callback in the opts. For example, to
// simply log intermediate errors:
//
//     opts.UpdateFunc = func(u Update) {
//         if u.Err != nil {
//             log.Println(err)
//         }
//     }
//
// Or to get the updates sent back to you on a buffered channel:
//
//     updateChan := make(chan Update, 50)
//     opts.UpdateFunc = func(u Update) {
//         updateChan <- res
//     }
//
// This allows the consumer the most flexibility in how to consume these
// updates.
func Updater(ctx context.Context,
	api *slack.Client,
	channelID string,
	frames <-chan string,
	opts UpdaterOptions) error {

	var delayTicker *time.Ticker
	if opts.MinDelay > 0 {
		delayTicker = time.NewTicker(opts.MinDelay)
		defer delayTicker.Stop()
	}

	msgOpts := opts.slackMsgOptions()

	var dst, ts string
	for frame := range frames {
		// if context is already cancelled, exit immediately
		if err := ctx.Err(); err != nil {
			return err
		}

		// If we have a minDelay ticker, ensure at least that much time has
		// passed before proceeding. Also continue to check for context
		// completion just in case, so we can handle that situation immediately
		// if it occurs while we're waiting for the minDelay.
		if delayTicker != nil {
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-delayTicker.C:
			}
		}

		// If no messages have been posted, post the initial message; otherwise,
		// update using the previous channel/timestamp pairing as identifier.
		msgText := slack.MsgOptionText(frame, true)
		var err error
		if dst == "" || ts == "" {
			dst, ts, err = api.PostMessage(channelID, msgText, msgOpts)
			if err != nil {
				return fmt.Errorf("FATAL: Could not post initial frame: %w", err)
			}
		} else {
			_, _, _, err = api.UpdateMessage(dst, ts, msgText, msgOpts)
		}
		if opts.UpdateFunc != nil {
			opts.UpdateFunc(Update{dst, ts, frame, err})
		}
	}

	return nil
}

// Update represents the status returned from the Slack API for a specific
// message post or update.
type Update struct {
	Dst   string // target message destination channel ID
	TS    string // target message timestamp in Slack API format
	Frame string // text sent as message payload
	Err   error
}
<commit_msg>updater: propagate context to external API calls<commit_after>package main

import (
	"context"
	"fmt"
	"time"

	"github.com/slack-go/slack"
)

type UpdaterOptions struct {
	MinDelay   time.Duration // minimum delay between frames // TODO: externalize?
	UpdateFunc func(Update)

	Username  string // override bot username
	IconEmoji string // override bot icon with Emoji
	IconURL   string // override bot icon with URL
}

func (opts UpdaterOptions) slackMsgOptions() slack.MsgOption {
	var msgOpts []slack.MsgOption
	if opts.Username != "" {
		msgOpts = append(msgOpts, slack.MsgOptionUsername(opts.Username))
	}
	if opts.IconEmoji != "" {
		msgOpts = append(msgOpts, slack.MsgOptionIconEmoji(opts.IconEmoji))
	}
	if opts.IconURL != "" {
		msgOpts = append(msgOpts, slack.MsgOptionIconURL(opts.IconURL))
	}
	return slack.MsgOptionCompose(msgOpts...)
}

// Updater posts and updates the "animated" message via the Slack API.
//
// Will consume the required frames chan, posting the initial frame as a Slack
// message to the provided destination Slack channel, and using each subsequent
// frame to update the text of the posted message.
//
// The Slack channel can be an encoded ID, or a name.
//
// The Slack api client should be configured using an authentication token that
// is bearing required OAuth scopes for its destination and options.
//
// Results
//
// This function blocks until the provided frame chan is closed, or it
// encounters a fatal condition. This fatal condition will be returned as a
// non-nil error, an example would be not being able to make the initial post to
// Slack. Subsequent message update errors may be transient and thus are not
// considered fatal errors, and can be monitored or handled via the
// UpdaterOptions.UpdateFunc callback.
//
// Monitoring Realtime Updates
//
// If you wish to monitor or act upon individual updates to the Updater
// completing, you can set an UpdateFunc callback in the opts. For example, to
// simply log intermediate errors:
//
//     opts.UpdateFunc = func(u Update) {
//         if u.Err != nil {
//             log.Println(err)
//         }
//     }
//
// Or to get the updates sent back to you on a buffered channel:
//
//     updateChan := make(chan Update, 50)
//     opts.UpdateFunc = func(u Update) {
//         updateChan <- res
//     }
//
// This allows the consumer the most flexibility in how to consume these
// updates.
func Updater(ctx context.Context,
	api *slack.Client,
	channelID string,
	frames <-chan string,
	opts UpdaterOptions) error {

	var delayTicker *time.Ticker
	if opts.MinDelay > 0 {
		delayTicker = time.NewTicker(opts.MinDelay)
		defer delayTicker.Stop()
	}

	msgOpts := opts.slackMsgOptions()

	var dst, ts string
	for frame := range frames {
		// if context is already cancelled, exit immediately
		if err := ctx.Err(); err != nil {
			return err
		}

		// If we have a minDelay ticker, ensure at least that much time has
		// passed before proceeding. Also continue to check for context
		// completion just in case, so we can handle that situation immediately
		// if it occurs while we're waiting for the minDelay.
		if delayTicker != nil {
			select {
			case <-ctx.Done():
				return ctx.Err()
			case <-delayTicker.C:
			}
		}

		// If no messages have been posted, post the initial message; otherwise,
		// update using the previous channel/timestamp pairing as identifier.
		msgText := slack.MsgOptionText(frame, true)
		var err error
		if dst == "" || ts == "" {
			dst, ts, err = api.PostMessageContext(ctx, channelID, msgText, msgOpts)
			if err != nil {
				return fmt.Errorf("FATAL: Could not post initial frame: %w", err)
			}
		} else {
			_, _, _, err = api.UpdateMessageContext(ctx, dst, ts, msgText, msgOpts)
		}
		if opts.UpdateFunc != nil {
			opts.UpdateFunc(Update{dst, ts, frame, err})
		}
	}

	return nil
}

// Update represents the status returned from the Slack API for a specific
// message post or update.
type Update struct {
	Dst   string // target message destination channel ID
	TS    string // target message timestamp in Slack API format
	Frame string // text sent as message payload
	Err   error
}
<|endoftext|>


<commit_before>package nxt

// SOUND
import "fmt"

// PlaySoundFile creates a Command to play a sound file given a filename and
// whether it should loop or not.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// To wait for the reply, pass in a replyChannel; to not wait, pass in nil
// for the replyChannel.
func PlaySoundFile(filename string, loop bool, replyChannel chan *ReplyTelegram) *Command {
	var loopBytes []byte
	if loop {
		loopBytes = []byte{0xff}
	} else {
		loopBytes = []byte{0x00}
	}
	fileBytes := append([]byte(filename), 0) // null-terminated string
	message := append(loopBytes, fileBytes...)

	return NewDirectCommand(0x02, message, replyChannel)
}

// PlayTone plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// To wait for the reply, pass in a replyChannel; to not wait, pass in nil
// for the replyChannel.
func PlayTone(frequency int, duration int, replyChannel chan *ReplyTelegram) *Command {
	frequencyBytes := []byte{calculateLSB(frequency), calculateMSB(frequency)}
	durationBytes := []byte{calculateLSB(duration), calculateMSB(duration)}
	message := append(frequencyBytes, durationBytes...)

	return NewDirectCommand(0x03, message, replyChannel)
}

// PlaySoundFile plays the sound file with the given filename and
// loops the file if true is passed in for loop; does not loop if
// false is passed in.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// This call is asynchronous and does not wait for a reply.  To wait
// for a reply to see if the call is successful, use PlayToneSync.
func (n NXT) PlaySoundFile(filename string, loop bool) {
	n.CommandChannel <- PlaySoundFile(filename, loop, nil)
}

// PlaySoundFileSync plays the sound file with the given filename and
// loops the file if true is passed in for loop; does not loop if
// false is passed in.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// This call is snchronous waits for a reply.  If there was a problem
// starting the program, it will return a non-nil error.
func (n NXT) PlaySoundFileSync(filename string, loop bool) (*ReplyTelegram, error) {
	reply := make(chan *ReplyTelegram)

	n.CommandChannel <- PlaySoundFile(filename, loop, reply)
	playSoundFileReply := <-reply

	if !playSoundFileReply.IsSuccess() {
		return playSoundFileReply, fmt.Errorf("%v: \"%s\"", playSoundFileReply.Status, filename)
	}

	return playSoundFileReply, nil

}

// PlayTone plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// This call is asynchronous and does not wait for a reply.  To wait
// for a reply to see if the call is successful, use PlayToneSync
func (n NXT) PlayTone(frequency int, duration int) {
	n.CommandChannel <- PlayTone(frequency, duration, nil)
}

// PlayToneSync plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// This call is snchronous waits for a reply.  If there was a problem,
// it will return a non-nil error.
func (n NXT) PlayToneSync(frequency int, duration int) (*ReplyTelegram, error) {
	reply := make(chan *ReplyTelegram)

	n.CommandChannel <- PlayTone(frequency, duration, reply)
	playToneReply := <-reply

	if !playToneReply.IsSuccess() {
		return playToneReply, fmt.Errorf("%v", playToneReply.Status)
	}

	return playToneReply, nil

}
<commit_msg>remove superfluous comment<commit_after>package nxt

import "fmt"

// PlaySoundFile creates a Command to play a sound file given a filename and
// whether it should loop or not.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// To wait for the reply, pass in a replyChannel; to not wait, pass in nil
// for the replyChannel.
func PlaySoundFile(filename string, loop bool, replyChannel chan *ReplyTelegram) *Command {
	var loopBytes []byte
	if loop {
		loopBytes = []byte{0xff}
	} else {
		loopBytes = []byte{0x00}
	}
	fileBytes := append([]byte(filename), 0) // null-terminated string
	message := append(loopBytes, fileBytes...)

	return NewDirectCommand(0x02, message, replyChannel)
}

// PlayTone plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// To wait for the reply, pass in a replyChannel; to not wait, pass in nil
// for the replyChannel.
func PlayTone(frequency int, duration int, replyChannel chan *ReplyTelegram) *Command {
	frequencyBytes := []byte{calculateLSB(frequency), calculateMSB(frequency)}
	durationBytes := []byte{calculateLSB(duration), calculateMSB(duration)}
	message := append(frequencyBytes, durationBytes...)

	return NewDirectCommand(0x03, message, replyChannel)
}

// PlaySoundFile plays the sound file with the given filename and
// loops the file if true is passed in for loop; does not loop if
// false is passed in.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// This call is asynchronous and does not wait for a reply.  To wait
// for a reply to see if the call is successful, use PlayToneSync.
func (n NXT) PlaySoundFile(filename string, loop bool) {
	n.CommandChannel <- PlaySoundFile(filename, loop, nil)
}

// PlaySoundFileSync plays the sound file with the given filename and
// loops the file if true is passed in for loop; does not loop if
// false is passed in.
// NOTE: The NXT will so the filename without a file extension.  If playing
// that file does not work, try adding ".rso" as the extension for sound files.
// This call is snchronous waits for a reply.  If there was a problem
// starting the program, it will return a non-nil error.
func (n NXT) PlaySoundFileSync(filename string, loop bool) (*ReplyTelegram, error) {
	reply := make(chan *ReplyTelegram)

	n.CommandChannel <- PlaySoundFile(filename, loop, reply)
	playSoundFileReply := <-reply

	if !playSoundFileReply.IsSuccess() {
		return playSoundFileReply, fmt.Errorf("%v: \"%s\"", playSoundFileReply.Status, filename)
	}

	return playSoundFileReply, nil

}

// PlayTone plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// This call is asynchronous and does not wait for a reply.  To wait
// for a reply to see if the call is successful, use PlayToneSync
func (n NXT) PlayTone(frequency int, duration int) {
	n.CommandChannel <- PlayTone(frequency, duration, nil)
}

// PlayToneSync plays a tone with the Hz specified in frequency and for the
// duration of duration milliseconds.
// This call is snchronous waits for a reply.  If there was a problem,
// it will return a non-nil error.
func (n NXT) PlayToneSync(frequency int, duration int) (*ReplyTelegram, error) {
	reply := make(chan *ReplyTelegram)

	n.CommandChannel <- PlayTone(frequency, duration, reply)
	playToneReply := <-reply

	if !playToneReply.IsSuccess() {
		return playToneReply, fmt.Errorf("%v", playToneReply.Status)
	}

	return playToneReply, nil

}
<|endoftext|>


<commit_before>// go:generate statik -src=./static

package main

import (
	"flag"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/gorilla/handlers"
	"github.com/jakdept/dandler"
	"github.com/jakdept/flagTrap"
	_ "github.com/jakdept/sp9k1/statik"
	"github.com/rakyll/statik/fs"
)

// now would this be shitposting if there were _tests_?

var serverBanner = `
'______________________________________________________________________________
/                                                                              \
|                 '.'                           .-:::::::::::::::::::::-'      |
|                 -///-'                      '/+++++++++++++++++++++++++-     |
|                ':+++++/-                    -++//////////////////////+++     |
|                /++++++//:.                  -+/----------------------:++     |
|               '/++++///:::.                 -+/---:dddddddds+ymmms----++     |
|             .://+////:::::-                 -+/...:mmmmmdyoymNNNNy...-++     |
|           '://////::::::::::-.              -++::-:mmmdyoymNNNNNNy--:/++     |
|           ://:::::::::::/+++//:'            -+++++ommmhoymNNNNNNNh++++++     |
|           :/syys+:::///++syhyo:.            -+++++ommmmdsodNNNNNNh++++++     |
|         '.+mdo+hmh++++/ommo+yNh-''          -+++++ommmmmmhosmNNNNh++++++     |
|       .//+mN.'''hNs///:mN:'''sNy//-         -+++++ommmmmmmdyohNNNh++++++     |
|      '///+NN.'''hNs::::mM-'''sMy/::-        -++++++dmmmmmmmmdssdNh++++++     |
|      '::::yNd+/yMd::://sNmo/sMm/:::-        -++++++oyyyyyyyyyyo+s+++++++     |
|      '-::::+hmmds//++++/+ydmds/::::-'       -+++++++++++++++++++++++++++     |
|   '-///++++++++++++++++++///:::::////:.     '/+++++++++++++++++++++++++-     |
|  .///////////+hmmmmmmmmmmmh+::://+///::-      .:::::::+++++++++/:::::-'      |
|  ::////////::::+shmmNmmhs+:://++//::::::.             +++++++++:             |
|  -:::::::::::::::::::::///+++///:::::::-'             +++++++++:             |
|   .-:::::::::::::::////+////:::::::::-.'              +++++++++:             |
|      '''''''    ''''''''''''''''''''                  .........'             |
\______________________________________________________________________________/
`

const (
	defaultListen         = ":8080"
	defaultImgDir         = "./"
	defaultWidth          = 310
	defaultHeight         = 200
	defaultCacheDays      = 30
	defaultCacheVariation = 7
	defaultURL            = "localhost:80"
)

var (
	listenAddress       string
	imgDir              string
	thumbWidth          int
	thumbHeight         int
	cacheMinDays        int
	cacheVariation      int
	staticDir           flagTrap.StringTrap
	templateFile        flagTrap.StringTrap
	canonicalURL        string
	canonicalForceHost  bool
	canonicalForcePort  bool
	canonicalDisableTLS bool
	canonicalForceTLS   bool
)

func flags() {
	usage := "address to listen for incoming traffic"
	flag.StringVar(&listenAddress, "listen", defaultListen, usage)
	flag.StringVar(&listenAddress, "l", defaultListen, usage+" (shorthand)")

	usage = "directory of images to serve"
	flag.StringVar(&imgDir, "images", defaultImgDir, usage)
	flag.StringVar(&imgDir, "i", defaultImgDir, usage+" (shorthand)")

	usage = "cache length"
	flag.IntVar(&cacheMinDays, "cacheTime", defaultCacheDays, usage)

	usage = "cache variation"
	flag.IntVar(&cacheVariation, "cacheSkew", defaultCacheVariation, usage)

	usage = "thumbnail width"
	flag.IntVar(&thumbWidth, "width", defaultWidth, usage)
	flag.IntVar(&thumbWidth, "w", defaultWidth, usage+" (shorthand)")

	usage = "thumbnail height"
	flag.IntVar(&thumbHeight, "height", defaultHeight, usage)
	flag.IntVar(&thumbHeight, "h", defaultHeight, usage+" (shorthand)")

	usage = "alternate static directory to serve"
	flag.Var(&staticDir, "static", usage)
	flag.Var(&staticDir, "s", usage+" (shorthand)")

	usage = "alternate index template to serve"
	flag.Var(&templateFile, "template", usage)
	flag.Var(&templateFile, "t", usage+" (shorthand)")

	flag.StringVar(&canonicalURL, "canonicalURl", defaultURL, "canonical host to force")
	flag.BoolVar(&canonicalDisableTLS, "canonicalDisableTLS", false, "force unencrypted protocol")
	flag.BoolVar(&canonicalForceTLS, "canonicalForceTLS", false, "force encrypted protocol")
	flag.BoolVar(&canonicalForceHost, "canonicalForceHost", false, "force a specific hostname")
	flag.BoolVar(&canonicalForcePort, "canonicalForcePort", false, "force a specific port")

	flag.Parse()
}

func parseTemplate(logger *log.Logger, fs http.FileSystem) *template.Template {
	if templateFile.IsSet() {
		// if an alternate template was provided, i can use that instead
		return template.Must(template.ParseFiles(templateFile.String()))
	}
	// have to do it the hard way because it comes from fs
	templFile, err := fs.Open("/page.template")
	if err != nil {
		logger.Fatal(err)
	}
	templData, err := ioutil.ReadAll(templFile)
	if err != nil {
		logger.Fatal(err)
	}
	return template.Must(template.New("page.template").Parse(string(templData)))
}

func createStaticFS(logger *log.Logger, path flagTrap.StringTrap) http.FileSystem {
	if path.IsSet() {
		return http.Dir(path.String())
	}
	filesystem, err := fs.New()
	if err != nil {
		logger.Fatal(err)
	}
	return filesystem
}

func buildMuxer(logger *log.Logger,
	fs http.FileSystem,
	templ *template.Template,
	done chan struct{},
) http.Handler {

	day := time.Hour * time.Duration(64)
	var h http.Handler
	mux := http.NewServeMux()

	// building the static handler
	h = http.FileServer(fs)
	// split the main folder off into a redirect
	h = dandler.Split(http.RedirectHandler("/", 302), h)
	// add a prefix before the handler
	h = http.StripPrefix("/static/", h)
	// add some expiration
	h = dandler.ExpiresRange(day*time.Duration(cacheMinDays),
		day*time.Duration(cacheVariation), h)
	// add the static handler to the muxer
	mux.Handle("/static/", h)

	// create a caching handler
	h = dandler.ThumbCache(logger, thumbWidth, thumbHeight, 32<<20, imgDir, "thumbs", "jpg")
	// split the folder itself into a redirect
	h = dandler.Split(http.RedirectHandler("/", 302), h)
	// strip the prefix
	h = http.StripPrefix("/thumb/", h)
	// add some expiration
	h = dandler.ExpiresRange(day*time.Duration(cacheMinDays),
		day*time.Duration(cacheVariation), h)
	// add the thumbnail handler to the muxer
	mux.Handle("/thumb/", h)

	h = dandler.DirSplit(logger, imgDir, done,
		dandler.Index(logger, imgDir, done, templ),
		dandler.ContentType(logger, imgDir),
	)
	mux.Handle("/", h)

	h = dandler.ASCIIHeader("shit\nposting\n9001", serverBanner, " ", mux)
	h = handlers.CombinedLoggingHandler(os.Stdout, h)

	// add canonical header if required
	if canonicalForceHost ||
		canonicalForcePort ||
		canonicalForceTLS ||
		canonicalDisableTLS {
		options := 0
		if canonicalForceHost {
			options += dandler.ForceHost
		}
		if canonicalForcePort {
			options += dandler.ForcePort
		}
		if canonicalForceTLS {
			options += dandler.ForceHTTPS
		} else if canonicalDisableTLS {
			options += dandler.ForceHTTP
		}

		h = dandler.CanonicalHost(canonicalURL, options, h)
	}

	// compress responses
	h = handlers.CompressHandler(h)

	return h
}

func main() {

	flags()

	logger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Llongfile)

	fs := createStaticFS(logger, staticDir)

	templ := parseTemplate(logger, fs)

	done := make(chan struct{})
	defer close(done)

	srvHandlers := buildMuxer(logger, fs, templ, done)

	logger.Fatal(http.ListenAndServe(listenAddress, srvHandlers))
}
<commit_msg>changed sp9k1 to use kingpin for flags<commit_after>// go:generate statik -src=./static

package main

import (
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/alecthomas/kingpin"
	"github.com/gorilla/handlers"
	"github.com/jakdept/dandler"
	_ "github.com/jakdept/sp9k1/statik"
	"github.com/rakyll/statik/fs"
)

// now would this be shitposting if there were _tests_?

var serverBanner = `
'______________________________________________________________________________
/                                                                              \
|                 '.'                           .-:::::::::::::::::::::-'      |
|                 -///-'                      '/+++++++++++++++++++++++++-     |
|                ':+++++/-                    -++//////////////////////+++     |
|                /++++++//:.                  -+/----------------------:++     |
|               '/++++///:::.                 -+/---:dddddddds+ymmms----++     |
|             .://+////:::::-                 -+/...:mmmmmdyoymNNNNy...-++     |
|           '://////::::::::::-.              -++::-:mmmdyoymNNNNNNy--:/++     |
|           ://:::::::::::/+++//:'            -+++++ommmhoymNNNNNNNh++++++     |
|           :/syys+:::///++syhyo:.            -+++++ommmmdsodNNNNNNh++++++     |
|         '.+mdo+hmh++++/ommo+yNh-''          -+++++ommmmmmhosmNNNNh++++++     |
|       .//+mN.'''hNs///:mN:'''sNy//-         -+++++ommmmmmmdyohNNNh++++++     |
|      '///+NN.'''hNs::::mM-'''sMy/::-        -++++++dmmmmmmmmdssdNh++++++     |
|      '::::yNd+/yMd::://sNmo/sMm/:::-        -++++++oyyyyyyyyyyo+s+++++++     |
|      '-::::+hmmds//++++/+ydmds/::::-'       -+++++++++++++++++++++++++++     |
|   '-///++++++++++++++++++///:::::////:.     '/+++++++++++++++++++++++++-     |
|  .///////////+hmmmmmmmmmmmh+::://+///::-      .:::::::+++++++++/:::::-'      |
|  ::////////::::+shmmNmmhs+:://++//::::::.             +++++++++:             |
|  -:::::::::::::::::::::///+++///:::::::-'             +++++++++:             |
|   .-:::::::::::::::////+////:::::::::-.'              +++++++++:             |
|      '''''''    ''''''''''''''''''''                  .........'             |
\______________________________________________________________________________/
`

var (
	listenAddress = kingpin.Flag("listen", "addresses to listen for incoming non-TLS connections").
			Short('l').Default("127.0.0.1:8080").TCP()

	hostname = kingpin.Flag("hostname", "hostname to register").Short('h').String()

	imgDir = kingpin.Flag("images", "directory of images to serve").
		Short('i').Default("./").ExistingDir()

	cacheMinDays = kingpin.Flag("cacheMin", "minimum days to cache images in browser").
			Default("30").Int()

	cacheVariation = kingpin.Flag("cacheVariation", "difference between minimum and maximum length to cache images").
			Default("7").Int()

	thumbWidth  = kingpin.Flag("width", "maximum thumbnail width").Default("310").Int()
	thumbHeight = kingpin.Flag("height", "thumbnail height").Default("200").Int()

	staticDir = kingpin.Flag("static", "alternate static directory to serve").Short('s').ExistingDir()

	templateFile = kingpin.Flag("template", "alternate index template to serve").Short('t').ExistingFile()

	canonicalURL        = kingpin.Flag("canonicalURL", "default redirect to serve").Default("localhost:80").String()
	canonicalDisableTLS = kingpin.Flag("canonicalDisableTLS", "force unencrypted protocol").Default("false").Bool()
	canonicalForceTLS   = kingpin.Flag("canonicalForceTLS", "force encrypted protocol").Default("true").Bool()
	canonicalForceHost  = kingpin.Flag("canonicalForceHost", "force a specific hostname").Default("true").Bool()
	canonicalForcePort  = kingpin.Flag("canonicalForcePort", "force a specific port").Default("false").Bool()
)

func parseTemplate(logger *log.Logger, fs http.FileSystem) *template.Template {
	if *templateFile != "" {
		// if an alternate template was provided, i can use that instead
		return template.Must(template.ParseFiles(*templateFile))
	}
	// have to do it the hard way because it comes from fs
	templFile, err := fs.Open("/page.template")
	if err != nil {
		logger.Fatal(err)
	}
	templData, err := ioutil.ReadAll(templFile)
	if err != nil {
		logger.Fatal(err)
	}
	return template.Must(template.New("page.template").Parse(string(templData)))
}

func createStaticFS(logger *log.Logger, path string) http.FileSystem {
	if path != "" {
		return http.Dir(path)
	}
	filesystem, err := fs.New()
	if err != nil {
		logger.Fatal(err)
	}
	return filesystem
}

func buildMuxer(logger *log.Logger,
	fs http.FileSystem,
	templ *template.Template,
	done chan struct{},
) http.Handler {

	day := time.Hour * time.Duration(64)
	var h http.Handler
	mux := http.NewServeMux()

	// building the static handler
	h = http.FileServer(fs)
	// split the main folder off into a redirect
	h = dandler.Split(http.RedirectHandler("/", 302), h)
	// add a prefix before the handler
	h = http.StripPrefix("/static/", h)
	// add some expiration
	h = dandler.ExpiresRange(day*time.Duration(*cacheMinDays),
		day*time.Duration(*cacheVariation), h)
	// add the static handler to the muxer
	mux.Handle("/static/", h)

	// create a caching handler
	h = dandler.ThumbCache(logger, *thumbWidth, *thumbHeight, 32<<20, *imgDir, "thumbs", "jpg")
	// split the folder itself into a redirect
	h = dandler.Split(http.RedirectHandler("/", 302), h)
	// strip the prefix
	h = http.StripPrefix("/thumb/", h)
	// add some expiration
	h = dandler.ExpiresRange(day*time.Duration(*cacheMinDays),
		day*time.Duration(*cacheVariation), h)
	// add the thumbnail handler to the muxer
	mux.Handle("/thumb/", h)

	h = dandler.DirSplit(logger, *imgDir, done,
		dandler.Index(logger, *imgDir, done, templ),
		dandler.ContentType(logger, *imgDir),
	)
	mux.Handle("/", h)

	h = dandler.ASCIIHeader("shit\nposting\n9001", serverBanner, " ", mux)
	h = handlers.CombinedLoggingHandler(os.Stdout, h)

	// add canonical header if required
	if *canonicalForceHost ||
		*canonicalForcePort ||
		*canonicalForceTLS ||
		*canonicalDisableTLS {
		options := 0
		if *canonicalForceHost {
			options += dandler.ForceHost
		}
		if *canonicalForcePort {
			options += dandler.ForcePort
		}
		if *canonicalForceTLS {
			options += dandler.ForceHTTPS
		} else if *canonicalDisableTLS {
			options += dandler.ForceHTTP
		}

		h = dandler.CanonicalHost(*canonicalURL, options, h)
	}

	// compress responses
	h = handlers.CompressHandler(h)

	return h
}

func main() {

	kingpin.Parse()

	logger := log.New(os.Stderr, "", log.Ldate|log.Ltime|log.Llongfile)

	fs := createStaticFS(logger, *staticDir)

	templ := parseTemplate(logger, fs)

	done := make(chan struct{})
	defer close(done)

	srvHandlers := buildMuxer(logger, fs, templ, done)

	logger.Fatal(http.ListenAndServe((*listenAddress).String(), srvHandlers))
}
<|endoftext|>


<commit_before>// Package srest contains utilyties for sites creation and web services.
/*
	RESTfuler interface:
		One(w http.ResponseWriter, r *http.Request)
		List(w http.ResponseWriter, r *http.Request)
		Create(w http.ResponseWriter, r *http.Request)
		Update(w http.ResponseWriter, r *http.Request)
		Delete(w http.ResponseWriter, r *http.Request)

	Modeler interface:
		IsValid() error
*/
// The MIT License (MIT)
//
// Copyright (c) 2016 Angel Del Castillo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package srest

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"syscall"

	"github.com/bmizerany/pat"
	"github.com/gorilla/schema"
)

var (
	debug        bool
	templatesDir string

	// DefaultFuncMap can be used with LoadViews for common template tasks like:
	//
	// cap: capitalize strings
	// eqs: compare value of two types.
	DefaultFuncMap = deffuncmap()

	// ErrModeler error returned when modeler interface is
	// not implemented.
	ErrModeler = errors.New("srest: modeler interface not found")

	// ErrTemplatesInited error returned when LoadViews
	// function is called twice.
	ErrTemplatesInited = errors.New("srest: templates already inited")

	// ErrTemplatesNil error returned when not template files
	// were loaded.
	ErrTemplatesNil = errors.New("srest: not templates found")

	// ErrTemplateNotFound error returned when template name
	// is not present.
	ErrTemplateNotFound = errors.New("srest: template not found")
)

func deffuncmap() template.FuncMap {
	return template.FuncMap{
		"cap": func(s string) string {
			if len(s) < 1 {
				return s
			}
			return strings.ToUpper(s[:1]) + s[1:]
		},
		// eqs validates x and y are equal no matter type.
		"eqs": func(x, y interface{}) bool {
			return fmt.Sprintf("%v", x) == fmt.Sprintf("%v", y)
		},
	}
}

// Options struct
type Options struct {
	UseTLS bool
	TLSCer string
	TLSKey string
}

var (
	// DefaultConf contains default configuration without TLS.
	DefaultConf = &Options{
		UseTLS: false,
	}
)

// SREST struct.
type SREST struct {
	Mux     *pat.PatternServeMux
	Options *Options
}

// New returns a new server.
func New(options *Options) *SREST {
	if options == nil {
		options = DefaultConf
	}
	m := &SREST{
		Mux:     pat.New(),
		Options: options,
	}
	return m
}

// Get wrapper allows GET endpoints and middlewares. It will
// generate endpoints for `resource` and `resource/` because
// some services requires both endpoints.
func (m *SREST) Get(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	// FIXME; allow both o remove one?
	m.Mux.Get(path.Clean(uri), ChainHandler(hf, mws...))
	m.Mux.Get(path.Clean(uri)+"/", ChainHandler(hf, mws...))
}

// Post wrapper useful for add middleware like Use method.
func (m *SREST) Post(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Post(path.Clean(uri), ChainHandler(hf, mws...))
}

// Put wrapper useful for add middleware like Use method.
func (m *SREST) Put(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Put(path.Clean(uri), ChainHandler(hf, mws...))
}

// Del wrapper useful for add middleware like Use method.
func (m *SREST) Del(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Del(path.Clean(uri), ChainHandler(hf, mws...))
}

// Use receives a RESTfuler interface and generates endpoints for:
//
// GET /:id
// GET /
// POST /
// PUT /:id
// DELETE /:id
func (m *SREST) Use(uri string, n RESTfuler, mws ...func(http.Handler) http.Handler) {
	m.Get(uri+"/:id", http.HandlerFunc(n.One), mws...)
	m.Get(uri, http.HandlerFunc(n.List), mws...)
	m.Post(uri, http.HandlerFunc(n.Create), mws...)
	m.Put(uri+"/:id", http.HandlerFunc(n.Update), mws...)
	m.Del(uri+"/:id", http.HandlerFunc(n.Delete), mws...)
}

// Run start a server listening with http.ListenAndServe or http.ListenAndServeTLS
// returns a channel bind it to SIGTERM and SIGINT signal
// you will block this way: <-m.Run()
func (m *SREST) Run(port int) chan os.Signal {
	// TODO; change logic to allow server stop without leaking a goroutine and handle graceful shutdown.
	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		addrs := fmt.Sprintf(":%v", port)
		log.Printf("srest: Run %v", addrs)
		var err error
		if m.Options.UseTLS {
			err = http.ListenAndServeTLS(addrs, m.Options.TLSCer, m.Options.TLSKey, m.Mux)
		} else {
			err = http.ListenAndServe(addrs, m.Mux)
		}
		if err != nil {
			log.Printf("srest: Run : ListenAndServe : err [%s]", err)
		}
	}()
	return c
}

// Debug enables template files reload on every request.
func (m *SREST) Debug(ok bool) {
	debug = ok
}

// Debug enables template files reload on every request.
func Debug(ok bool) {
	debug = ok
}

// Static handler.
//
// Usage:
// Get("/public", Static("/public", "mydir"))
func Static(uri, dir string) http.Handler {
	uri = path.Clean(uri) + "/"
	dir = path.Clean(dir) + "/"
	return http.StripPrefix(uri, http.FileServer(http.Dir(dir)))
}

// ChainHandler concats multiple handlers in one http.Handler.
func ChainHandler(fh http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
	// no middlewares then return handler
	if len(mws) < 1 {
		return fh
	}

	var cs []func(http.Handler) http.Handler
	cs = append(cs, mws...)
	var h http.Handler
	h = fh // this disable linter warning
	for i := range cs {
		h = cs[len(cs)-1-i](h)
	}
	return h
}

// RESTfuler interface
type RESTfuler interface {
	Create(w http.ResponseWriter, r *http.Request)
	One(w http.ResponseWriter, r *http.Request)
	List(w http.ResponseWriter, r *http.Request)
	Update(w http.ResponseWriter, r *http.Request)
	Delete(w http.ResponseWriter, r *http.Request)
}

var (
	// schDecoder default gorilla schema decoder.
	schDecoder = schema.NewDecoder()
)

// Bind implements gorilla schema and runs IsValid method from data.
func Bind(vars url.Values, dst interface{}) error {
	err := schDecoder.Decode(dst, vars)
	if err != nil {
		return err
	}
	// check model is valid
	mo, ok := dst.(Modeler)
	if !ok {
		return ErrModeler
	}
	return mo.IsValid()
}

// JSON writes v to response writer.
func JSON(w http.ResponseWriter, v interface{}) error {
	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(v)
}

var (
	// templates collection.
	templates  = map[string]*template.Template{}
	tmplInited bool
	mut        sync.RWMutex
)

// LoadViews read html files on dir tree and parses it
// as templates.
// In order to render templates you need to call Render
// function passing <file.html> or <subdir>/<file.html>
// as name for template.
//
// funcMap will overwrite DefaultFuncMap.
func LoadViews(dir string, funcMap template.FuncMap) error {
	if tmplInited {
		return ErrTemplatesInited
	}

	dir = filepath.Clean(dir)
	templatesDir = dir

	var files []string
	var data []byte
	err := filepath.Walk(dir, func(name string, info os.FileInfo, err error) error {
		// take template name from subdir+filename
		tname := strings.Replace(name, dir+"/", "", -1)
		ext := filepath.Ext(name)
		// ommit files not .html
		if ext != ".html" {
			return nil
		}
		b, err := ioutil.ReadFile(name)
		if err != nil {
			return err
		}
		// append to unique template data
		data = append(data, []byte(fmt.Sprintf(`{{define "%s"}}`, tname))...)
		data = append(data, b...)
		data = append(data, []byte(`{{end}}`)...)
		// wee need this after for template parsing
		files = append(files, tname)
		return nil
	})
	if err != nil {
		return err
	}

	DefaultFuncMap = funcMap
	for _, k := range files {
		// template parsing
		templates[k] = template.Must(template.New(k).Funcs(funcMap).Parse(string(data)))
	}

	tmplInited = true
	return nil
}

// Render writes a template to http response.
// In order to render templates you need to call Render
// function passing <file.html> or <subdir>/<file.html>
// as name for template.
func Render(w http.ResponseWriter, name string, v interface{}) error {
	// for now use a mutex, later implementations can use sync.Pool of templates.
	mut.RLock()
	defer mut.RUnlock()

	if debug {
		// clean templates
		for k := range templates {
			delete(templates, k)
		}
		tmplInited = false
		// load templates again
		// this generates a race condition. TODO; check later if a really trouble
		// on debug mode, this is not expected to be turned on into production.
		err := LoadViews(templatesDir, DefaultFuncMap)
		if err != nil {
			return err
		}
	}
	if !tmplInited {
		return ErrTemplatesNil
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	// write template to buffer to make sure is working.
	var buf bytes.Buffer
	t, ok := templates[name]
	if !ok {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("template not found"))
		return ErrTemplateNotFound
	}
	err := t.ExecuteTemplate(&buf, name, v)
	if err != nil {
		return err
	}
	// buffer writing was done without errors. Write to http
	// response.
	_, err = buf.WriteTo(w)
	return err
}

// Modeler interface
type Modeler interface {
	IsValid() error
}
<commit_msg>unexported chain<commit_after>// Package srest contains utilyties for sites creation and web services.
/*
	RESTfuler interface:
		One(w http.ResponseWriter, r *http.Request)
		List(w http.ResponseWriter, r *http.Request)
		Create(w http.ResponseWriter, r *http.Request)
		Update(w http.ResponseWriter, r *http.Request)
		Delete(w http.ResponseWriter, r *http.Request)

	Modeler interface:
		IsValid() error
*/
// The MIT License (MIT)
//
// Copyright (c) 2016 Angel Del Castillo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package srest

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path"
	"path/filepath"
	"strings"
	"sync"
	"syscall"

	"github.com/bmizerany/pat"
	"github.com/gorilla/schema"
)

var (
	debug        bool
	templatesDir string

	// DefaultFuncMap can be used with LoadViews for common template tasks like:
	//
	// cap: capitalize strings
	// eqs: compare value of two types.
	DefaultFuncMap = deffuncmap()

	// ErrModeler error returned when modeler interface is
	// not implemented.
	ErrModeler = errors.New("srest: modeler interface not found")

	// ErrTemplatesInited error returned when LoadViews
	// function is called twice.
	ErrTemplatesInited = errors.New("srest: templates already inited")

	// ErrTemplatesNil error returned when not template files
	// were loaded.
	ErrTemplatesNil = errors.New("srest: not templates found")

	// ErrTemplateNotFound error returned when template name
	// is not present.
	ErrTemplateNotFound = errors.New("srest: template not found")
)

func deffuncmap() template.FuncMap {
	return template.FuncMap{
		"cap": func(s string) string {
			if len(s) < 1 {
				return s
			}
			return strings.ToUpper(s[:1]) + s[1:]
		},
		// eqs validates x and y are equal no matter type.
		"eqs": func(x, y interface{}) bool {
			return fmt.Sprintf("%v", x) == fmt.Sprintf("%v", y)
		},
	}
}

// Options struct
type Options struct {
	UseTLS bool
	TLSCer string
	TLSKey string
}

var (
	// DefaultConf contains default configuration without TLS.
	DefaultConf = &Options{
		UseTLS: false,
	}
)

// SREST struct.
type SREST struct {
	Mux     *pat.PatternServeMux
	Options *Options
}

// New returns a new server.
func New(options *Options) *SREST {
	if options == nil {
		options = DefaultConf
	}
	m := &SREST{
		Mux:     pat.New(),
		Options: options,
	}
	return m
}

// Get wrapper allows GET endpoints and middlewares. It will
// generate endpoints for `resource` and `resource/` because
// some services requires both endpoints.
func (m *SREST) Get(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	// FIXME; allow both o remove one?
	m.Mux.Get(path.Clean(uri), chainHandler(hf, mws...))
	m.Mux.Get(path.Clean(uri)+"/", chainHandler(hf, mws...))
}

// Post wrapper useful for add middleware like Use method.
func (m *SREST) Post(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Post(path.Clean(uri), chainHandler(hf, mws...))
}

// Put wrapper useful for add middleware like Use method.
func (m *SREST) Put(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Put(path.Clean(uri), chainHandler(hf, mws...))
}

// Del wrapper useful for add middleware like Use method.
func (m *SREST) Del(uri string, hf http.Handler, mws ...func(http.Handler) http.Handler) {
	m.Mux.Del(path.Clean(uri), chainHandler(hf, mws...))
}

// Use receives a RESTfuler interface and generates endpoints for:
//
// GET /:id
// GET /
// POST /
// PUT /:id
// DELETE /:id
func (m *SREST) Use(uri string, n RESTfuler, mws ...func(http.Handler) http.Handler) {
	m.Get(uri+"/:id", http.HandlerFunc(n.One), mws...)
	m.Get(uri, http.HandlerFunc(n.List), mws...)
	m.Post(uri, http.HandlerFunc(n.Create), mws...)
	m.Put(uri+"/:id", http.HandlerFunc(n.Update), mws...)
	m.Del(uri+"/:id", http.HandlerFunc(n.Delete), mws...)
}

// Run start a server listening with http.ListenAndServe or http.ListenAndServeTLS
// returns a channel bind it to SIGTERM and SIGINT signal
// you will block this way: <-m.Run()
func (m *SREST) Run(port int) chan os.Signal {
	// TODO; change logic to allow server stop without leaking a goroutine and handle graceful shutdown.
	c := make(chan os.Signal)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
	go func() {
		addrs := fmt.Sprintf(":%v", port)
		log.Printf("srest: Run %v", addrs)
		var err error
		if m.Options.UseTLS {
			err = http.ListenAndServeTLS(addrs, m.Options.TLSCer, m.Options.TLSKey, m.Mux)
		} else {
			err = http.ListenAndServe(addrs, m.Mux)
		}
		if err != nil {
			log.Printf("srest: Run : ListenAndServe : err [%s]", err)
		}
	}()
	return c
}

// Debug enables template files reload on every request.
func (m *SREST) Debug(ok bool) {
	debug = ok
}

// Debug enables template files reload on every request.
func Debug(ok bool) {
	debug = ok
}

// Static handler.
//
// Usage:
// Get("/public", Static("/public", "mydir"))
func Static(uri, dir string) http.Handler {
	uri = path.Clean(uri) + "/"
	dir = path.Clean(dir) + "/"
	return http.StripPrefix(uri, http.FileServer(http.Dir(dir)))
}

// chainHandler concats multiple handlers in one http.Handler.
func chainHandler(fh http.Handler, mws ...func(http.Handler) http.Handler) http.Handler {
	// no middlewares then return handler
	if len(mws) < 1 {
		return fh
	}

	var cs []func(http.Handler) http.Handler
	cs = append(cs, mws...)
	var h http.Handler
	h = fh // this disable linter warning
	for i := range cs {
		h = cs[len(cs)-1-i](h)
	}
	return h
}

// RESTfuler interface
type RESTfuler interface {
	Create(w http.ResponseWriter, r *http.Request)
	One(w http.ResponseWriter, r *http.Request)
	List(w http.ResponseWriter, r *http.Request)
	Update(w http.ResponseWriter, r *http.Request)
	Delete(w http.ResponseWriter, r *http.Request)
}

var (
	// schDecoder default gorilla schema decoder.
	schDecoder = schema.NewDecoder()
)

// Bind implements gorilla schema and runs IsValid method from data.
func Bind(vars url.Values, dst interface{}) error {
	err := schDecoder.Decode(dst, vars)
	if err != nil {
		return err
	}
	// check model is valid
	mo, ok := dst.(Modeler)
	if !ok {
		return ErrModeler
	}
	return mo.IsValid()
}

// JSON writes v to response writer.
func JSON(w http.ResponseWriter, v interface{}) error {
	w.Header().Set("Content-Type", "application/json")
	return json.NewEncoder(w).Encode(v)
}

var (
	// templates collection.
	templates  = map[string]*template.Template{}
	tmplInited bool
	mut        sync.RWMutex
)

// LoadViews read html files on dir tree and parses it
// as templates.
// In order to render templates you need to call Render
// function passing <file.html> or <subdir>/<file.html>
// as name for template.
//
// funcMap will overwrite DefaultFuncMap.
func LoadViews(dir string, funcMap template.FuncMap) error {
	if tmplInited {
		return ErrTemplatesInited
	}

	dir = filepath.Clean(dir)
	templatesDir = dir

	var files []string
	var data []byte
	err := filepath.Walk(dir, func(name string, info os.FileInfo, err error) error {
		// take template name from subdir+filename
		tname := strings.Replace(name, dir+"/", "", -1)
		ext := filepath.Ext(name)
		// ommit files not .html
		if ext != ".html" {
			return nil
		}
		b, err := ioutil.ReadFile(name)
		if err != nil {
			return err
		}
		// append to unique template data
		data = append(data, []byte(fmt.Sprintf(`{{define "%s"}}`, tname))...)
		data = append(data, b...)
		data = append(data, []byte(`{{end}}`)...)
		// wee need this after for template parsing
		files = append(files, tname)
		return nil
	})
	if err != nil {
		return err
	}

	DefaultFuncMap = funcMap
	for _, k := range files {
		// template parsing
		templates[k] = template.Must(template.New(k).Funcs(funcMap).Parse(string(data)))
	}

	tmplInited = true
	return nil
}

// Render writes a template to http response.
// In order to render templates you need to call Render
// function passing <file.html> or <subdir>/<file.html>
// as name for template.
func Render(w http.ResponseWriter, name string, v interface{}) error {
	// for now use a mutex, later implementations can use sync.Pool of templates.
	mut.RLock()
	defer mut.RUnlock()

	if debug {
		// clean templates
		for k := range templates {
			delete(templates, k)
		}
		tmplInited = false
		// load templates again
		// this generates a race condition. TODO; check later if a really trouble
		// on debug mode, this is not expected to be turned on into production.
		err := LoadViews(templatesDir, DefaultFuncMap)
		if err != nil {
			return err
		}
	}
	if !tmplInited {
		return ErrTemplatesNil
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	// write template to buffer to make sure is working.
	var buf bytes.Buffer
	t, ok := templates[name]
	if !ok {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte("template not found"))
		return ErrTemplateNotFound
	}
	err := t.ExecuteTemplate(&buf, name, v)
	if err != nil {
		return err
	}
	// buffer writing was done without errors. Write to http
	// response.
	_, err = buf.WriteTo(w)
	return err
}

// Modeler interface
type Modeler interface {
	IsValid() error
}
<|endoftext|>


<commit_before>package boardgame

import (
	"encoding/json"
)

//State represents the entire semantic state of a game at a given version. For
//your specific game, Game and Players will actually be concrete structs to
//your particular game. Games often define a top-level concreteStates()
//*myGameState, []*myPlayerState so at the top of methods that accept a State
//they can quickly get concrete, type-checked types with only a single
//conversion leap of faith at the top. States are intended to be read-only;
//methods where you are allowed to mutate the state (e.g. Move.Apply()) will
//take a MutableState instead as a signal that it is permissable to modify the
//state. That is why the states only return non-mutable states
//(PropertyReaders, not PropertyReadSetters, although realistically it is
//possible to cast them and modify directly. The MarshalJSON output of a State
//is appropriate for sending to a client or serializing a state to be put in
//storage. Given a blob serialized in that fashion, GameManager.StateFromBlob
//will return a state.
type State interface {
	//Game returns the GameState for this State
	Game() GameState
	//Players returns a slice of all PlayerStates for this State
	Players() []PlayerState
	//DynamicComponentValues returns a map of deck name to array of component
	//values, one per component in that deck.
	DynamicComponentValues() map[string][]DynamicComponentValues
	//Copy returns a deep copy of the State, including copied version of the Game
	//and Player States.
	Copy(sanitized bool) State
	//Diagram returns a basic, ascii rendering of the state for debug rendering.
	//It thunks out to Delegate.Diagram.
	Diagram() string
	//Santizied will return false if this is a full-fidelity State object, or
	//true if it has been sanitized, which means that some properties might be
	//hidden or otherwise altered. This should return true if the object was
	//created with Copy(true)
	Sanitized() bool
	//Computed returns the computed properties for this state.
	Computed() ComputedProperties
	//SanitizedForPlayer produces a copy state object that has been sanitized for
	//the player at the given index. The state object returned will have
	//Sanitized() return true. Will call GameDelegate.StateSanitizationPolicy to
	//retrieve the policy in place. See the package level comment for an overview
	//of how state sanitization works.
	SanitizedForPlayer(playerIndex int) State
}

//A MutableState is a state that is designed to be modified in place. These
//are passed to methods (instead of normal States) as a signal that
//modifications are intended to be done on the state.
type MutableState interface {
	//MutableState contains all of the methods of a read-only state.
	State
	//MutableGame is a reference to the MutableGameState for this MutableState.
	MutableGame() MutableGameState
	//MutablePlayers returns a slice of MutablePlayerStates for this MutableState.
	MutablePlayers() []MutablePlayerState
}

//state implements both State and MutableState, so it can always be passed for
//either, and what it's interpreted as is primarily a function of what the
//method signature is that it's passed to
type state struct {
	game                   MutableGameState
	players                []MutablePlayerState
	computed               *computedPropertiesImpl
	dynamicComponentValues map[string][]DynamicComponentValues
	sanitized              bool
	delegate               GameDelegate
}

func (s *state) MutableGame() MutableGameState {
	return s.game
}

func (s *state) MutablePlayers() []MutablePlayerState {
	return s.players
}

func (s *state) Game() GameState {
	return s.game
}

func (s *state) Players() []PlayerState {
	result := make([]PlayerState, len(s.players))
	for i := 0; i < len(s.players); i++ {
		result[i] = s.players[i]
	}
	return result
}

func (s *state) Copy(sanitized bool) State {
	return s.copy(sanitized)
}

func (s *state) copy(sanitized bool) *state {
	players := make([]MutablePlayerState, len(s.players))

	for i, player := range s.players {
		players[i] = player.MutableCopy()
	}

	result := &state{
		game:                   s.game.MutableCopy(),
		players:                players,
		dynamicComponentValues: make(map[string][]DynamicComponentValues),
		sanitized:              sanitized,
		delegate:               s.delegate,
	}

	//TODO: fix up stacks for component values

	for deckName, values := range s.dynamicComponentValues {
		arr := make([]DynamicComponentValues, len(values))
		for i := 0; i < len(values); i++ {
			arr[i] = values[i].Copy()
		}
		result.dynamicComponentValues[deckName] = arr
	}

	//FixUp stacks to make sure they point to this new state.
	if err := verifyReaderStacks(result.game.Reader(), result); err != nil {
		return nil
	}
	for _, player := range result.players {
		if err := verifyReaderStacks(player.Reader(), result); err != nil {
			return nil
		}
	}

	return result
}

func (s *state) MarshalJSON() ([]byte, error) {
	obj := map[string]interface{}{
		"Game":     s.game,
		"Players":  s.players,
		"Computed": s.Computed(),
	}

	dynamic := s.DynamicComponentValues()

	if dynamic != nil && len(dynamic) != 0 {
		obj["Components"] = dynamic
	}

	return json.Marshal(obj)
}

func (s *state) Diagram() string {
	return s.delegate.Diagram(s)
}

func (s *state) Sanitized() bool {
	return s.sanitized
}

func (s *state) DynamicComponentValues() map[string][]DynamicComponentValues {
	return s.dynamicComponentValues
}

func (s *state) Computed() ComputedProperties {
	if s.computed == nil {
		s.computed = newComputedPropertiesImpl(s.delegate.ComputedPropertiesConfig(), s)
	}
	return s.computed
}

func (s *state) SanitizedForPlayer(playerIndex int) State {

	//If the playerIndex isn't an actuall player's index, just return self.
	if playerIndex < 0 || playerIndex >= len(s.players) {
		return s
	}

	policy := s.delegate.StateSanitizationPolicy()

	if policy == nil {
		policy = &StatePolicy{}
	}

	sanitized := s.copy(true)

	sanitizeStateObj(sanitized.game.ReadSetter(), policy.Game, -1, playerIndex, PolicyVisible)

	playerStates := sanitized.players

	for i := 0; i < len(playerStates); i++ {
		sanitizeStateObj(playerStates[i].ReadSetter(), policy.Player, i, playerIndex, PolicyVisible)
	}

	return sanitized

}

//sanitizedWithExceptions will return a Sanitized() State where properties
//that are not in the passed policy are treated as PolicyRandom. Useful in
//computing properties.
func (s *state) sanitizedWithExceptions(policy *StatePolicy) State {

	sanitized := s.copy(true)

	sanitizeStateObj(sanitized.game.ReadSetter(), policy.Game, -1, -1, PolicyRandom)

	playerStates := sanitized.players

	for i := 0; i < len(playerStates); i++ {
		sanitizeStateObj(playerStates[i].ReadSetter(), policy.Player, -1, -1, PolicyRandom)
	}

	return sanitized

}

//BaseState is the interface that all state objects--PlayerStates and GameStates
//--implement.
type BaseState interface {
	Reader() PropertyReader
}

//MutableBaseState is the interface that Mutable{Game,Player}State's
//implement.
type MutableBaseState interface {
	ReadSetter() PropertyReadSetter
}

//PlayerState represents the state of a game associated with a specific user.
type PlayerState interface {
	//PlayerIndex encodes the index this user's state is in the containing
	//state object.
	PlayerIndex() int
	//Copy produces a copy of our current state. Be sure it's a deep copy that
	//makes a copy of any pointer arguments.
	Copy() PlayerState
	BaseState
}

//A MutablePlayerState is a PlayerState that is allowed to be mutated.
type MutablePlayerState interface {
	PlayerState
	MutableCopy() MutablePlayerState
	MutableBaseState
}

//GameState represents the state of a game that is not associated with a
//particular user. For example, the draw stack of cards, who the current
//player is, and other properites.
type GameState interface {
	//Copy returns a copy of our current state. Be sure it's a deep copy that
	//makes a copy of any pointer arguments.
	Copy() GameState
	BaseState
}

//A MutableGameState is a GameState that is allowed to be mutated.
type MutableGameState interface {
	GameState
	MutableCopy() MutableGameState
	MutableBaseState
}

//DefaultMarshalJSON is a simple wrapper around json.MarshalIndent, with the
//right defaults set. If your structs need to implement MarshaLJSON to output
//JSON, use this to encode it.
func DefaultMarshalJSON(obj interface{}) ([]byte, error) {
	return json.MarshalIndent(obj, "", "  ")
}
<commit_msg>state.Copy() fixes up component value stacks to point to new state.<commit_after>package boardgame

import (
	"encoding/json"
)

//State represents the entire semantic state of a game at a given version. For
//your specific game, Game and Players will actually be concrete structs to
//your particular game. Games often define a top-level concreteStates()
//*myGameState, []*myPlayerState so at the top of methods that accept a State
//they can quickly get concrete, type-checked types with only a single
//conversion leap of faith at the top. States are intended to be read-only;
//methods where you are allowed to mutate the state (e.g. Move.Apply()) will
//take a MutableState instead as a signal that it is permissable to modify the
//state. That is why the states only return non-mutable states
//(PropertyReaders, not PropertyReadSetters, although realistically it is
//possible to cast them and modify directly. The MarshalJSON output of a State
//is appropriate for sending to a client or serializing a state to be put in
//storage. Given a blob serialized in that fashion, GameManager.StateFromBlob
//will return a state.
type State interface {
	//Game returns the GameState for this State
	Game() GameState
	//Players returns a slice of all PlayerStates for this State
	Players() []PlayerState
	//DynamicComponentValues returns a map of deck name to array of component
	//values, one per component in that deck.
	DynamicComponentValues() map[string][]DynamicComponentValues
	//Copy returns a deep copy of the State, including copied version of the Game
	//and Player States.
	Copy(sanitized bool) State
	//Diagram returns a basic, ascii rendering of the state for debug rendering.
	//It thunks out to Delegate.Diagram.
	Diagram() string
	//Santizied will return false if this is a full-fidelity State object, or
	//true if it has been sanitized, which means that some properties might be
	//hidden or otherwise altered. This should return true if the object was
	//created with Copy(true)
	Sanitized() bool
	//Computed returns the computed properties for this state.
	Computed() ComputedProperties
	//SanitizedForPlayer produces a copy state object that has been sanitized for
	//the player at the given index. The state object returned will have
	//Sanitized() return true. Will call GameDelegate.StateSanitizationPolicy to
	//retrieve the policy in place. See the package level comment for an overview
	//of how state sanitization works.
	SanitizedForPlayer(playerIndex int) State
}

//A MutableState is a state that is designed to be modified in place. These
//are passed to methods (instead of normal States) as a signal that
//modifications are intended to be done on the state.
type MutableState interface {
	//MutableState contains all of the methods of a read-only state.
	State
	//MutableGame is a reference to the MutableGameState for this MutableState.
	MutableGame() MutableGameState
	//MutablePlayers returns a slice of MutablePlayerStates for this MutableState.
	MutablePlayers() []MutablePlayerState
}

//state implements both State and MutableState, so it can always be passed for
//either, and what it's interpreted as is primarily a function of what the
//method signature is that it's passed to
type state struct {
	game                   MutableGameState
	players                []MutablePlayerState
	computed               *computedPropertiesImpl
	dynamicComponentValues map[string][]DynamicComponentValues
	sanitized              bool
	delegate               GameDelegate
}

func (s *state) MutableGame() MutableGameState {
	return s.game
}

func (s *state) MutablePlayers() []MutablePlayerState {
	return s.players
}

func (s *state) Game() GameState {
	return s.game
}

func (s *state) Players() []PlayerState {
	result := make([]PlayerState, len(s.players))
	for i := 0; i < len(s.players); i++ {
		result[i] = s.players[i]
	}
	return result
}

func (s *state) Copy(sanitized bool) State {
	return s.copy(sanitized)
}

func (s *state) copy(sanitized bool) *state {
	players := make([]MutablePlayerState, len(s.players))

	for i, player := range s.players {
		players[i] = player.MutableCopy()
	}

	result := &state{
		game:                   s.game.MutableCopy(),
		players:                players,
		dynamicComponentValues: make(map[string][]DynamicComponentValues),
		sanitized:              sanitized,
		delegate:               s.delegate,
	}

	for deckName, values := range s.dynamicComponentValues {
		arr := make([]DynamicComponentValues, len(values))
		for i := 0; i < len(values); i++ {
			arr[i] = values[i].Copy()
			if err := verifyReaderStacks(arr[i].Reader(), result); err != nil {
				return nil
			}
		}
		result.dynamicComponentValues[deckName] = arr
	}

	//FixUp stacks to make sure they point to this new state.
	if err := verifyReaderStacks(result.game.Reader(), result); err != nil {
		return nil
	}
	for _, player := range result.players {
		if err := verifyReaderStacks(player.Reader(), result); err != nil {
			return nil
		}
	}

	return result
}

func (s *state) MarshalJSON() ([]byte, error) {
	obj := map[string]interface{}{
		"Game":     s.game,
		"Players":  s.players,
		"Computed": s.Computed(),
	}

	dynamic := s.DynamicComponentValues()

	if dynamic != nil && len(dynamic) != 0 {
		obj["Components"] = dynamic
	}

	return json.Marshal(obj)
}

func (s *state) Diagram() string {
	return s.delegate.Diagram(s)
}

func (s *state) Sanitized() bool {
	return s.sanitized
}

func (s *state) DynamicComponentValues() map[string][]DynamicComponentValues {
	return s.dynamicComponentValues
}

func (s *state) Computed() ComputedProperties {
	if s.computed == nil {
		s.computed = newComputedPropertiesImpl(s.delegate.ComputedPropertiesConfig(), s)
	}
	return s.computed
}

func (s *state) SanitizedForPlayer(playerIndex int) State {

	//If the playerIndex isn't an actuall player's index, just return self.
	if playerIndex < 0 || playerIndex >= len(s.players) {
		return s
	}

	policy := s.delegate.StateSanitizationPolicy()

	if policy == nil {
		policy = &StatePolicy{}
	}

	sanitized := s.copy(true)

	sanitizeStateObj(sanitized.game.ReadSetter(), policy.Game, -1, playerIndex, PolicyVisible)

	playerStates := sanitized.players

	for i := 0; i < len(playerStates); i++ {
		sanitizeStateObj(playerStates[i].ReadSetter(), policy.Player, i, playerIndex, PolicyVisible)
	}

	return sanitized

}

//sanitizedWithExceptions will return a Sanitized() State where properties
//that are not in the passed policy are treated as PolicyRandom. Useful in
//computing properties.
func (s *state) sanitizedWithExceptions(policy *StatePolicy) State {

	sanitized := s.copy(true)

	sanitizeStateObj(sanitized.game.ReadSetter(), policy.Game, -1, -1, PolicyRandom)

	playerStates := sanitized.players

	for i := 0; i < len(playerStates); i++ {
		sanitizeStateObj(playerStates[i].ReadSetter(), policy.Player, -1, -1, PolicyRandom)
	}

	return sanitized

}

//BaseState is the interface that all state objects--PlayerStates and GameStates
//--implement.
type BaseState interface {
	Reader() PropertyReader
}

//MutableBaseState is the interface that Mutable{Game,Player}State's
//implement.
type MutableBaseState interface {
	ReadSetter() PropertyReadSetter
}

//PlayerState represents the state of a game associated with a specific user.
type PlayerState interface {
	//PlayerIndex encodes the index this user's state is in the containing
	//state object.
	PlayerIndex() int
	//Copy produces a copy of our current state. Be sure it's a deep copy that
	//makes a copy of any pointer arguments.
	Copy() PlayerState
	BaseState
}

//A MutablePlayerState is a PlayerState that is allowed to be mutated.
type MutablePlayerState interface {
	PlayerState
	MutableCopy() MutablePlayerState
	MutableBaseState
}

//GameState represents the state of a game that is not associated with a
//particular user. For example, the draw stack of cards, who the current
//player is, and other properites.
type GameState interface {
	//Copy returns a copy of our current state. Be sure it's a deep copy that
	//makes a copy of any pointer arguments.
	Copy() GameState
	BaseState
}

//A MutableGameState is a GameState that is allowed to be mutated.
type MutableGameState interface {
	GameState
	MutableCopy() MutableGameState
	MutableBaseState
}

//DefaultMarshalJSON is a simple wrapper around json.MarshalIndent, with the
//right defaults set. If your structs need to implement MarshaLJSON to output
//JSON, use this to encode it.
func DefaultMarshalJSON(obj interface{}) ([]byte, error) {
	return json.MarshalIndent(obj, "", "  ")
}
<|endoftext|>