<commit_before>package terraform

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// DebugInfo is the global handler for writing the debug archive. All methods
// are safe to call concurrently. Setting DebugInfo to nil will disable writing
// the debug archive. All methods are safe to call on the nil value.
var dbug *debugInfo

// SetDebugInfo initializes the debug handler with a backing file in the
// provided directory. This must be called before any other terraform package
// operations or not at all. Once his is called, CloseDebugInfo should be
// called before program exit.
func SetDebugInfo(path string) error {
	if os.Getenv("TF_DEBUG") == "" {
		return nil
	}

	di, err := newDebugInfoFile(path)
	if err != nil {
		return err
	}

	dbug = di
	return nil
}

// CloseDebugInfo is the exported interface to Close the debug info handler.
// The debug handler needs to be closed before program exit, so we export this
// function to be deferred in the appropriate entrypoint for our executable.
func CloseDebugInfo() error {
	return dbug.Close()
}

// newDebugInfoFile initializes the global debug handler with a backing file in
// the provided directory.
func newDebugInfoFile(dir string) (*debugInfo, error) {
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		return nil, err
	}

	// FIXME: not guaranteed unique, but good enough for now
	name := fmt.Sprintf("debug-%s", time.Now().Format("2006-01-02-15-04-05.999999999"))
	archivePath := filepath.Join(dir, name+".tar.gz")

	f, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
	if err != nil {
		return nil, err
	}
	return newDebugInfo(name, f)
}

// newDebugInfo initializes the global debug handler.
func newDebugInfo(name string, w io.Writer) (*debugInfo, error) {
	gz := gzip.NewWriter(w)

	d := &debugInfo{
		name: name,
		w:    w,
		gz:   gz,
		tar:  tar.NewWriter(gz),
	}

	// create the subdirs we need
	topHdr := &tar.Header{
		Name:     name,
		Typeflag: tar.TypeDir,
		Mode:     0755,
	}
	graphsHdr := &tar.Header{
		Name:     name + "/graphs",
		Typeflag: tar.TypeDir,
		Mode:     0755,
	}
	err := d.tar.WriteHeader(topHdr)
	// if the first errors, the second will too
	err = d.tar.WriteHeader(graphsHdr)
	if err != nil {
		return nil, err
	}

	return d, nil
}

// debugInfo provides various methods for writing debug information to a
// central archive. The debugInfo struct should be initialized once before any
// output is written, and Close should be called before program exit. All
// exported methods on debugInfo will be safe for concurrent use. The exported
// methods are also all safe to call on a nil pointer, so that there is no need
// for conditional blocks before writing debug information.
//
// Each write operation done by the debugInfo will flush the gzip.Writer and
// tar.Writer, and call Sync() or Flush() on the output writer as needed. This
// ensures that as much data as possible is written to storage in the event of
// a crash. The append format of the tar file, and the stream format of the
// gzip writer allow easy recovery f the data in the event that the debugInfo
// is not closed before program exit.
type debugInfo struct {
	sync.Mutex

	// archive root directory name
	name string

	// current operation phase
	phase string

	// step is monotonic counter for for recording the order of operations
	step int

	// flag to protect Close()
	closed bool

	// the debug log output is in a tar.gz format, written to the io.Writer w
	w   io.Writer
	gz  *gzip.Writer
	tar *tar.Writer
}

// Set the name of the current operational phase in the debug handler. Each file
// in the archive will contain the name of the phase in which it was created,
// i.e. "input", "apply", "plan", "refresh", "validate"
func (d *debugInfo) SetPhase(phase string) {
	if d == nil {
		return
	}
	d.Lock()
	defer d.Unlock()

	d.phase = phase
}

// Close the debugInfo, finalizing the data in storage. This closes the
// tar.Writer, the gzip.Wrtier, and if the output writer is an io.Closer, it is
// also closed.
func (d *debugInfo) Close() error {
	if d == nil {
		return nil
	}

	d.Lock()
	defer d.Unlock()

	if d.closed {
		return nil
	}
	d.closed = true

	d.tar.Close()
	d.gz.Close()

	if c, ok := d.w.(io.Closer); ok {
		return c.Close()
	}
	return nil
}

// debug buffer is an io.WriteCloser that will write itself to the debug
// archive when closed.
type debugBuffer struct {
	debugInfo *debugInfo
	name      string
	buf       bytes.Buffer
}

func (b *debugBuffer) Write(d []byte) (int, error) {
	return b.buf.Write(d)
}

func (b *debugBuffer) Close() error {
	return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
}

// ioutils only has a noop ReadCloser
type nopWriteCloser struct{}

func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
func (nopWriteCloser) Close() error              { return nil }

// NewFileWriter returns an io.WriteClose that will be buffered and written to
// the debug archive when closed.
func (d *debugInfo) NewFileWriter(name string) io.WriteCloser {
	if d == nil {
		return nopWriteCloser{}
	}

	return &debugBuffer{
		debugInfo: d,
		name:      name,
	}
}

type syncer interface {
	Sync() error
}

type flusher interface {
	Flush() error
}

// Flush the tar.Writer and the gzip.Writer. Flush() or Sync() will be called
// on the output writer if they are available.
func (d *debugInfo) flush() {
	d.tar.Flush()
	d.gz.Flush()

	if f, ok := d.w.(flusher); ok {
		f.Flush()
	}

	if s, ok := d.w.(syncer); ok {
		s.Sync()
	}
}

// WriteFile writes data as a single file to the debug arhive.
func (d *debugInfo) WriteFile(name string, data []byte) error {
	if d == nil {
		return nil
	}

	d.Lock()
	defer d.Unlock()
	return d.writeFile(name, data)
}

func (d *debugInfo) writeFile(name string, data []byte) error {
	defer d.flush()
	path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name)
	d.step++

	hdr := &tar.Header{
		Name: path,
		Mode: 0644,
		Size: int64(len(data)),
	}
	err := d.tar.WriteHeader(hdr)
	if err != nil {
		return err
	}

	_, err = d.tar.Write(data)
	return err
}

// DebugHook implements all methods of the terraform.Hook interface, and writes
// the arguments to a file in the archive. When a suitable format for the
// argument isn't available, the argument is encoded using json.Marshal. If the
// debug handler is nil, all DebugHook methods are noop, so no time is spent in
// marshaling the data structures.
type DebugHook struct{}

func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String() + "\n")
	}

	idCopy, err := id.Copy()
	if err != nil {
		return HookActionContinue, err
	}
	js, err := json.MarshalIndent(idCopy, "", "  ")
	if err != nil {
		return HookActionContinue, err
	}
	buf.Write(js)

	dbug.WriteFile("hook-PreApply", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String() + "\n")
	}

	if err != nil {
		buf.WriteString(err.Error())
	}

	dbug.WriteFile("hook-PostApply", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreDiff", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	idCopy, err := id.Copy()
	if err != nil {
		return HookActionContinue, err
	}
	js, err := json.MarshalIndent(idCopy, "", "  ")
	if err != nil {
		return HookActionContinue, err
	}
	buf.Write(js)

	dbug.WriteFile("hook-PostDiff", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreProvisionResource", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PostProvisionResource", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PreProvision", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostProvision(ii *InstanceInfo, s string) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PostProvision", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) {
	if dbug == nil {
		return
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s1 + "\n")
	buf.WriteString(s2 + "\n")

	dbug.WriteFile("hook-ProvisionOutput", buf.Bytes())
}

func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreRefresh", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PostRefresh", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PreImportState", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	for _, is := range iss {
		if is != nil {
			buf.WriteString(is.String() + "\n")
		}
	}
	dbug.WriteFile("hook-PostImportState", buf.Bytes())
	return HookActionContinue, nil
}

// skip logging this for now, since it could be huge
func (*DebugHook) PostStateUpdate(*State) (HookAction, error) {
	return HookActionContinue, nil
}
<commit_msg>terraform: fix compilation by complying with interface<commit_after>package terraform

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"
)

// DebugInfo is the global handler for writing the debug archive. All methods
// are safe to call concurrently. Setting DebugInfo to nil will disable writing
// the debug archive. All methods are safe to call on the nil value.
var dbug *debugInfo

// SetDebugInfo initializes the debug handler with a backing file in the
// provided directory. This must be called before any other terraform package
// operations or not at all. Once his is called, CloseDebugInfo should be
// called before program exit.
func SetDebugInfo(path string) error {
	if os.Getenv("TF_DEBUG") == "" {
		return nil
	}

	di, err := newDebugInfoFile(path)
	if err != nil {
		return err
	}

	dbug = di
	return nil
}

// CloseDebugInfo is the exported interface to Close the debug info handler.
// The debug handler needs to be closed before program exit, so we export this
// function to be deferred in the appropriate entrypoint for our executable.
func CloseDebugInfo() error {
	return dbug.Close()
}

// newDebugInfoFile initializes the global debug handler with a backing file in
// the provided directory.
func newDebugInfoFile(dir string) (*debugInfo, error) {
	err := os.MkdirAll(dir, 0755)
	if err != nil {
		return nil, err
	}

	// FIXME: not guaranteed unique, but good enough for now
	name := fmt.Sprintf("debug-%s", time.Now().Format("2006-01-02-15-04-05.999999999"))
	archivePath := filepath.Join(dir, name+".tar.gz")

	f, err := os.OpenFile(archivePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
	if err != nil {
		return nil, err
	}
	return newDebugInfo(name, f)
}

// newDebugInfo initializes the global debug handler.
func newDebugInfo(name string, w io.Writer) (*debugInfo, error) {
	gz := gzip.NewWriter(w)

	d := &debugInfo{
		name: name,
		w:    w,
		gz:   gz,
		tar:  tar.NewWriter(gz),
	}

	// create the subdirs we need
	topHdr := &tar.Header{
		Name:     name,
		Typeflag: tar.TypeDir,
		Mode:     0755,
	}
	graphsHdr := &tar.Header{
		Name:     name + "/graphs",
		Typeflag: tar.TypeDir,
		Mode:     0755,
	}
	err := d.tar.WriteHeader(topHdr)
	// if the first errors, the second will too
	err = d.tar.WriteHeader(graphsHdr)
	if err != nil {
		return nil, err
	}

	return d, nil
}

// debugInfo provides various methods for writing debug information to a
// central archive. The debugInfo struct should be initialized once before any
// output is written, and Close should be called before program exit. All
// exported methods on debugInfo will be safe for concurrent use. The exported
// methods are also all safe to call on a nil pointer, so that there is no need
// for conditional blocks before writing debug information.
//
// Each write operation done by the debugInfo will flush the gzip.Writer and
// tar.Writer, and call Sync() or Flush() on the output writer as needed. This
// ensures that as much data as possible is written to storage in the event of
// a crash. The append format of the tar file, and the stream format of the
// gzip writer allow easy recovery f the data in the event that the debugInfo
// is not closed before program exit.
type debugInfo struct {
	sync.Mutex

	// archive root directory name
	name string

	// current operation phase
	phase string

	// step is monotonic counter for for recording the order of operations
	step int

	// flag to protect Close()
	closed bool

	// the debug log output is in a tar.gz format, written to the io.Writer w
	w   io.Writer
	gz  *gzip.Writer
	tar *tar.Writer
}

// Set the name of the current operational phase in the debug handler. Each file
// in the archive will contain the name of the phase in which it was created,
// i.e. "input", "apply", "plan", "refresh", "validate"
func (d *debugInfo) SetPhase(phase string) {
	if d == nil {
		return
	}
	d.Lock()
	defer d.Unlock()

	d.phase = phase
}

// Close the debugInfo, finalizing the data in storage. This closes the
// tar.Writer, the gzip.Wrtier, and if the output writer is an io.Closer, it is
// also closed.
func (d *debugInfo) Close() error {
	if d == nil {
		return nil
	}

	d.Lock()
	defer d.Unlock()

	if d.closed {
		return nil
	}
	d.closed = true

	d.tar.Close()
	d.gz.Close()

	if c, ok := d.w.(io.Closer); ok {
		return c.Close()
	}
	return nil
}

// debug buffer is an io.WriteCloser that will write itself to the debug
// archive when closed.
type debugBuffer struct {
	debugInfo *debugInfo
	name      string
	buf       bytes.Buffer
}

func (b *debugBuffer) Write(d []byte) (int, error) {
	return b.buf.Write(d)
}

func (b *debugBuffer) Close() error {
	return b.debugInfo.WriteFile(b.name, b.buf.Bytes())
}

// ioutils only has a noop ReadCloser
type nopWriteCloser struct{}

func (nopWriteCloser) Write([]byte) (int, error) { return 0, nil }
func (nopWriteCloser) Close() error              { return nil }

// NewFileWriter returns an io.WriteClose that will be buffered and written to
// the debug archive when closed.
func (d *debugInfo) NewFileWriter(name string) io.WriteCloser {
	if d == nil {
		return nopWriteCloser{}
	}

	return &debugBuffer{
		debugInfo: d,
		name:      name,
	}
}

type syncer interface {
	Sync() error
}

type flusher interface {
	Flush() error
}

// Flush the tar.Writer and the gzip.Writer. Flush() or Sync() will be called
// on the output writer if they are available.
func (d *debugInfo) flush() {
	d.tar.Flush()
	d.gz.Flush()

	if f, ok := d.w.(flusher); ok {
		f.Flush()
	}

	if s, ok := d.w.(syncer); ok {
		s.Sync()
	}
}

// WriteFile writes data as a single file to the debug arhive.
func (d *debugInfo) WriteFile(name string, data []byte) error {
	if d == nil {
		return nil
	}

	d.Lock()
	defer d.Unlock()
	return d.writeFile(name, data)
}

func (d *debugInfo) writeFile(name string, data []byte) error {
	defer d.flush()
	path := fmt.Sprintf("%s/%d-%s-%s", d.name, d.step, d.phase, name)
	d.step++

	hdr := &tar.Header{
		Name: path,
		Mode: 0644,
		Size: int64(len(data)),
	}
	err := d.tar.WriteHeader(hdr)
	if err != nil {
		return err
	}

	_, err = d.tar.Write(data)
	return err
}

// DebugHook implements all methods of the terraform.Hook interface, and writes
// the arguments to a file in the archive. When a suitable format for the
// argument isn't available, the argument is encoded using json.Marshal. If the
// debug handler is nil, all DebugHook methods are noop, so no time is spent in
// marshaling the data structures.
type DebugHook struct{}

func (*DebugHook) PreApply(ii *InstanceInfo, is *InstanceState, id *InstanceDiff) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String() + "\n")
	}

	idCopy, err := id.Copy()
	if err != nil {
		return HookActionContinue, err
	}
	js, err := json.MarshalIndent(idCopy, "", "  ")
	if err != nil {
		return HookActionContinue, err
	}
	buf.Write(js)

	dbug.WriteFile("hook-PreApply", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostApply(ii *InstanceInfo, is *InstanceState, err error) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String() + "\n")
	}

	if err != nil {
		buf.WriteString(err.Error())
	}

	dbug.WriteFile("hook-PostApply", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PreDiff(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreDiff", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostDiff(ii *InstanceInfo, id *InstanceDiff) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	idCopy, err := id.Copy()
	if err != nil {
		return HookActionContinue, err
	}
	js, err := json.MarshalIndent(idCopy, "", "  ")
	if err != nil {
		return HookActionContinue, err
	}
	buf.Write(js)

	dbug.WriteFile("hook-PostDiff", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PreProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreProvisionResource", buf.Bytes())

	return HookActionContinue, nil
}

func (*DebugHook) PostProvisionResource(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PostProvisionResource", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PreProvision(ii *InstanceInfo, s string) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PreProvision", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostProvision(ii *InstanceInfo, s string, err error) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PostProvision", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) ProvisionOutput(ii *InstanceInfo, s1 string, s2 string) {
	if dbug == nil {
		return
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s1 + "\n")
	buf.WriteString(s2 + "\n")

	dbug.WriteFile("hook-ProvisionOutput", buf.Bytes())
}

func (*DebugHook) PreRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PreRefresh", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostRefresh(ii *InstanceInfo, is *InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}

	if is != nil {
		buf.WriteString(is.String())
		buf.WriteString("\n")
	}
	dbug.WriteFile("hook-PostRefresh", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PreImportState(ii *InstanceInfo, s string) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer
	if ii != nil {
		buf.WriteString(ii.HumanId())
		buf.WriteString("\n")
	}
	buf.WriteString(s + "\n")

	dbug.WriteFile("hook-PreImportState", buf.Bytes())
	return HookActionContinue, nil
}

func (*DebugHook) PostImportState(ii *InstanceInfo, iss []*InstanceState) (HookAction, error) {
	if dbug == nil {
		return HookActionContinue, nil
	}

	var buf bytes.Buffer

	if ii != nil {
		buf.WriteString(ii.HumanId() + "\n")
	}

	for _, is := range iss {
		if is != nil {
			buf.WriteString(is.String() + "\n")
		}
	}
	dbug.WriteFile("hook-PostImportState", buf.Bytes())
	return HookActionContinue, nil
}

// skip logging this for now, since it could be huge
func (*DebugHook) PostStateUpdate(*State) (HookAction, error) {
	return HookActionContinue, nil
}
<|endoftext|>


<commit_before>package chem

import (
	"database/sql"
	"fmt"
	"reflect"
	"strings"
)

type SelectStmt struct {
	columns []Column
	filters []Filter
}

func Select(columnThings ...Columnser) SelectStmt {
	columns := make([]Column, 0, len(columnThings))
	for _, c := range columnThings {
		columns = append(columns, c.Columns()...)
	}

	return SelectStmt{columns: columns}
}

func (stmt SelectStmt) Where(filters ...Filter) SelectStmt {
	stmt.filters = append(stmt.filters, filters...)
	return stmt
}

func toTableNames(columns []Column) []string {
	names := make(map[string]bool)
	for _, c := range columns {
		names[c.Table().Name()] = true
	}
	nameList := make([]string, 0, len(names))
	for name := range names {
		nameList = append(nameList, name)
	}
	return nameList
}

func toColumnExpressions(columns []Column, withTableName bool) (out []string) {
	for _, c := range columns {
		out = append(out, c.toColumnExpression(withTableName))
	}
	return
}

func flattenValues(values []interface{}) []interface{} {
	out := make([]interface{}, 0, len(values))
	flattened := false

	for _, value := range values {
		reflection := reflect.ValueOf(value).Elem()
		reflectType := reflection.Type()
		switch reflectType.Kind() {
		case reflect.Struct:
			for i := 0; i < reflection.NumField(); i++ {
				// flatten this struct into pointers to each field of it
				out = append(out, reflection.Field(i).Addr().Interface())
			}
			flattened = true
		default:
			out = append(out, value)
		}
	}
	// if we ever flattened any structs, there could be nested structs, so let's recurse
	if flattened {
		return flattenValues(out)
	}
	// otherwise we're done
	return out
}

func makeWhereClause(f Filter, withTableNames bool) string {
	expression := f.toBooleanExpression(withTableNames)
	if expression == "" {
		return ""
	}
	return fmt.Sprintf("WHERE %v", expression)
}

func (s SelectStmt) constructSQL() string {
	tableNames := toTableNames(s.columns)
	fullyQualifyColumns := (len(tableNames) > 1)
	return strings.Join(
		filterStringSlice(
			fmt.Sprintf(
				"SELECT %v FROM %v",
				strings.Join(toColumnExpressions(s.columns, fullyQualifyColumns), ", "),
				strings.Join(tableNames, ", "),
			),
			makeWhereClause(AND(s.filters...), fullyQualifyColumns),
		),
		" ",
	)
}

func (s SelectStmt) First(tx *sql.Tx, values ...interface{}) error {
	return tx.QueryRow(
		s.constructSQL(),
		AND(s.filters...).binds()...,
	).Scan(flattenValues(values)...)
}

func (s SelectStmt) All(tx *sql.Tx, values ...interface{}) error {
	reflections := make([]reflect.Value, len(values))
	for i, value := range values {
		reflection := reflect.ValueOf(value).Elem()
		reflectType := reflection.Type()
		if reflectType.Kind() != reflect.Slice {
			return NonSliceError{
				Type: reflectType,
			}
		}
		reflections[i] = reflection
	}

	rows, err := tx.Query(
		s.constructSQL(),
		AND(s.filters...).binds()...,
	)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		// create new slice element value(s) to scan the database row into
		rowValues := make([]interface{}, len(reflections))
		for i, reflection := range reflections {
			rowValues[i] = reflect.New(reflection.Type().Elem()).Interface()
		}

		err = rows.Scan(flattenValues(rowValues)...)
		if err != nil {
			return err
		}

		for i, rowValue := range rowValues {
			reflections[i].Set(reflect.Append(reflections[i], reflect.ValueOf(rowValue).Elem()))
		}
	}

	return rows.Err()
}
<commit_msg>no more one character variable names in more functions<commit_after>package chem

import (
	"database/sql"
	"fmt"
	"reflect"
	"strings"
)

type SelectStmt struct {
	columns []Column
	filters []Filter
}

func Select(columnThings ...Columnser) SelectStmt {
	columns := make([]Column, 0, len(columnThings))
	for _, c := range columnThings {
		columns = append(columns, c.Columns()...)
	}

	return SelectStmt{columns: columns}
}

func (stmt SelectStmt) Where(filters ...Filter) SelectStmt {
	stmt.filters = append(stmt.filters, filters...)
	return stmt
}

func toTableNames(columns []Column) []string {
	names := make(map[string]bool)
	for _, column := range columns {
		names[column.Table().Name()] = true
	}
	nameList := make([]string, 0, len(names))
	for name := range names {
		nameList = append(nameList, name)
	}
	return nameList
}

func toColumnExpressions(columns []Column, withTableName bool) (out []string) {
	for _, column := range columns {
		out = append(out, column.toColumnExpression(withTableName))
	}
	return
}

func flattenValues(values []interface{}) []interface{} {
	out := make([]interface{}, 0, len(values))
	flattened := false

	for _, value := range values {
		reflection := reflect.ValueOf(value).Elem()
		reflectType := reflection.Type()
		switch reflectType.Kind() {
		case reflect.Struct:
			for i := 0; i < reflection.NumField(); i++ {
				// flatten this struct into pointers to each field of it
				out = append(out, reflection.Field(i).Addr().Interface())
			}
			flattened = true
		default:
			out = append(out, value)
		}
	}
	// if we ever flattened any structs, there could be nested structs, so let's recurse
	if flattened {
		return flattenValues(out)
	}
	// otherwise we're done
	return out
}

func makeWhereClause(f Filter, withTableNames bool) string {
	expression := f.toBooleanExpression(withTableNames)
	if expression == "" {
		return ""
	}
	return fmt.Sprintf("WHERE %v", expression)
}

func (stmt SelectStmt) constructSQL() string {
	tableNames := toTableNames(stmt.columns)
	fullyQualifyColumns := (len(tableNames) > 1)
	return strings.Join(
		filterStringSlice(
			fmt.Sprintf(
				"SELECT %v FROM %v",
				strings.Join(toColumnExpressions(stmt.columns, fullyQualifyColumns), ", "),
				strings.Join(tableNames, ", "),
			),
			makeWhereClause(AND(stmt.filters...), fullyQualifyColumns),
		),
		" ",
	)
}

func (stmt SelectStmt) First(tx *sql.Tx, values ...interface{}) error {
	return tx.QueryRow(
		stmt.constructSQL(),
		AND(stmt.filters...).binds()...,
	).Scan(flattenValues(values)...)
}

func (stmt SelectStmt) All(tx *sql.Tx, values ...interface{}) error {
	reflections := make([]reflect.Value, len(values))
	for i, value := range values {
		reflection := reflect.ValueOf(value).Elem()
		reflectType := reflection.Type()
		if reflectType.Kind() != reflect.Slice {
			return NonSliceError{
				Type: reflectType,
			}
		}
		reflections[i] = reflection
	}

	rows, err := tx.Query(
		stmt.constructSQL(),
		AND(stmt.filters...).binds()...,
	)
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		// create new slice element value(s) to scan the database row into
		rowValues := make([]interface{}, len(reflections))
		for i, reflection := range reflections {
			rowValues[i] = reflect.New(reflection.Type().Elem()).Interface()
		}

		err = rows.Scan(flattenValues(rowValues)...)
		if err != nil {
			return err
		}

		for i, rowValue := range rowValues {
			reflections[i].Set(reflect.Append(reflections[i], reflect.ValueOf(rowValue).Elem()))
		}
	}

	return rows.Err()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"time"
	"xpfunds"
	"xpfunds/simulate"
)

var (
	funds       []*xpfunds.Fund
	maxDuration int
	maxMonths   = 3
	numFunds    = 10
)

func main() {
	funds = xpfunds.ReadFunds()
	for _, f := range funds {
		if f.Duration() > maxDuration {
			maxDuration = f.Duration()
		}
	}
	point := make([]float64, (funds[0].FeatureCount()+(&simulate.Weighted{}).FeatureCount())*numFunds)
	fmt.Println(point, simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, point)))
	step := 1.0
	for i := 0; true; i++ {
		start := time.Now()
		best, perf := bestInRegion(point, step)
		end := time.Now()
		fmt.Printf("%v\t%v\t%v\t%v\t%v\n", i, best, perf, end.Sub(start).String(), step)
		point = nextPoint(point, best)
		step /= 2
	}
}

func bestInRegion(point []float64, step float64) ([]float64, float64) {
	newPoint := make([]float64, len(point))
	for i, p := range point {
		newPoint[i] = p
	}
	bestPerf := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
	for i := 0; i < len(newPoint); i++ {
		newPoint[i] -= step
		left := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
		newPoint[i] += step * 2
		right := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
		// No change.
		if bestPerf > left && bestPerf > right {
			newPoint[i] -= step
			continue
		}
		if right >= left {
			bestPerf = right
			// NewPoint is already at right.
			continue
		}
		bestPerf = left
		newPoint[i] -= step * 2
	}
	return newPoint, bestPerf
}

func nextPoint(orig []float64, best []float64) []float64 {
	new := make([]float64, len(orig))
	for i := range orig {
		new[i] = (orig[i] + best[i]) / 2
	}
	return new
}
<commit_msg>order<commit_after>package main

import (
	"fmt"
	"time"
	"xpfunds"
	"xpfunds/simulate"
)

var (
	funds       []*xpfunds.Fund
	maxDuration int
	maxMonths   = 3
	numFunds    = 10
)

func main() {
	funds = xpfunds.ReadFunds()
	for _, f := range funds {
		if f.Duration() > maxDuration {
			maxDuration = f.Duration()
		}
	}
	point := make([]float64, (funds[0].FeatureCount()+(&simulate.Weighted{}).FeatureCount())*numFunds)
	step := 1.0
	for i := 0; true; i++ {
		start := time.Now()
		best, perf := bestInRegion(point, step)
		end := time.Now()
		fmt.Printf("%v\t%v\t%v\t%v\t%v\n", i, perf, end.Sub(start).String(), best, step)
		point = nextPoint(point, best)
		step /= 2
	}
}

func bestInRegion(point []float64, step float64) ([]float64, float64) {
	newPoint := make([]float64, len(point))
	for i, p := range point {
		newPoint[i] = p
	}
	bestPerf := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
	for i := 0; i < len(newPoint); i++ {
		newPoint[i] -= step
		left := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
		newPoint[i] += step * 2
		right := simulate.MedianPerformance(funds, maxDuration, maxMonths*2, numFunds, simulate.NewWeighted(maxMonths, newPoint))
		// No change.
		if bestPerf > left && bestPerf > right {
			newPoint[i] -= step
			continue
		}
		if right >= left {
			bestPerf = right
			// NewPoint is already at right.
			continue
		}
		bestPerf = left
		newPoint[i] -= step * 2
	}
	return newPoint, bestPerf
}

func nextPoint(orig []float64, best []float64) []float64 {
	new := make([]float64, len(orig))
	for i := range orig {
		new[i] = (orig[i] + best[i]) / 2
	}
	return new
}
<|endoftext|>


<commit_before>package semver

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	dot        = "."
	hyphen     = "-"
	plus       = "+"
	delimiters = dot + hyphen + plus
)

// Version is the container for semver version data.
type Version struct {
	major      uint64
	minor      uint64
	patch      uint64
	prerelease *prereleases
	metadata   []string
}

type prereleases struct {
	values  []string
	numbers []int
}

// Build accepts version numbers in uint64 and optional prerelease and metadata information in a string array.
// Build circumvents error checking, and is mostly used for testing.
func Build(major, minor, patch uint64, extra ...[]string) *Version {
	if len(extra) == 1 {
		ver := &Version{major, minor, patch, nil, nil}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	if len(extra) > 1 {
		ver := &Version{major, minor, patch, nil, extra[1]}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	return &Version{major, minor, patch, nil, nil}
}

// New accepts a valid semver version string and returns a Version struct.
// Returns error if the supplied string is an invalid semver version.
func New(version string) (*Version, error) {
	var versions []string
	var prereleases []string
	var metadatas []string

	result := new(Version)

	if strings.Contains(version, plus) {
		metadata := strings.Split(version, plus)
		metadatas = strings.Split(metadata[1], dot)

		if err := result.SetMetadata(metadatas...); err != nil {
			return nil, err
		}

		version = metadata[0]
	}

	if strings.Contains(version, hyphen) {
		prerelease := strings.SplitN(version, hyphen, 2)
		prereleases = strings.Split(prerelease[1], dot)

		if err := result.SetPrerelease(prereleases...); err != nil {
			return nil, err
		}

		version = prerelease[0]
	}

	versions = strings.Split(version, dot)
	if len(versions) != 3 {
		return nil, errors.New("major.minor.patch pattern not found")
	}

	var versionNumbers [3]uint64
	for i, partial := range versions {

		if num, err := strconv.ParseUint(partial, 10, 0); err != nil {
			return nil, errors.New(fmt.Sprint("expected unsigned integer: ", partial))
		} else {
			if hasLeadingZero(partial) {
				return nil, errors.New(fmt.Sprint("leading zeroes in version number: ", partial))
			}
			versionNumbers[i] = num
		}
	}

	result.major = versionNumbers[0]
	result.minor = versionNumbers[1]
	result.patch = versionNumbers[2]

	return result, nil
}

// String returns a valid semver string based on the data contained in Version.
func (v Version) String() string {
	var buffer bytes.Buffer
	w := bufio.NewWriter(&buffer)

	fmt.Fprintf(w, "%d.%d.%d", v.major, v.minor, v.patch)

	if v.prerelease != nil {
		fmt.Fprintf(w, "%v%v", hyphen, v.Prerelease())
	}

	if v.metadata != nil {
		fmt.Fprintf(w, "%v%v", plus, v.Metadata())
	}

	w.Flush()
	return buffer.String()
}

// Major returns the major version.
func (v Version) Major() uint64 {
	return v.major
}

// SetMajor accepts a uint64 to change the currently set major version.
func (v *Version) SetMajor(major uint64) {
	v.major = major
}

// Minor returns the minor version.
func (v Version) Minor() uint64 {
	return v.minor
}

// SetMinor accepts a uint64 to change the currently set minor version.
func (v *Version) SetMinor(minor uint64) {
	v.minor = minor
}

// Patch returns the patch version.
func (v Version) Patch() uint64 {
	return v.patch
}

// SetPatch accepts a uint64 to change the currently set patch version.
func (v *Version) SetPatch(patch uint64) {
	v.patch = patch
}

// Prerelease returns the prerelease identifiers as a dot seperated string.
func (v Version) Prerelease() string {
	return strings.Join(v.prerelease.values, dot)
}

// SetPrerelease accepts a series of strings to form the prerelease identifiers.
// Returns error if any of the supplied strings aren't a valid prerelease identifier.
func (v *Version) SetPrerelease(identifiers ...string) error {
	var result []string
	var numbers []int

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if num, err := strconv.ParseUint(ident, 10, 0); err == nil {
			if hasLeadingZero(ident) {
				return errors.New(fmt.Sprint("leading zeroes in numerical identifier: ", ident))
			}
			numbers = append(numbers, int(num))
			result = append(result, ident)
		} else {
			if !containsOnly(ident, alphanumeric) {
				return errors.New(fmt.Sprint("not alphanumerical: ", ident))
			}
			numbers = append(numbers, -1)
			result = append(result, ident)
		}
	}
	pre := &prereleases{result, numbers}
	v.prerelease = pre
	return nil
}

// Metadata returns the metadata identifiers as a dot seperated string.
func (v Version) Metadata() string {
	return strings.Join(v.metadata, dot)
}

// SetMetadata accepts a series of strings to form the metadata identifiers.
// Returns error if any of the supplied strings aren't a valid metadata identifier.
func (v *Version) SetMetadata(identifiers ...string) error {
	var result []string

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if !containsOnly(ident, alphanumeric) {
			return errors.New(fmt.Sprint("not alphanumerical: ", ident))
		}
		result = append(result, ident)
	}

	v.metadata = result
	return nil
}

// Satifies accepts a set of comparators and version numbers as a string.
// The syntax for the string is documented here: https://www.npmjs.org/doc/misc/semver.html
// Returns true if Version matches the comparators, false if it does not.
func (v Version) Satifies(other string) bool {
	return true
}
<commit_msg>Performed some refactoring.<commit_after>package semver

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
)

const (
	dot        = "."
	hyphen     = "-"
	plus       = "+"
	delimiters = dot + hyphen + plus
)

// Version is the container for semver version data.
type Version struct {
	major      uint64
	minor      uint64
	patch      uint64
	prerelease *prereleases
	metadata   []string
}

type prereleases struct {
	values  []string
	numbers []int
}

// Build accepts version numbers in uint64 and optional prerelease and metadata information in a string array.
// Build circumvents error checking, and is mostly used for testing.
func Build(major, minor, patch uint64, extra ...[]string) *Version {
	if len(extra) == 1 {
		ver := &Version{major, minor, patch, nil, nil}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	if len(extra) > 1 {
		ver := &Version{major, minor, patch, nil, extra[1]}
		ver.SetPrerelease(extra[0]...)
		return ver
	}
	return &Version{major, minor, patch, nil, nil}
}

// New accepts a valid semver version string and returns a Version struct.
// Returns error if the supplied string is an invalid semver version.
func New(version string) (*Version, error) {
	var versions []string
	var prereleases []string
	var metadatas []string

	result := new(Version)

	if strings.Contains(version, plus) {
		metadata := strings.Split(version, plus)
		metadatas = strings.Split(metadata[1], dot)

		if err := result.SetMetadata(metadatas...); err != nil {
			return nil, err
		}

		version = metadata[0]
	}

	if strings.Contains(version, hyphen) {
		prerelease := strings.SplitN(version, hyphen, 2)
		prereleases = strings.Split(prerelease[1], dot)

		if err := result.SetPrerelease(prereleases...); err != nil {
			return nil, err
		}

		version = prerelease[0]
	}

	versions = strings.Split(version, dot)
	if len(versions) != 3 {
		return nil, errors.New("major.minor.patch pattern not found")
	}

	var versionNumbers [3]uint64
	for i, partial := range versions {

		num, err := strconv.ParseUint(partial, 10, 0)
		if err != nil {
			return nil, errors.New(fmt.Sprint("expected unsigned integer: ", partial))
		}
		if hasLeadingZero(partial) {
			return nil, errors.New(fmt.Sprint("leading zeroes in version number: ", partial))
		}
		versionNumbers[i] = num

	}

	result.major = versionNumbers[0]
	result.minor = versionNumbers[1]
	result.patch = versionNumbers[2]

	return result, nil
}

// String returns a valid semver string based on the data contained in Version.
func (v Version) String() string {
	var buffer bytes.Buffer
	w := bufio.NewWriter(&buffer)

	fmt.Fprintf(w, "%d.%d.%d", v.major, v.minor, v.patch)

	if v.prerelease != nil {
		fmt.Fprintf(w, "%v%v", hyphen, v.Prerelease())
	}

	if v.metadata != nil {
		fmt.Fprintf(w, "%v%v", plus, v.Metadata())
	}

	w.Flush()
	return buffer.String()
}

// Major returns the major version.
func (v Version) Major() uint64 {
	return v.major
}

// SetMajor accepts a uint64 to change the currently set major version.
func (v *Version) SetMajor(major uint64) {
	v.major = major
}

// Minor returns the minor version.
func (v Version) Minor() uint64 {
	return v.minor
}

// SetMinor accepts a uint64 to change the currently set minor version.
func (v *Version) SetMinor(minor uint64) {
	v.minor = minor
}

// Patch returns the patch version.
func (v Version) Patch() uint64 {
	return v.patch
}

// SetPatch accepts a uint64 to change the currently set patch version.
func (v *Version) SetPatch(patch uint64) {
	v.patch = patch
}

// Prerelease returns the prerelease identifiers as a dot seperated string.
func (v Version) Prerelease() string {
	return strings.Join(v.prerelease.values, dot)
}

// SetPrerelease accepts a series of strings to form the prerelease identifiers.
// Returns error if any of the supplied strings aren't a valid prerelease identifier.
func (v *Version) SetPrerelease(identifiers ...string) error {
	var result []string
	var numbers []int

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if num, err := strconv.ParseUint(ident, 10, 0); err == nil {
			if hasLeadingZero(ident) {
				return errors.New(fmt.Sprint("leading zeroes in numerical identifier: ", ident))
			}
			numbers = append(numbers, int(num))
			result = append(result, ident)
		} else {
			if !containsOnly(ident, alphanumeric) {
				return errors.New(fmt.Sprint("not alphanumerical: ", ident))
			}
			numbers = append(numbers, -1)
			result = append(result, ident)
		}
	}
	pre := &prereleases{result, numbers}
	v.prerelease = pre
	return nil
}

// Metadata returns the metadata identifiers as a dot seperated string.
func (v Version) Metadata() string {
	return strings.Join(v.metadata, dot)
}

// SetMetadata accepts a series of strings to form the metadata identifiers.
// Returns error if any of the supplied strings aren't a valid metadata identifier.
func (v *Version) SetMetadata(identifiers ...string) error {
	var result []string

	for _, ident := range identifiers {
		if len(ident) < 1 {
			return errors.New("identifier is empty")
		}

		if !containsOnly(ident, alphanumeric) {
			return errors.New(fmt.Sprint("not alphanumerical: ", ident))
		}
		result = append(result, ident)
	}

	v.metadata = result
	return nil
}

// Satifies accepts a set of comparators and version numbers as a string.
// The syntax for the string is documented here: https://www.npmjs.org/doc/misc/semver.html
// Returns true if Version matches the comparators, false if it does not.
func (v Version) Satifies(other string) bool {
	return true
}
<|endoftext|>


<commit_before>package logrus_sentry

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent.
	//
	// This is ignored for asynchronous hooks. If you want to set a timeout when
	// using an async hook (to bound the length of time that hook.Flush can take),
	// you probably want to create your own raven.Client and set
	// ravenClient.Transport.(*raven.HTTPTransport).Client.Timeout to set a
	// timeout on the underlying HTTP request instead.
	Timeout                 time.Duration
	StacktraceConfiguration StackTraceConfiguration

	client *raven.Client
	levels []logrus.Level

	ignoreFields map[string]struct{}
	extraFilters map[string]func(interface{}) interface{}

	asynchronous bool

	mu sync.RWMutex
	wg sync.WaitGroup
}

// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
	GetStacktrace() *raven.Stacktrace
}

type causer interface {
	Cause() error
}

type pkgErrorStackTracer interface {
	StackTrace() errors.StackTrace
}

// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
	// whether stacktraces should be enabled
	Enable bool
	// the level at which to start capturing stacktraces
	Level logrus.Level
	// how many stack frames to skip before stacktrace starts recording
	Skip int
	// the number of lines to include around a stack frame for context
	Context int
	// the prefixes that will be matched against the stack frame.
	// if the stack frame's package matches one of these prefixes
	// sentry will identify the stack frame as "in_app"
	InAppPrefixes []string
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	return &SentryHook{
		Timeout: 100 * time.Millisecond,
		StacktraceConfiguration: StackTraceConfiguration{
			Enable:        false,
			Level:         logrus.ErrorLevel,
			Skip:          5,
			Context:       0,
			InAppPrefixes: nil,
		},
		client:       client,
		levels:       levels,
		ignoreFields: make(map[string]struct{}),
		extraFilters: make(map[string]func(interface{}) interface{}),
	}, nil
}

// NewAsyncSentryHook creates a hook same as NewSentryHook, but in asynchronous
// mode.
func NewAsyncSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewSentryHook(DSN, levels)
	return setAsync(hook), err
}

// NewAsyncWithTagsSentryHook creates a hook same as NewWithTagsSentryHook, but
// in asynchronous mode.
func NewAsyncWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithTagsSentryHook(DSN, tags, levels)
	return setAsync(hook), err
}

// NewAsyncWithClientSentryHook creates a hook same as NewWithClientSentryHook,
// but in asynchronous mode.
func NewAsyncWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithClientSentryHook(client, levels)
	return setAsync(hook), err
}

func setAsync(hook *SentryHook) *SentryHook {
	if hook == nil {
		return nil
	}
	hook.asynchronous = true
	return hook
}

// Fire is called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: error, logger, server_name, http_request, tags
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	hook.mu.RLock() // Allow multiple go routines to log simultaneously
	defer hook.mu.RUnlock()
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Platform = "go"

	df := newDataField(entry.Data)

	// set special fields
	if logger, ok := df.getLogger(); ok {
		packet.Logger = logger
	}
	if serverName, ok := df.getServerName(); ok {
		packet.ServerName = serverName
	}
	if eventID, ok := df.getEventID(); ok {
		packet.EventID = eventID
	}
	if tags, ok := df.getTags(); ok {
		packet.Tags = tags
	}
	if fingerprint, ok := df.getFingerprint(); ok {
		packet.Fingerprint = fingerprint
	}
	if req, ok := df.getHTTPRequest(); ok {
		packet.Interfaces = append(packet.Interfaces, req)
	}
	if user, ok := df.getUser(); ok {
		packet.Interfaces = append(packet.Interfaces, user)
	}

	// set stacktrace data
	stConfig := &hook.StacktraceConfiguration
	if stConfig.Enable && entry.Level <= stConfig.Level {
		if err, ok := df.getError(); ok {
			var currentStacktrace *raven.Stacktrace
			currentStacktrace = hook.findStacktrace(err)
			if currentStacktrace == nil {
				currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			}
			err := errors.Cause(err)
			exc := raven.NewException(err, currentStacktrace)
			packet.Interfaces = append(packet.Interfaces, exc)
			packet.Culprit = err.Error()
		} else {
			currentStacktrace := raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			if currentStacktrace != nil {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
			}
		}
	} else {
		// set the culprit even when the stack trace is disabled, as long as we have an error
		if err, ok := df.getError(); ok {
			packet.Culprit = err.Error()
		}
	}

	// set other fields
	dataExtra := hook.formatExtraData(df)
	if packet.Extra == nil {
		packet.Extra = dataExtra
	} else {
		for k, v := range dataExtra {
			packet.Extra[k] = v
		}
	}

	_, errCh := hook.client.Capture(packet, nil)

	if hook.asynchronous {
		// Our use of hook.mu guarantees that we are following the WaitGroup rule of
		// not calling Add in parallel with Wait.
		hook.wg.Add(1)
		go func() {
			if err := <-errCh; err != nil {
				fmt.Println(err)
			}
			hook.wg.Done()
		}()
		return nil
	} else if timeout := hook.Timeout; timeout == 0 {
		return nil
	} else {
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
}

// Flush waits for the log queue to empty. This function only does anything in
// asynchronous mode.
func (hook *SentryHook) Flush() {
	if !hook.asynchronous {
		return
	}
	hook.mu.Lock() // Claim exclusive access; any logging goroutines will block until the flush completes
	defer hook.mu.Unlock()

	hook.wg.Wait()
}

func (hook *SentryHook) findStacktrace(err error) *raven.Stacktrace {
	var stacktrace *raven.Stacktrace
	var stackErr errors.StackTrace
	for err != nil {
		// Find the earliest *raven.Stacktrace, or error.StackTrace
		if tracer, ok := err.(Stacktracer); ok {
			stacktrace = tracer.GetStacktrace()
			stackErr = nil
		} else if tracer, ok := err.(pkgErrorStackTracer); ok {
			stacktrace = nil
			stackErr = tracer.StackTrace()
		}
		if cause, ok := err.(causer); ok {
			err = cause.Cause()
		} else {
			break
		}
	}
	if stackErr != nil {
		stacktrace = hook.convertStackTrace(stackErr)
	}
	return stacktrace
}

// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
	stConfig := &hook.StacktraceConfiguration
	stFrames := []errors.Frame(st)
	frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
	for i := range stFrames {
		pc := uintptr(stFrames[i])
		fn := runtime.FuncForPC(pc)
		file, line := fn.FileLine(pc)
		frame := raven.NewStacktraceFrame(pc, file, line, stConfig.Context, stConfig.InAppPrefixes)
		if frame != nil {
			frames = append(frames, frame)
		}
	}

	// Sentry wants the frames with the oldest first, so reverse them
	for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
		frames[i], frames[j] = frames[j], frames[i]
	}
	return &raven.Stacktrace{Frames: frames}
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}

// SetRelease sets release tag.
func (hook *SentryHook) SetRelease(release string) {
	hook.client.SetRelease(release)
}

// SetEnvironment sets environment tag.
func (hook *SentryHook) SetEnvironment(environment string) {
	hook.client.SetEnvironment(environment)
}

// AddIgnore adds field name to ignore.
func (hook *SentryHook) AddIgnore(name string) {
	hook.ignoreFields[name] = struct{}{}
}

// AddExtraFilter adds a custom filter function.
func (hook *SentryHook) AddExtraFilter(name string, fn func(interface{}) interface{}) {
	hook.extraFilters[name] = fn
}

func (hook *SentryHook) formatExtraData(df *dataField) (result map[string]interface{}) {
	// create a map for passing to Sentry's extra data
	result = make(map[string]interface{}, df.len())
	for k, v := range df.data {
		if df.isOmit(k) {
			continue // skip already used special fields
		}
		if _, ok := hook.ignoreFields[k]; ok {
			continue
		}

		if fn, ok := hook.extraFilters[k]; ok {
			v = fn(v) // apply custom filter
		} else {
			v = formatData(v) // use default formatter
		}
		result[k] = v
	}
	return result
}

// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
	switch value := value.(type) {
	case json.Marshaler:
		return value
	case error:
		return value.Error()
	case fmt.Stringer:
		return value.String()
	default:
		return value
	}
}
<commit_msg>Add flag SendExceptionType to configuration. (#49)<commit_after>package logrus_sentry

import (
	"encoding/json"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
)

var (
	severityMap = map[logrus.Level]raven.Severity{
		logrus.DebugLevel: raven.DEBUG,
		logrus.InfoLevel:  raven.INFO,
		logrus.WarnLevel:  raven.WARNING,
		logrus.ErrorLevel: raven.ERROR,
		logrus.FatalLevel: raven.FATAL,
		logrus.PanicLevel: raven.FATAL,
	}
)

// SentryHook delivers logs to a sentry server.
type SentryHook struct {
	// Timeout sets the time to wait for a delivery error from the sentry server.
	// If this is set to zero the server will not wait for any response and will
	// consider the message correctly sent.
	//
	// This is ignored for asynchronous hooks. If you want to set a timeout when
	// using an async hook (to bound the length of time that hook.Flush can take),
	// you probably want to create your own raven.Client and set
	// ravenClient.Transport.(*raven.HTTPTransport).Client.Timeout to set a
	// timeout on the underlying HTTP request instead.
	Timeout                 time.Duration
	StacktraceConfiguration StackTraceConfiguration

	client *raven.Client
	levels []logrus.Level

	ignoreFields map[string]struct{}
	extraFilters map[string]func(interface{}) interface{}

	asynchronous bool

	mu sync.RWMutex
	wg sync.WaitGroup
}

// The Stacktracer interface allows an error type to return a raven.Stacktrace.
type Stacktracer interface {
	GetStacktrace() *raven.Stacktrace
}

type causer interface {
	Cause() error
}

type pkgErrorStackTracer interface {
	StackTrace() errors.StackTrace
}

// StackTraceConfiguration allows for configuring stacktraces
type StackTraceConfiguration struct {
	// whether stacktraces should be enabled
	Enable bool
	// the level at which to start capturing stacktraces
	Level logrus.Level
	// how many stack frames to skip before stacktrace starts recording
	Skip int
	// the number of lines to include around a stack frame for context
	Context int
	// the prefixes that will be matched against the stack frame.
	// if the stack frame's package matches one of these prefixes
	// sentry will identify the stack frame as "in_app"
	InAppPrefixes []string
	// whether sending exception type should be enabled.
	SendExceptionType bool
}

// NewSentryHook creates a hook to be added to an instance of logger
// and initializes the raven client.
// This method sets the timeout to 100 milliseconds.
func NewSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.New(DSN)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithTagsSentryHook creates a hook with tags to be added to an instance
// of logger and initializes the raven client. This method sets the timeout to
// 100 milliseconds.
func NewWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	client, err := raven.NewWithTags(DSN, tags)
	if err != nil {
		return nil, err
	}
	return NewWithClientSentryHook(client, levels)
}

// NewWithClientSentryHook creates a hook using an initialized raven client.
// This method sets the timeout to 100 milliseconds.
func NewWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	return &SentryHook{
		Timeout: 100 * time.Millisecond,
		StacktraceConfiguration: StackTraceConfiguration{
			Enable:            false,
			Level:             logrus.ErrorLevel,
			Skip:              5,
			Context:           0,
			InAppPrefixes:     nil,
			SendExceptionType: true,
		},
		client:       client,
		levels:       levels,
		ignoreFields: make(map[string]struct{}),
		extraFilters: make(map[string]func(interface{}) interface{}),
	}, nil
}

// NewAsyncSentryHook creates a hook same as NewSentryHook, but in asynchronous
// mode.
func NewAsyncSentryHook(DSN string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewSentryHook(DSN, levels)
	return setAsync(hook), err
}

// NewAsyncWithTagsSentryHook creates a hook same as NewWithTagsSentryHook, but
// in asynchronous mode.
func NewAsyncWithTagsSentryHook(DSN string, tags map[string]string, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithTagsSentryHook(DSN, tags, levels)
	return setAsync(hook), err
}

// NewAsyncWithClientSentryHook creates a hook same as NewWithClientSentryHook,
// but in asynchronous mode.
func NewAsyncWithClientSentryHook(client *raven.Client, levels []logrus.Level) (*SentryHook, error) {
	hook, err := NewWithClientSentryHook(client, levels)
	return setAsync(hook), err
}

func setAsync(hook *SentryHook) *SentryHook {
	if hook == nil {
		return nil
	}
	hook.asynchronous = true
	return hook
}

// Fire is called when an event should be sent to sentry
// Special fields that sentry uses to give more information to the server
// are extracted from entry.Data (if they are found)
// These fields are: error, logger, server_name, http_request, tags
func (hook *SentryHook) Fire(entry *logrus.Entry) error {
	hook.mu.RLock() // Allow multiple go routines to log simultaneously
	defer hook.mu.RUnlock()
	packet := raven.NewPacket(entry.Message)
	packet.Timestamp = raven.Timestamp(entry.Time)
	packet.Level = severityMap[entry.Level]
	packet.Platform = "go"

	df := newDataField(entry.Data)

	// set special fields
	if logger, ok := df.getLogger(); ok {
		packet.Logger = logger
	}
	if serverName, ok := df.getServerName(); ok {
		packet.ServerName = serverName
	}
	if eventID, ok := df.getEventID(); ok {
		packet.EventID = eventID
	}
	if tags, ok := df.getTags(); ok {
		packet.Tags = tags
	}
	if fingerprint, ok := df.getFingerprint(); ok {
		packet.Fingerprint = fingerprint
	}
	if req, ok := df.getHTTPRequest(); ok {
		packet.Interfaces = append(packet.Interfaces, req)
	}
	if user, ok := df.getUser(); ok {
		packet.Interfaces = append(packet.Interfaces, user)
	}

	// set stacktrace data
	stConfig := &hook.StacktraceConfiguration
	if stConfig.Enable && entry.Level <= stConfig.Level {
		if err, ok := df.getError(); ok {
			var currentStacktrace *raven.Stacktrace
			currentStacktrace = hook.findStacktrace(err)
			if currentStacktrace == nil {
				currentStacktrace = raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			}
			err := errors.Cause(err)
			exc := raven.NewException(err, currentStacktrace)
			if !stConfig.SendExceptionType {
				exc.Type = ""
			}
			packet.Interfaces = append(packet.Interfaces, exc)
			packet.Culprit = err.Error()
		} else {
			currentStacktrace := raven.NewStacktrace(stConfig.Skip, stConfig.Context, stConfig.InAppPrefixes)
			if currentStacktrace != nil {
				packet.Interfaces = append(packet.Interfaces, currentStacktrace)
			}
		}
	} else {
		// set the culprit even when the stack trace is disabled, as long as we have an error
		if err, ok := df.getError(); ok {
			packet.Culprit = err.Error()
		}
	}

	// set other fields
	dataExtra := hook.formatExtraData(df)
	if packet.Extra == nil {
		packet.Extra = dataExtra
	} else {
		for k, v := range dataExtra {
			packet.Extra[k] = v
		}
	}

	_, errCh := hook.client.Capture(packet, nil)

	if hook.asynchronous {
		// Our use of hook.mu guarantees that we are following the WaitGroup rule of
		// not calling Add in parallel with Wait.
		hook.wg.Add(1)
		go func() {
			if err := <-errCh; err != nil {
				fmt.Println(err)
			}
			hook.wg.Done()
		}()
		return nil
	} else if timeout := hook.Timeout; timeout == 0 {
		return nil
	} else {
		timeoutCh := time.After(timeout)
		select {
		case err := <-errCh:
			return err
		case <-timeoutCh:
			return fmt.Errorf("no response from sentry server in %s", timeout)
		}
	}
}

// Flush waits for the log queue to empty. This function only does anything in
// asynchronous mode.
func (hook *SentryHook) Flush() {
	if !hook.asynchronous {
		return
	}
	hook.mu.Lock() // Claim exclusive access; any logging goroutines will block until the flush completes
	defer hook.mu.Unlock()

	hook.wg.Wait()
}

func (hook *SentryHook) findStacktrace(err error) *raven.Stacktrace {
	var stacktrace *raven.Stacktrace
	var stackErr errors.StackTrace
	for err != nil {
		// Find the earliest *raven.Stacktrace, or error.StackTrace
		if tracer, ok := err.(Stacktracer); ok {
			stacktrace = tracer.GetStacktrace()
			stackErr = nil
		} else if tracer, ok := err.(pkgErrorStackTracer); ok {
			stacktrace = nil
			stackErr = tracer.StackTrace()
		}
		if cause, ok := err.(causer); ok {
			err = cause.Cause()
		} else {
			break
		}
	}
	if stackErr != nil {
		stacktrace = hook.convertStackTrace(stackErr)
	}
	return stacktrace
}

// convertStackTrace converts an errors.StackTrace into a natively consumable
// *raven.Stacktrace
func (hook *SentryHook) convertStackTrace(st errors.StackTrace) *raven.Stacktrace {
	stConfig := &hook.StacktraceConfiguration
	stFrames := []errors.Frame(st)
	frames := make([]*raven.StacktraceFrame, 0, len(stFrames))
	for i := range stFrames {
		pc := uintptr(stFrames[i])
		fn := runtime.FuncForPC(pc)
		file, line := fn.FileLine(pc)
		frame := raven.NewStacktraceFrame(pc, file, line, stConfig.Context, stConfig.InAppPrefixes)
		if frame != nil {
			frames = append(frames, frame)
		}
	}

	// Sentry wants the frames with the oldest first, so reverse them
	for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
		frames[i], frames[j] = frames[j], frames[i]
	}
	return &raven.Stacktrace{Frames: frames}
}

// Levels returns the available logging levels.
func (hook *SentryHook) Levels() []logrus.Level {
	return hook.levels
}

// SetRelease sets release tag.
func (hook *SentryHook) SetRelease(release string) {
	hook.client.SetRelease(release)
}

// SetEnvironment sets environment tag.
func (hook *SentryHook) SetEnvironment(environment string) {
	hook.client.SetEnvironment(environment)
}

// AddIgnore adds field name to ignore.
func (hook *SentryHook) AddIgnore(name string) {
	hook.ignoreFields[name] = struct{}{}
}

// AddExtraFilter adds a custom filter function.
func (hook *SentryHook) AddExtraFilter(name string, fn func(interface{}) interface{}) {
	hook.extraFilters[name] = fn
}

func (hook *SentryHook) formatExtraData(df *dataField) (result map[string]interface{}) {
	// create a map for passing to Sentry's extra data
	result = make(map[string]interface{}, df.len())
	for k, v := range df.data {
		if df.isOmit(k) {
			continue // skip already used special fields
		}
		if _, ok := hook.ignoreFields[k]; ok {
			continue
		}

		if fn, ok := hook.extraFilters[k]; ok {
			v = fn(v) // apply custom filter
		} else {
			v = formatData(v) // use default formatter
		}
		result[k] = v
	}
	return result
}

// formatData returns value as a suitable format.
func formatData(value interface{}) (formatted interface{}) {
	switch value := value.(type) {
	case json.Marshaler:
		return value
	case error:
		return value.Error()
	case fmt.Stringer:
		return value.String()
	default:
		return value
	}
}
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/UniversityRadioYork/2016-site/controllers"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

// Server is the type of the main 2016site web application.
type Server struct {
	*negroni.Negroni
}

// NewServer creates a 2016site server based on the config c.
func NewServer(c *structs.Config) (*Server, error) {

	s := Server{negroni.Classic()}

	session, err := myradio.NewSessionFromKeyFile()

	if err != nil {
		return &s, err
	}

	router := mux.NewRouter().StrictSlash(true)

	getRouter := router.Methods("GET").Subrouter()
	postRouter := router.Methods("POST").Subrouter()

	// Routes go in here
	nfc := controllers.NewNotFoundController(c)
	router.NotFoundHandler = http.HandlerFunc(nfc.Get)

	ic := controllers.NewIndexController(session, c)
	getRouter.HandleFunc("/", ic.Get)

	sc := controllers.NewSearchController(session, c)
	getRouter.HandleFunc("/search/", sc.Get)

	showC := controllers.NewShowController(session, c)
	//	getRouter.HandleFunc("/schedule/shows", showC.Get) // @TODO: Implement this
	getRouter.HandleFunc("/schedule/shows/{id:[0-9]+}/", showC.GetShow).Name("show")
	getRouter.HandleFunc("/schedule/shows/timeslots/{id:[0-9]+}/", showC.GetTimeslot).Name("timeslot")
	getRouter.HandleFunc("/schedule/shows/seasons/{id:[0-9]+}/", showC.GetSeason).Name("season")

	// NOTE: NewScheduleWeekController assumes 'timeslot' is installed BEFORE it is called.
	schedWeekC := controllers.NewScheduleWeekController(session, getRouter, c)
	getRouter.HandleFunc("/schedule/thisweek/", schedWeekC.GetThisWeek).Name("schedule-thisweek")

	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/", schedWeekC.GetByYearWeek).Name("schedule-week")
	// This route exists so that day schedule links from the previous website aren't broken.
	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/{day:[1-7]}/", schedWeekC.GetByYearWeek).Name("schedule-week-day-compat")

	pc := controllers.NewPeopleController(session, c)
	getRouter.HandleFunc("/people/{id:[0-9]+}/", pc.Get)

	teamC := controllers.NewTeamController(session, c)
	getRouter.HandleFunc("/teams/", teamC.GetAll)
	getRouter.HandleFunc("/teams/{alias}/", teamC.Get)

	getinvolvedC := controllers.NewGetInvolvedController(session, c)
	getRouter.HandleFunc("/getinvolved/", getinvolvedC.Get)

	signupC := controllers.NewSignUpController(session, c)
	postRouter.HandleFunc("/signup/", signupC.Post)

	staticC := controllers.NewStaticController(c)
	getRouter.HandleFunc("/about/", staticC.GetAbout)
	getRouter.HandleFunc("/contact/", staticC.GetContact)
	getRouter.HandleFunc("/competitions/", staticC.GetCompetitions)

	// End routes

	s.UseHandler(router)

	return &s, nil

}
<commit_msg>Redirect shows page to schedule.<commit_after>package main

import (
	"net/http"

	"github.com/UniversityRadioYork/2016-site/controllers"
	"github.com/UniversityRadioYork/2016-site/structs"
	"github.com/UniversityRadioYork/myradio-go"
	"github.com/codegangsta/negroni"
	"github.com/gorilla/mux"
)

// Server is the type of the main 2016site web application.
type Server struct {
	*negroni.Negroni
}

// NewServer creates a 2016site server based on the config c.
func NewServer(c *structs.Config) (*Server, error) {

	s := Server{negroni.Classic()}

	session, err := myradio.NewSessionFromKeyFile()

	if err != nil {
		return &s, err
	}

	router := mux.NewRouter().StrictSlash(true)

	getRouter := router.Methods("GET").Subrouter()
	postRouter := router.Methods("POST").Subrouter()

	// Routes go in here
	nfc := controllers.NewNotFoundController(c)
	router.NotFoundHandler = http.HandlerFunc(nfc.Get)

	redirectC := controllers.NewRedirectController(c)

	ic := controllers.NewIndexController(session, c)
	getRouter.HandleFunc("/", ic.Get)

	sc := controllers.NewSearchController(session, c)
	getRouter.HandleFunc("/search/", sc.Get)

	showC := controllers.NewShowController(session, c)
	getRouter.HandleFunc("/schedule/shows/", func(w http.ResponseWriter, r *http.Request) {
		redirectC.Redirect(w, r, "/schedule/thisweek/", 301)
	})
	getRouter.HandleFunc("/schedule/shows/{id:[0-9]+}/", showC.GetShow).Name("show")
	getRouter.HandleFunc("/schedule/shows/timeslots/{id:[0-9]+}/", showC.GetTimeslot).Name("timeslot")
	getRouter.HandleFunc("/schedule/shows/seasons/{id:[0-9]+}/", showC.GetSeason).Name("season")

	// NOTE: NewScheduleWeekController assumes 'timeslot' is installed BEFORE it is called.
	schedWeekC := controllers.NewScheduleWeekController(session, getRouter, c)
	getRouter.HandleFunc("/schedule/thisweek/", schedWeekC.GetThisWeek).Name("schedule-thisweek")

	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/", schedWeekC.GetByYearWeek).Name("schedule-week")
	// This route exists so that day schedule links from the previous website aren't broken.
	getRouter.HandleFunc("/schedule/{year:[1-9][0-9][0-9][0-9]}/w{week:[0-5]?[0-9]}/{day:[1-7]}/", schedWeekC.GetByYearWeek).Name("schedule-week-day-compat")

	pc := controllers.NewPeopleController(session, c)
	getRouter.HandleFunc("/people/{id:[0-9]+}/", pc.Get)

	teamC := controllers.NewTeamController(session, c)
	getRouter.HandleFunc("/teams/", teamC.GetAll)
	getRouter.HandleFunc("/teams/{alias}/", teamC.Get)

	getinvolvedC := controllers.NewGetInvolvedController(session, c)
	getRouter.HandleFunc("/getinvolved/", getinvolvedC.Get)

	signupC := controllers.NewSignUpController(session, c)
	postRouter.HandleFunc("/signup/", signupC.Post)

	staticC := controllers.NewStaticController(c)
	getRouter.HandleFunc("/about/", staticC.GetAbout)
	getRouter.HandleFunc("/contact/", staticC.GetContact)
	getRouter.HandleFunc("/competitions/", staticC.GetCompetitions)

	// End routes

	s.UseHandler(router)

	return &s, nil

}
<|endoftext|>


<commit_before>package main

import (
	"./uuid"
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
	"text/template"
)

const (
	HOST_NAME            = "localhost"
	PORT_NUMBER          = "8080"
	APPSERVER_API_PREFIX = "/notify/"
)

type Client struct {
	Websocket *websocket.Conn `json:"-"`
	UAID      string          `json:"uaid"`
	Ip        string          `json:"ip"`
	Port      float64         `json:"port"`
}

type Channel struct {
	UAID      string `json:"uaid"`
	ChannelID string `json:"channelID"`
	Version   string `json:"version"`
}

type ServerState struct {
	// Mapping from a UAID to the Client object
	ConnectedClients map[string]*Client `json:"connectedClients"`

	// Mapping from a UAID to all channels owned by that UAID
	UAIDToChannels map[string][]*Channel `json:"uaidToChannels"`

	// Mapping from a ChannelID to the cooresponding Channel
	ChannelIDToChannel map[string]*Channel `json:"channelIDToChannel"`
}

var gServerState ServerState

func openState() bool {

	var data []byte
	var err error

	data, err = ioutil.ReadFile("serverstate.json")
	if err != nil {
		log.Println("Could not read in serverstate")
		return false
	}

	err = json.Unmarshal(data, &gServerState)
	if err != nil {
		log.Println("Could not unmarshal serverstate")
		return false
	}

	return true
}

func saveState() {
	log.Println(" -> saving state..")

	var data []byte
	var err error

	data, err = json.Marshal(gServerState)
	if err != nil {
		return
	}
	ioutil.WriteFile("serverstate.json", data, 0644)
}

func handleRegister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)

	// TODO https!
	var pushEndpoint = "http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX + channelID

	channel := &Channel{client.UAID, channelID, ""}

	gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID], channel)
	gServerState.ChannelIDToChannel[channelID] = channel

	type RegisterResponse struct {
		Name         string `json:"messageType"`
		Status       int    `json:"status"`
		PushEndpoint string `json:"pushEndpoint"`
		ChannelID    string `json:"channelID"`
	}

	register := RegisterResponse{"register", 200, pushEndpoint, channelID}

	j, err := json.Marshal(register)
	if err != nil {
		log.Println("Could not convert register response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleUnregister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)
	channel, ok := gServerState.ChannelIDToChannel[channelID]
	if ok {
		// only delete if UA owns this channel
		var index = -1
		for p, v := range gServerState.UAIDToChannels[client.UAID] {
			if v == channel {
				delete(gServerState.ChannelIDToChannel, channelID)
				index = p
			}
		}
		if index >= 0 {
			gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID][:index], gServerState.UAIDToChannels[client.UAID][index+1:]...)
		}
	}

	type UnregisterResponse struct {
		Name      string `json:"messageType"`
		Status    int    `json:"status"`
		ChannelID string `json:"channelID"`
	}

	unregister := UnregisterResponse{"unregister", 200, channelID}

	j, err := json.Marshal(unregister)
	if err != nil {
		log.Println("Could not convert unregister response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {

	gServerState.ConnectedClients[client.UAID] = client

	status := 200

	if f["uaid"] == nil {
		uaid, err := uuid.GenUUID()
		if err != nil {
			status = 400
			log.Println("GenUUID error %s", err)
		}
		client.UAID = uaid
	} else {
		client.UAID = f["uaid"].(string)

		// BUG(nikhilm): Does not deal with sending
		// a new UAID if their is a channel that was sent
		// by the UA which the server does not know about.
		// Which means in the case of this memory only server
		// it should actually always send a new UAID when it was
		// restarted
		if f["channelIDs"] != nil {
			for _, foo := range f["channelIDs"].([]interface{}) {
				channelID := foo.(string)

				c := &Channel{client.UAID, channelID, ""}
				gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID], c)
				gServerState.ChannelIDToChannel[channelID] = c
			}
		}
	}

	if f["interface"] != nil {
		m := f["interface"].(map[string]interface{})
		client.Ip = m["ip"].(string)
		client.Port = m["port"].(float64)
	}

	type HelloResponse struct {
		Name   string `json:"messageType"`
		Status int    `json:"status"`
		UAID   string `json:"uaid"`
	}

	hello := HelloResponse{"hello", status, client.UAID}

	j, err := json.Marshal(hello)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
}

func pushHandler(ws *websocket.Conn) {

	client := &Client{ws, "", "", 0}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			break
		}

		log.Println("pushHandler msg: ", f["messageType"])

		switch f["messageType"] {
		case "hello":
			handleHello(client, f)
			break

		case "register":
			handleRegister(client, f)
			break

		case "unregister":
			handleUnregister(client, f)
			break

		case "ack":
			handleAck(client, f)
			break

		default:
			log.Println(" -> Unknown", f)
			break
		}

		saveState()
	}

	log.Println("Closing Websocket!")
	ws.Close()
	gServerState.ConnectedClients[client.UAID].Websocket = nil
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Got notification from app server ", r.URL)

	if r.Method != "PUT" {
		log.Println("NOT A PUT")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Replace(r.URL.Path, APPSERVER_API_PREFIX, "", 1)

	if strings.Contains(channelID, "/") {
		log.Println("Could not find a valid channelID")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return
	}

	value := r.FormValue("version")

	if value == "" {
		log.Println("Could not find version")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find version."))
		return
	}

	channel, found := gServerState.ChannelIDToChannel[channelID]
	if !found {
		log.Println("Could not find channel " + channelID)
		return
	}
	channel.Version = value

	client := gServerState.ConnectedClients[channel.UAID]

	saveState()

	if client == nil {
		log.Println("no known client for the channel.")
	} else if client.Websocket == nil {
		wakeupClient(client)
	} else {
		sendNotificationToClient(client, channel)
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

func wakeupClient(client *Client) {

	// TODO probably want to do this a few times before
	// giving up.

	log.Println("wakeupClient: ", client)
	service := fmt.Sprintf("%s:%g", client.Ip, client.Port)

	udpAddr, err := net.ResolveUDPAddr("up4", service)
	if err != nil {
		log.Println("ResolveUDPAddr error ", err.Error())
		return
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		log.Println("DialUDP error ", err.Error())
		return
	}

	_, err = conn.Write([]byte("anything"))
	if err != nil {
		log.Println("UDP Write error ", err.Error())
		return
	}

}

func sendNotificationToClient(client *Client, channel *Channel) {

	type NotificationResponse struct {
		Name     string    `json:"messageType"`
		Channels []Channel `json:"updates"`
	}

	var channels []Channel
	channels = append(channels, *channel)

	notification := NotificationResponse{"notification", channels}

	j, err := json.Marshal(notification)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", channel, err.Error())
	}
}

func admin(w http.ResponseWriter, r *http.Request) {

	type User struct {
		UAID      string
		Connected bool
		Channels  []*Channel
	}

	type Arguments struct {
		PushEndpointPrefix string
		Users              []User
	}

	// TODO https!
	arguments := Arguments{"http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX, nil}

	for k := range gServerState.UAIDToChannels {
		connected := gServerState.ConnectedClients[k] != nil
		u := User{k, connected, gServerState.UAIDToChannels[k]}
		arguments.Users = append(arguments.Users, u)
	}

	t := template.New("users.template")
	s1, _ := t.ParseFiles("templates/users.template")
	s1.Execute(w, arguments)
}

func main() {

	s := openState()

	if s == false {
		log.Println("Could not restore state")

		gServerState.UAIDToChannels = make(map[string][]*Channel)
		gServerState.ChannelIDToChannel = make(map[string]*Channel)
		gServerState.ConnectedClients = make(map[string]*Client)
	}

	http.HandleFunc("/admin", admin)

	http.Handle("/", websocket.Handler(pushHandler))

	http.HandleFunc(APPSERVER_API_PREFIX, notifyHandler)

	log.Println("Listening on", HOST_NAME+":"+PORT_NUMBER)
	log.Fatal(http.ListenAndServe(HOST_NAME+":"+PORT_NUMBER, nil))
}
<commit_msg>Wait until after we know the client uaid before using it as a key to the connected clients map<commit_after>package main

import (
	"./uuid"
	"code.google.com/p/go.net/websocket"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"strings"
	"text/template"
)

const (
	HOST_NAME            = "localhost"
	PORT_NUMBER          = "8080"
	APPSERVER_API_PREFIX = "/notify/"
)

type Client struct {
	Websocket *websocket.Conn `json:"-"`
	UAID      string          `json:"uaid"`
	Ip        string          `json:"ip"`
	Port      float64         `json:"port"`
}

type Channel struct {
	UAID      string `json:"uaid"`
	ChannelID string `json:"channelID"`
	Version   string `json:"version"`
}

type ServerState struct {
	// Mapping from a UAID to the Client object
	ConnectedClients map[string]*Client `json:"connectedClients"`

	// Mapping from a UAID to all channels owned by that UAID
	UAIDToChannels map[string][]*Channel `json:"uaidToChannels"`

	// Mapping from a ChannelID to the cooresponding Channel
	ChannelIDToChannel map[string]*Channel `json:"channelIDToChannel"`
}

var gServerState ServerState

func openState() bool {

	var data []byte
	var err error

	data, err = ioutil.ReadFile("serverstate.json")
	if err != nil {
		log.Println("Could not read in serverstate")
		return false
	}

	err = json.Unmarshal(data, &gServerState)
	if err != nil {
		log.Println("Could not unmarshal serverstate")
		return false
	}

	return true
}

func saveState() {
	log.Println(" -> saving state..")

	var data []byte
	var err error

	data, err = json.Marshal(gServerState)
	if err != nil {
		return
	}
	ioutil.WriteFile("serverstate.json", data, 0644)
}

func handleRegister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)

	// TODO https!
	var pushEndpoint = "http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX + channelID

	channel := &Channel{client.UAID, channelID, ""}

	gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID], channel)
	gServerState.ChannelIDToChannel[channelID] = channel

	type RegisterResponse struct {
		Name         string `json:"messageType"`
		Status       int    `json:"status"`
		PushEndpoint string `json:"pushEndpoint"`
		ChannelID    string `json:"channelID"`
	}

	register := RegisterResponse{"register", 200, pushEndpoint, channelID}

	j, err := json.Marshal(register)
	if err != nil {
		log.Println("Could not convert register response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleUnregister(client *Client, f map[string]interface{}) {

	if f["channelID"] == nil {
		log.Println("channelID is missing!")
		return
	}

	var channelID = f["channelID"].(string)
	channel, ok := gServerState.ChannelIDToChannel[channelID]
	if ok {
		// only delete if UA owns this channel
		var index = -1
		for p, v := range gServerState.UAIDToChannels[client.UAID] {
			if v == channel {
				delete(gServerState.ChannelIDToChannel, channelID)
				index = p
			}
		}
		if index >= 0 {
			gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID][:index], gServerState.UAIDToChannels[client.UAID][index+1:]...)
		}
	}

	type UnregisterResponse struct {
		Name      string `json:"messageType"`
		Status    int    `json:"status"`
		ChannelID string `json:"channelID"`
	}

	unregister := UnregisterResponse{"unregister", 200, channelID}

	j, err := json.Marshal(unregister)
	if err != nil {
		log.Println("Could not convert unregister response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		// we could not send the message to a peer
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleHello(client *Client, f map[string]interface{}) {

	status := 200

	if f["uaid"] == nil {
		uaid, err := uuid.GenUUID()
		if err != nil {
			status = 400
			log.Println("GenUUID error %s", err)
		}
		client.UAID = uaid
	} else {
		client.UAID = f["uaid"].(string)

		// BUG(nikhilm): Does not deal with sending
		// a new UAID if their is a channel that was sent
		// by the UA which the server does not know about.
		// Which means in the case of this memory only server
		// it should actually always send a new UAID when it was
		// restarted
		if f["channelIDs"] != nil {
			for _, foo := range f["channelIDs"].([]interface{}) {
				channelID := foo.(string)

				c := &Channel{client.UAID, channelID, ""}
				gServerState.UAIDToChannels[client.UAID] = append(gServerState.UAIDToChannels[client.UAID], c)
				gServerState.ChannelIDToChannel[channelID] = c
			}
		}
	}

	gServerState.ConnectedClients[client.UAID] = client

	if f["interface"] != nil {
		m := f["interface"].(map[string]interface{})
		client.Ip = m["ip"].(string)
		client.Port = m["port"].(float64)
	}

	type HelloResponse struct {
		Name   string `json:"messageType"`
		Status int    `json:"status"`
		UAID   string `json:"uaid"`
	}

	hello := HelloResponse{"hello", status, client.UAID}

	j, err := json.Marshal(hello)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", client.Websocket, err.Error())
	}
}

func handleAck(client *Client, f map[string]interface{}) {
}

func pushHandler(ws *websocket.Conn) {

	client := &Client{ws, "", "", 0}

	for {
		var f map[string]interface{}

		var err error
		if err = websocket.JSON.Receive(ws, &f); err != nil {
			log.Println("Websocket Disconnected.", err.Error())
			break
		}

		log.Println("pushHandler msg: ", f["messageType"])

		switch f["messageType"] {
		case "hello":
			handleHello(client, f)
			break

		case "register":
			handleRegister(client, f)
			break

		case "unregister":
			handleUnregister(client, f)
			break

		case "ack":
			handleAck(client, f)
			break

		default:
			log.Println(" -> Unknown", f)
			break
		}

		saveState()
	}

	log.Println("Closing Websocket!")
	ws.Close()

	gServerState.ConnectedClients[client.UAID].Websocket = nil
}

func notifyHandler(w http.ResponseWriter, r *http.Request) {
	log.Println("Got notification from app server ", r.URL)

	if r.Method != "PUT" {
		log.Println("NOT A PUT")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Method must be PUT."))
		return
	}

	channelID := strings.Replace(r.URL.Path, APPSERVER_API_PREFIX, "", 1)

	if strings.Contains(channelID, "/") {
		log.Println("Could not find a valid channelID")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find a valid channelID."))
		return
	}

	value := r.FormValue("version")

	if value == "" {
		log.Println("Could not find version")
		w.WriteHeader(http.StatusBadRequest)
		w.Write([]byte("Could not find version."))
		return
	}

	channel, found := gServerState.ChannelIDToChannel[channelID]
	if !found {
		log.Println("Could not find channel " + channelID)
		return
	}
	channel.Version = value

	client := gServerState.ConnectedClients[channel.UAID]

	saveState()

	if client == nil {
		log.Println("no known client for the channel.")
	} else if client.Websocket == nil {
		wakeupClient(client)
	} else {
		sendNotificationToClient(client, channel)
	}

	w.WriteHeader(http.StatusOK)
	w.Write([]byte("OK"))
}

func wakeupClient(client *Client) {

	// TODO probably want to do this a few times before
	// giving up.

	log.Println("wakeupClient: ", client)
	service := fmt.Sprintf("%s:%g", client.Ip, client.Port)

	udpAddr, err := net.ResolveUDPAddr("up4", service)
	if err != nil {
		log.Println("ResolveUDPAddr error ", err.Error())
		return
	}

	conn, err := net.DialUDP("udp", nil, udpAddr)
	if err != nil {
		log.Println("DialUDP error ", err.Error())
		return
	}

	_, err = conn.Write([]byte("anything"))
	if err != nil {
		log.Println("UDP Write error ", err.Error())
		return
	}

}

func sendNotificationToClient(client *Client, channel *Channel) {

	type NotificationResponse struct {
		Name     string    `json:"messageType"`
		Channels []Channel `json:"updates"`
	}

	var channels []Channel
	channels = append(channels, *channel)

	notification := NotificationResponse{"notification", channels}

	j, err := json.Marshal(notification)
	if err != nil {
		log.Println("Could not convert hello response to json %s", err)
		return
	}

	if err = websocket.Message.Send(client.Websocket, string(j)); err != nil {
		log.Println("Could not send message to ", channel, err.Error())
	}
}

func admin(w http.ResponseWriter, r *http.Request) {

	type User struct {
		UAID      string
		Connected bool
		Channels  []*Channel
	}

	type Arguments struct {
		PushEndpointPrefix string
		Users              []User
	}

	// TODO https!
	arguments := Arguments{"http://" + HOST_NAME + ":" + PORT_NUMBER + APPSERVER_API_PREFIX, nil}

	for k := range gServerState.UAIDToChannels {
		connected := gServerState.ConnectedClients[k] != nil
		u := User{k, connected, gServerState.UAIDToChannels[k]}
		arguments.Users = append(arguments.Users, u)
	}

	t := template.New("users.template")
	s1, _ := t.ParseFiles("templates/users.template")
	s1.Execute(w, arguments)
}

func main() {

	s := openState()

	if s == false {
		log.Println("Could not restore state")

		gServerState.UAIDToChannels = make(map[string][]*Channel)
		gServerState.ChannelIDToChannel = make(map[string]*Channel)
		gServerState.ConnectedClients = make(map[string]*Client)
	}

	http.HandleFunc("/admin", admin)

	http.Handle("/", websocket.Handler(pushHandler))

	http.HandleFunc(APPSERVER_API_PREFIX, notifyHandler)

	log.Println("Listening on", HOST_NAME+":"+PORT_NUMBER)
	log.Fatal(http.ListenAndServe(HOST_NAME+":"+PORT_NUMBER, nil))
}
<|endoftext|>