<commit_before>package main

import (
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/jroimartin/gocui"
)

const (
	bufSize = 128 // How many queries or results to queue during async find-as-you-type.
	// View names.
	resultsWindow = "resultsWindow"
	searchTitle   = "searchTitle"
	searchBar     = "searchBar"
	toolBar       = "toolBar"
)

var (
	// GUI state.
	gui            *gocui.Gui
	db             database
	shellSessionID string
	queries        chan query
	results        chan []record
	resultsOffset  int
	once           sync.Once
	// Settings.
	set setting
	// Currently-displayed results.
	currentResults []record
	// Key binding map.
	keybindings = map[gocui.Key]gocui.KeybindingHandler{
		gocui.KeyCtrlC: quit,
		gocui.KeyCtrlD: quit,
		gocui.KeyCtrlS: func(g *gocui.Gui, v *gocui.View) error {
			set.SortByFreq = !set.SortByFreq
			return drawSettings(v)
		},
		gocui.KeyCtrlL: func(g *gocui.Gui, v *gocui.View) error {
			set.OnlyMySession = !set.OnlyMySession
			return drawSettings(v)
		},
		gocui.KeyCtrlW: func(g *gocui.Gui, v *gocui.View) error {
			set.OnlyMyCwd = !set.OnlyMyCwd
			return drawSettings(v)
		},
		gocui.KeyArrowUp:   func(g *gocui.Gui, v *gocui.View) error { return moveResultLine(true) },
		gocui.KeyArrowDown: func(g *gocui.Gui, v *gocui.View) error { return moveResultLine(false) },
		gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error {
			v, err := g.View(resultsWindow)
			if err != nil {
				return err
			}
			if resultsOffset < len(currentResults) {
				os.Stderr.WriteString(currentResults[resultsOffset].Cmd)
			}
			return quit(g, v)
		},
	}
)

func runGui(d database, shellID, initialQuery string) error {
	var err error
	gui = gocui.NewGui()
	db = d
	set, err = db.Setting()
	if err != nil {
		return err
	}
	shellSessionID = shellID
	if err := gui.Init(); err != nil {
		return err
	}
	defer gui.Close()
	gui.SetLayout(func(g *gocui.Gui) error {
		if err := layout(g); err != nil {
			return err
		}
		// Prefill the search bar. This is ugly, I admit.
		once.Do(func() {
			v, err := g.View(searchBar)
			if err != nil {
				panic(err)
			}
			for _, c := range initialQuery {
				v.EditWrite(c)
			}
			if err := guiFindAsYouType(shellSessionID, db, queries); err != nil {
				panic(err)
			}
		})
		return nil
	})
	if err := setKeybindings(); err != nil {
		return err
	}
	// Channels for sending queries and getting responses.
	queries = make(chan query, bufSize)
	results = make(chan []record, bufSize)
	// Set the editor to do find-as-you-type.
	gui.Editor = gocui.EditorFunc(func(v *gocui.View, k gocui.Key, c rune, m gocui.Modifier) {
		if _, ok := keybindings[k]; ok {
			return
		}
		gocui.DefaultEditor.Edit(v, k, c, m)
		guiFindAsYouType(shellSessionID, db, queries)
	})
	// Async function to execute queries.
	go func() {
		for q := range queries {
			rs, err := db.Query(q)
			if err != nil {
				panic(err)
			}
			results <- rs
		}
	}()
	// Async function to draw results.
	go func() {
		for rs := range results {
			if err := drawResults(rs); err != nil {
				panic(err)
			}
		}
	}()
	// Start GUI loop.
	err = gui.MainLoop()
	if err != nil && err != gocui.ErrQuit {
		return err
	}
	return nil
}

func quit(_ *gocui.Gui, _ *gocui.View) error {
	if err := db.WriteSetting(&set); err != nil {
		return err
	}
	return gocui.ErrQuit
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	/*
		Layout looks something like this:
		+--------------------------------------------------+
		|            resultsWindow                         |
		|                                                  |
		|+------------------------+------------------------+
		||  searchTitle           | searchBar             ||
		|+------------------------+------------------------|
		||  toolBar                                       ||
		+-+-----------------------------------------------++
	*/
	// resultsWindow
	if v, err := g.SetView(resultsWindow, -1, -1, maxX, maxY-4); err != nil {
		v.Frame = true
		v.Highlight = true
		v.SelBgColor, v.SelFgColor = v.FgColor, v.BgColor
	}
	// searchTitle
	searchTxt := "Search:"
	if v, err := g.SetView(searchTitle, -1, maxY-4, len(searchTxt), maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Frame = false
		v.FgColor = gocui.AttrBold
		fmt.Fprint(v, searchTxt)
	}
	// searchBar
	if v, err := g.SetView(searchBar, len(searchTxt), maxY-4, maxX, maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Editable = true
		v.Wrap = true
		v.Frame = false
	}
	// toolBar
	if v, err := g.SetView(toolBar, -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Frame = false
		drawSettings(v)
	}

	return g.SetCurrentView(searchBar)
}

func setKeybindings() error {
	for k, v := range keybindings {
		if err := gui.SetKeybinding("", k, gocui.ModNone, v); err != nil {
			return err
		}
	}
	return nil
}

func guiFindAsYouType(shellSessionID string, db database, qs chan<- query) error {
	v, err := gui.View(searchBar)
	if err != nil {
		return err
	}
	s := strings.TrimSuffix(v.Buffer(), "\n")
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	h, err := os.Hostname()
	if err != nil {
		return err
	}
	q := query{
		Cmd:        &s,
		SortByFreq: set.SortByFreq,
	}
	if set.OnlyMySession {
		q.Hostname = &h
		q.ShellSessionID = &shellSessionID
	}
	if set.OnlyMyCwd {
		q.Dir = &wd
	}
	qs <- q
	return nil
}

func moveResultLine(up bool) error {
	v, err := gui.View(resultsWindow)
	if err != nil {
		return err
	}
	if up && resultsOffset > 0 {
		v.MoveCursor(0, -1, false)
		resultsOffset--
	} else if !up && resultsOffset < len(currentResults)-1 {
		v.MoveCursor(0, 1, false)
		resultsOffset++
	}
	return nil
}

func drawSettings(v *gocui.View) error {
	v, err := gui.View(toolBar)
	if err != nil {
		return err
	}
	v.Clear()
	maxX, _ := gui.Size()
	var a, b, s, c, d rune
	if set.SortByFreq {
		a, b, s = ' ', '*', 'f'
	} else {
		a, b, s = '*', ' ', 't'
	}
	// leftL is the "long" option; leftS is the "short" one.
	leftL := fmt.Sprintf("[^S]ort by [%c] time [%c] freq", a, b)
	leftS := fmt.Sprintf("[^S][%c]", s)
	// Middle (long and short).
	if set.OnlyMySession {
		c = '*'
	} else {
		c = ' '
	}
	middleL := fmt.Sprintf("[^L]imit to my session [%c]", c)
	middleS := fmt.Sprintf("[^L][%c]", c)

	// Right (long and short).
	if set.OnlyMyCwd {
		d = '*'
	} else {
		d = ' '
	}
	rightL := fmt.Sprintf("[^W]orking dir only [%c]", d)
	rightS := fmt.Sprintf("[^W][%c]", d)

	// Now choose the long or short form and pad it.
	var left, middle, right string
	if len(leftL)+len(middleL)+len(rightL) < maxX {
		left, middle, right = leftL, middleL, rightL
	} else {
		left, middle, right = leftS, middleS, rightS
	}
	var lpad, rpad []byte
	if len(left)+len(middle)/2 >= maxX/2 {
		lpad = []byte{}
	} else {
		lpad = make([]byte, maxX/2-len(left)-len(middle)/2)
	}
	if len(right)+len(middle)/2 >= maxX/2 {
		rpad = []byte{}
	} else {
		rpad = make([]byte, maxX/2-len(right)-len(middle)/2)
	}
	fmt.Fprint(v, left+string(lpad)+middle+string(rpad)+right)
	guiFindAsYouType(shellSessionID, db, queries)
	return nil
}

func drawResults(rs []record) error {
	v, err := gui.View(resultsWindow)
	if err != nil {
		return err
	}
	v.Clear()
	v.SetCursor(0, 0)
	resultsOffset = 0
	currentResults = rs
	for _, r := range rs {
		fmt.Fprintf(v, "%s\t\t|\t\t%s\n", r.Time.Format("2006/01/02 15:04:05"), strings.Replace(r.Cmd, "\n", " ", -1))
	}
	return nil
}
<commit_msg>Fix unsafe UI modification. Flush find-as-you-type channel before searching.<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
	"sync"

	"github.com/jroimartin/gocui"
)

const (
	bufSize = 128 // How many queries or results to queue during async find-as-you-type.
	// View names.
	resultsWindow = "resultsWindow"
	searchTitle   = "searchTitle"
	searchBar     = "searchBar"
	toolBar       = "toolBar"
)

var (
	// GUI state.
	gui            *gocui.Gui
	db             database
	shellSessionID string
	queries        chan query
	results        chan []record
	resultsOffset  int
	once           sync.Once
	// Settings.
	set setting
	// Currently-displayed results.
	currentResults []record
	// Key binding map.
	keybindings = map[gocui.Key]gocui.KeybindingHandler{
		gocui.KeyCtrlC: quit,
		gocui.KeyCtrlD: quit,
		gocui.KeyCtrlS: func(g *gocui.Gui, v *gocui.View) error {
			set.SortByFreq = !set.SortByFreq
			v, err := g.View(toolBar)
			if err != nil {
				return err
			}
			return drawSettings(v)
		},
		gocui.KeyCtrlL: func(g *gocui.Gui, v *gocui.View) error {
			set.OnlyMySession = !set.OnlyMySession
			v, err := g.View(toolBar)
			if err != nil {
				return err
			}
			return drawSettings(v)
		},
		gocui.KeyCtrlW: func(g *gocui.Gui, v *gocui.View) error {
			set.OnlyMyCwd = !set.OnlyMyCwd
			v, err := g.View(toolBar)
			if err != nil {
				return err
			}
			return drawSettings(v)
		},
		gocui.KeyArrowUp:   func(g *gocui.Gui, v *gocui.View) error { moveResultLine(true); return nil },
		gocui.KeyArrowDown: func(g *gocui.Gui, v *gocui.View) error { moveResultLine(false); return nil },
		gocui.KeyEnter: func(g *gocui.Gui, v *gocui.View) error {
			v, err := g.View(resultsWindow)
			if err != nil {
				return err
			}
			if resultsOffset < len(currentResults) {
				os.Stderr.WriteString(currentResults[resultsOffset].Cmd)
			}
			return quit(g, v)
		},
	}
)

func runGui(d database, shellID, initialQuery string) error {
	var err error
	gui = gocui.NewGui()
	db = d
	set, err = db.Setting()
	if err != nil {
		return err
	}
	shellSessionID = shellID
	if err := gui.Init(); err != nil {
		return err
	}
	defer gui.Close()
	gui.SetLayout(func(g *gocui.Gui) error {
		if err := layout(g); err != nil {
			return err
		}
		// Prefill the search bar. This is ugly, I admit.
		once.Do(func() {
			v, err := g.View(searchBar)
			if err != nil {
				panic(err)
			}
			for _, c := range initialQuery {
				v.EditWrite(c)
			}
			if err := guiFindAsYouType(shellSessionID, db, queries); err != nil {
				panic(err)
			}
		})
		return nil
	})
	if err := setKeybindings(); err != nil {
		return err
	}
	// Channels for sending queries and getting responses.
	queries = make(chan query, bufSize)
	results = make(chan []record, bufSize)
	// Set the editor to do find-as-you-type.
	gui.Editor = gocui.EditorFunc(func(v *gocui.View, k gocui.Key, c rune, m gocui.Modifier) {
		if _, ok := keybindings[k]; ok {
			return
		}
		gocui.DefaultEditor.Edit(v, k, c, m)
		guiFindAsYouType(shellSessionID, db, queries)
	})
	// Async function to execute queries.
	go func() {
		for q := range queries {
			// Try a non-blocking read on the channel to get to the most recent query.
			for {
				empty := false
				select {
				case q = <-queries:
					empty = false
				default:
					empty = true
				}
				if empty {
					break
				}
			}
			rs, err := db.Query(q)
			if err != nil {
				panic(err)
			}
			results <- rs
		}
	}()
	// Async function to draw results.
	go func() {
		for rs := range results {
			drawResults(rs)
		}
	}()
	// Start GUI loop.
	err = gui.MainLoop()
	if err != nil && err != gocui.ErrQuit {
		return err
	}
	return nil
}

func quit(_ *gocui.Gui, _ *gocui.View) error {
	if err := db.WriteSetting(&set); err != nil {
		return err
	}
	return gocui.ErrQuit
}

func layout(g *gocui.Gui) error {
	maxX, maxY := g.Size()
	/*
		Layout looks something like this:
		+--------------------------------------------------+
		|            resultsWindow                         |
		|                                                  |
		|+------------------------+------------------------+
		||  searchTitle           | searchBar             ||
		|+------------------------+------------------------|
		||  toolBar                                       ||
		+-+-----------------------------------------------++
	*/
	// resultsWindow
	if v, err := g.SetView(resultsWindow, -1, -1, maxX, maxY-4); err != nil {
		v.Frame = true
		v.Highlight = true
		v.SelBgColor, v.SelFgColor = v.FgColor, v.BgColor
	}
	// searchTitle
	searchTxt := "Search:"
	if v, err := g.SetView(searchTitle, -1, maxY-4, len(searchTxt), maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Frame = false
		v.FgColor = gocui.AttrBold
		fmt.Fprint(v, searchTxt)
	}
	// searchBar
	if v, err := g.SetView(searchBar, len(searchTxt), maxY-4, maxX, maxY-2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Editable = true
		v.Wrap = true
		v.Frame = false
	}
	// toolBar
	if v, err := g.SetView(toolBar, -1, maxY-2, maxX, maxY); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}
		v.Frame = false
		drawSettings(v)
	}

	return g.SetCurrentView(searchBar)
}

func setKeybindings() error {
	for k, v := range keybindings {
		if err := gui.SetKeybinding("", k, gocui.ModNone, v); err != nil {
			return err
		}
	}
	return nil
}

func guiFindAsYouType(shellSessionID string, db database, qs chan<- query) error {
	v, err := gui.View(searchBar)
	if err != nil {
		return err
	}
	s := strings.TrimSuffix(v.Buffer(), "\n")
	wd, err := os.Getwd()
	if err != nil {
		return err
	}
	h, err := os.Hostname()
	if err != nil {
		return err
	}
	q := query{
		Cmd:        &s,
		SortByFreq: set.SortByFreq,
	}
	if set.OnlyMySession {
		q.Hostname = &h
		q.ShellSessionID = &shellSessionID
	}
	if set.OnlyMyCwd {
		q.Dir = &wd
	}
	qs <- q
	return nil
}

func moveResultLine(up bool) {
	gui.Execute(func(g *gocui.Gui) error {
		v, err := g.View(resultsWindow)
		if err != nil {
			return err
		}
		if up && resultsOffset > 0 {
			v.MoveCursor(0, -1, false)
			resultsOffset--
		} else if !up && resultsOffset < len(currentResults)-1 {
			v.MoveCursor(0, 1, false)
			resultsOffset++
		}
		return nil
	})
}

func drawSettings(v *gocui.View) error {
	v.Clear()
	maxX, _ := gui.Size()
	var a, b, s, c, d rune
	if set.SortByFreq {
		a, b, s = ' ', '*', 'f'
	} else {
		a, b, s = '*', ' ', 't'
	}
	// leftL is the "long" option; leftS is the "short" one.
	leftL := fmt.Sprintf("[^S]ort by [%c] time [%c] freq", a, b)
	leftS := fmt.Sprintf("[^S][%c]", s)
	// Middle (long and short).
	if set.OnlyMySession {
		c = '*'
	} else {
		c = ' '
	}
	middleL := fmt.Sprintf("[^L]imit to my session [%c]", c)
	middleS := fmt.Sprintf("[^L][%c]", c)

	// Right (long and short).
	if set.OnlyMyCwd {
		d = '*'
	} else {
		d = ' '
	}
	rightL := fmt.Sprintf("[^W]orking dir only [%c]", d)
	rightS := fmt.Sprintf("[^W][%c]", d)

	// Now choose the long or short form and pad it.
	var left, middle, right string
	if len(leftL)+len(middleL)+len(rightL) < maxX {
		left, middle, right = leftL, middleL, rightL
	} else {
		left, middle, right = leftS, middleS, rightS
	}
	var lpad, rpad []byte
	if len(left)+len(middle)/2 >= maxX/2 {
		lpad = []byte{}
	} else {
		lpad = make([]byte, maxX/2-len(left)-len(middle)/2)
	}
	if len(right)+len(middle)/2 >= maxX/2 {
		rpad = []byte{}
	} else {
		rpad = make([]byte, maxX/2-len(right)-len(middle)/2)
	}
	fmt.Fprint(v, left+string(lpad)+middle+string(rpad)+right)
	guiFindAsYouType(shellSessionID, db, queries)
	return nil
}

func drawResults(rs []record) {
	gui.Execute(func(g *gocui.Gui) error {
		v, err := g.View(resultsWindow)
		if err != nil {
			return err
		}
		v.Clear()
		v.SetCursor(0, 0)
		resultsOffset = 0
		currentResults = rs
		for _, r := range rs {
			fmt.Fprintf(v, "%s\t\t|\t\t%s\n", r.Time.Format("2006/01/02 15:04:05"), strings.Replace(r.Cmd, "\n", " ", -1))
		}
		return nil
	})
}
<|endoftext|>


<commit_before>package hdf5

// #include "hdf5.h"
// #include <stdlib.h>
// #include <string.h>
import "C"

import (
	"fmt"
	"reflect"
	"runtime"
	"unsafe"
)

type Datatype struct {
	Location
	rt reflect.Type
}

type TypeClass C.H5T_class_t

const (
	T_NO_CLASS  TypeClass = -1 // Error
	T_INTEGER   TypeClass = 0  // integer types
	T_FLOAT     TypeClass = 1  // floating-point types
	T_TIME      TypeClass = 2  // date and time types
	T_STRING    TypeClass = 3  // character string types
	T_BITFIELD  TypeClass = 4  // bit field types
	T_OPAQUE    TypeClass = 5  // opaque types
	T_COMPOUND  TypeClass = 6  // compound types
	T_REFERENCE TypeClass = 7  // reference types
	T_ENUM      TypeClass = 8  // enumeration types
	T_VLEN      TypeClass = 9  // variable-length types
	T_ARRAY     TypeClass = 10 // array types
	T_NCLASSES  TypeClass = 11 // nbr of classes -- MUST BE LAST
)

// list of go types
var (
	_go_string_t reflect.Type = reflect.TypeOf(string(""))
	_go_int_t    reflect.Type = reflect.TypeOf(int(0))
	_go_int8_t   reflect.Type = reflect.TypeOf(int8(0))
	_go_int16_t  reflect.Type = reflect.TypeOf(int16(0))
	_go_int32_t  reflect.Type = reflect.TypeOf(int32(0))
	_go_int64_t  reflect.Type = reflect.TypeOf(int64(0))
	_go_uint_t   reflect.Type = reflect.TypeOf(uint(0))
	_go_uint8_t  reflect.Type = reflect.TypeOf(uint8(0))
	_go_uint16_t reflect.Type = reflect.TypeOf(uint16(0))
	_go_uint32_t reflect.Type = reflect.TypeOf(uint32(0))
	_go_uint64_t reflect.Type = reflect.TypeOf(uint64(0))

	_go_float32_t reflect.Type = reflect.TypeOf(float32(0))
	_go_float64_t reflect.Type = reflect.TypeOf(float64(0))

	_go_array_t reflect.Type = reflect.TypeOf([1]int{0})
	_go_slice_t reflect.Type = reflect.TypeOf([]int{0})

	_go_struct_t reflect.Type = reflect.TypeOf(struct{}{})

	_go_ptr_t reflect.Type = reflect.PtrTo(_go_int_t)
)

type typeClassToType map[TypeClass]reflect.Type

var (
	// mapping of type-class to go-type
	_type_cls_to_go_type typeClassToType = typeClassToType{
		T_NO_CLASS:  nil,
		T_INTEGER:   _go_int_t,
		T_FLOAT:     _go_float32_t,
		T_TIME:      nil,
		T_STRING:    _go_string_t,
		T_BITFIELD:  nil,
		T_OPAQUE:    nil,
		T_COMPOUND:  _go_struct_t,
		T_REFERENCE: _go_ptr_t,
		T_ENUM:      _go_int_t,
		T_VLEN:      _go_slice_t,
		T_ARRAY:     _go_array_t,
	}
)

func openDatatype(loc_id C.hid_t, name string, tapl_id int) (*Datatype, error) {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))

	id := C.H5Topen2(C.hid_t(loc_id), c_name, C.hid_t(tapl_id))
	err := h5err(C.herr_t(id))
	if err != nil {
		return nil, err
	}
	dt := &Datatype{Location{id}, nil}
	runtime.SetFinalizer(dt, (*Datatype).finalizer)
	return dt, err
}

func NewDatatype(id C.hid_t, rt reflect.Type) *Datatype {
	t := &Datatype{Location{id}, rt}
	runtime.SetFinalizer(t, (*Datatype).finalizer)
	return t
}

// Creates a new datatype.
func CreateDatatype(class TypeClass, size int) (t *Datatype, err error) {
	t = nil
	err = nil

	hid := C.H5Tcreate(C.H5T_class_t(class), C.size_t(size))
	err = h5err(C.herr_t(int(hid)))
	if err != nil {
		return
	}
	t = NewDatatype(hid, _type_cls_to_go_type[class])
	return
}

func (t *Datatype) finalizer() {
	err := t.Close()
	if err != nil {
		panic(fmt.Sprintf("error closing datatype: %s", err))
	}
}

// Releases a datatype.
func (t *Datatype) Close() error {
	if t.id > 0 {
		fmt.Printf("--- closing dtype [%d]...\n", t.id)
		err := h5err(C.H5Tclose(t.id))
		t.id = 0
		return err
	}
	return nil
}

// Determines whether a datatype is a named type or a transient type.
func (t *Datatype) Committed() bool {
	o := int(C.H5Tcommitted(t.id))
	if o > 0 {
		return true
	}
	return false
}

// Copies an existing datatype.
func (t *Datatype) Copy() (*Datatype, error) {
	hid := C.H5Tcopy(t.id)
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	o := NewDatatype(hid, t.rt)
	return o, err
}

// Determines whether two datatype identifiers refer to the same datatype.
func (t *Datatype) Equal(o *Datatype) bool {
	v := int(C.H5Tequal(t.id, o.id))
	if v > 0 {
		return true
	}
	return false
}

// Locks a datatype.
func (t *Datatype) Lock() error {
	return h5err(C.H5Tlock(t.id))
}

// Size returns the size of the Datatype.
func (t *Datatype) Size() uint {
	return uint(C.H5Tget_size(t.id))
}

// SetSize sets the total size of a Datatype.
func (t *Datatype) SetSize(sz uint) error {
	err := C.H5Tset_size(t.id, C.size_t(sz))
	return h5err(err)
}

type ArrayType struct {
	Datatype
}

func new_array_type(id C.hid_t) *ArrayType {
	t := &ArrayType{Datatype{Location{id}, nil}}
	return t
}

func NewArrayType(base_type *Datatype, dims []int) (*ArrayType, error) {
	ndims := C.uint(len(dims))
	c_dims := (*C.hsize_t)(unsafe.Pointer(&dims[0]))

	hid := C.H5Tarray_create2(base_type.id, ndims, c_dims)
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	t := new_array_type(hid)
	return t, err
}

// Returns the rank of an array datatype.
func (t *ArrayType) NDims() int {
	return int(C.H5Tget_array_ndims(t.id))
}

// Retrieves sizes of array dimensions.
func (t *ArrayType) ArrayDims() []int {
	rank := t.NDims()
	dims := make([]int, rank)
	// fixme: int/hsize_t size!
	c_dims := (*C.hsize_t)(unsafe.Pointer(&dims[0]))
	c_rank := int(C.H5Tget_array_dims2(t.id, c_dims))
	if c_rank == rank {
		return dims
	}
	return nil
}

type VarLenType struct {
	Datatype
}

func NewVarLenType(base_type *Datatype) (*VarLenType, error) {
	id := C.H5Tvlen_create(base_type.id)
	err := h5err(C.herr_t(int(id)))
	if err != nil {
		return nil, err
	}
	t := &VarLenType{Datatype{Location{id}, nil}}
	runtime.SetFinalizer(t, (*VarLenType).finalizer)
	return t, err
}

// Determines whether datatype is a variable-length string.
// htri_t H5Tis_variable_str( hid_t dtype_id )
func (vl *VarLenType) IsVariableStr() bool {
	o := int(C.H5Tis_variable_str(vl.id))
	if o > 0 {
		return true
	}
	return false
}

type CompoundType struct {
	Datatype
}

// Retrieves the number of elements in a compound or enumeration datatype.
func (t *CompoundType) NMembers() int {
	return int(C.H5Tget_nmembers(t.id))
}

// Returns datatype class of compound datatype member.
func (t *CompoundType) MemberClass(mbr_idx int) TypeClass {
	return TypeClass(C.H5Tget_member_class(t.id, C.uint(mbr_idx)))
}

// Retrieves the name of a compound or enumeration datatype member.
func (t *CompoundType) MemberName(mbr_idx int) string {
	c_name := C.H5Tget_member_name(t.id, C.uint(mbr_idx))
	return C.GoString(c_name)
}

// Retrieves the index of a compound or enumeration datatype member.
func (t *CompoundType) MemberIndex(name string) int {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	return int(C.H5Tget_member_index(t.id, c_name))
}

// Retrieves the offset of a field of a compound datatype.
func (t *CompoundType) MemberOffset(mbr_idx int) int {
	return int(C.H5Tget_member_offset(t.id, C.uint(mbr_idx)))
}

// Returns the datatype of the specified member.
func (t *CompoundType) MemberType(mbr_idx int) (*Datatype, error) {
	hid := C.H5Tget_member_type(t.id, C.uint(mbr_idx))
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	dt := NewDatatype(hid, t.rt.Field(mbr_idx).Type)
	return dt, nil
}

// Adds a new member to a compound datatype.
func (t *CompoundType) Insert(name string, offset int, field *Datatype) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	return h5err(C.H5Tinsert(t.id, cname, C.size_t(offset), field.id))
}

// Recursively removes padding from within a compound datatype.
func (t *CompoundType) Pack() error {
	return h5err(C.H5Tpack(t.id))
}

type OpaqueDatatype struct {
	Datatype
}

// Tags an opaque datatype.
func (t *OpaqueDatatype) SetTag(tag string) error {
	ctag := C.CString(tag)
	defer C.free(unsafe.Pointer(ctag))
	return h5err(C.H5Tset_tag(t.id, ctag))
}

// Gets the tag associated with an opaque datatype.
func (t *OpaqueDatatype) Tag() string {
	cname := C.H5Tget_tag(t.id)
	if cname != nil {
		return C.GoString(cname)
	}
	return ""
}

// NewDatatypeFromValue creates  a datatype from a value in an interface.
func NewDatatypeFromValue(v interface{}) *Datatype {
	t := reflect.TypeOf(v)
	return newDataTypeFromType(t)
}

func newDataTypeFromType(t reflect.Type) *Datatype {

	var dt *Datatype = nil

	switch t.Kind() {

	case reflect.Int:
		dt = T_NATIVE_INT // FIXME: .Copy() instead ?

	case reflect.Int8:
		dt = T_NATIVE_INT8

	case reflect.Int16:
		dt = T_NATIVE_INT16

	case reflect.Int32:
		dt = T_NATIVE_INT32

	case reflect.Int64:
		dt = T_NATIVE_INT64

	case reflect.Uint:
		dt = T_NATIVE_UINT // FIXME: .Copy() instead ?

	case reflect.Uint8:
		dt = T_NATIVE_UINT8

	case reflect.Uint16:
		dt = T_NATIVE_UINT16

	case reflect.Uint32:
		dt = T_NATIVE_UINT32

	case reflect.Uint64:
		dt = T_NATIVE_UINT64

	case reflect.Float32:
		dt = T_NATIVE_FLOAT

	case reflect.Float64:
		dt = T_NATIVE_DOUBLE

	case reflect.String:
		dt = T_GO_STRING

	case reflect.Array:
		elem_type := newDataTypeFromType(t.Elem())
		dims := getArrayDims(t)
		adt, err := NewArrayType(elem_type, dims)
		if err != nil {
			panic(err)
		}
		dt, err = adt.Copy()
		if err != nil {
			panic(err)
		}

	case reflect.Struct:
		sz := int(t.Size())
		hdf_dt, err := CreateDatatype(T_COMPOUND, sz)
		if err != nil {
			panic(err)
		}
		cdt := &CompoundType{*hdf_dt}
		n := t.NumField()
		for i := 0; i < n; i++ {
			f := t.Field(i)
			var field_dt *Datatype = nil
			field_dt = newDataTypeFromType(f.Type)
			offset := int(f.Offset + 0)
			if field_dt == nil {
				panic(fmt.Sprintf("pb with field [%d-%s]", i, f.Name))
			}
			field_name := string(f.Tag)
			if len(field_name) == 0 {
				field_name = f.Name
			}
			err = cdt.Insert(field_name, offset, field_dt)
			if err != nil {
				panic(fmt.Sprintf("pb with field [%d-%s]: %s", i, f.Name, err))
			}
		}
		cdt.Lock()
		dt, err = cdt.Copy()
		if err != nil {
			panic(err)
		}

	default:
		panic(fmt.Sprintf("unhandled kind (%v)", t.Kind()))
	}

	return dt
}

func getArrayDims(dt reflect.Type) []int {
	result := []int{}
	if dt.Kind() == reflect.Array {
		result = append(result, dt.Len())
		for _, dim := range getArrayDims(dt.Elem()) {
			result = append(result, dim)
		}
	}
	return result
}
<commit_msg>Remove extraneous print when closing a Dataspace<commit_after>package hdf5

// #include "hdf5.h"
// #include <stdlib.h>
// #include <string.h>
import "C"

import (
	"fmt"
	"reflect"
	"runtime"
	"unsafe"
)

type Datatype struct {
	Location
	rt reflect.Type
}

type TypeClass C.H5T_class_t

const (
	T_NO_CLASS  TypeClass = -1 // Error
	T_INTEGER   TypeClass = 0  // integer types
	T_FLOAT     TypeClass = 1  // floating-point types
	T_TIME      TypeClass = 2  // date and time types
	T_STRING    TypeClass = 3  // character string types
	T_BITFIELD  TypeClass = 4  // bit field types
	T_OPAQUE    TypeClass = 5  // opaque types
	T_COMPOUND  TypeClass = 6  // compound types
	T_REFERENCE TypeClass = 7  // reference types
	T_ENUM      TypeClass = 8  // enumeration types
	T_VLEN      TypeClass = 9  // variable-length types
	T_ARRAY     TypeClass = 10 // array types
	T_NCLASSES  TypeClass = 11 // nbr of classes -- MUST BE LAST
)

// list of go types
var (
	_go_string_t reflect.Type = reflect.TypeOf(string(""))
	_go_int_t    reflect.Type = reflect.TypeOf(int(0))
	_go_int8_t   reflect.Type = reflect.TypeOf(int8(0))
	_go_int16_t  reflect.Type = reflect.TypeOf(int16(0))
	_go_int32_t  reflect.Type = reflect.TypeOf(int32(0))
	_go_int64_t  reflect.Type = reflect.TypeOf(int64(0))
	_go_uint_t   reflect.Type = reflect.TypeOf(uint(0))
	_go_uint8_t  reflect.Type = reflect.TypeOf(uint8(0))
	_go_uint16_t reflect.Type = reflect.TypeOf(uint16(0))
	_go_uint32_t reflect.Type = reflect.TypeOf(uint32(0))
	_go_uint64_t reflect.Type = reflect.TypeOf(uint64(0))

	_go_float32_t reflect.Type = reflect.TypeOf(float32(0))
	_go_float64_t reflect.Type = reflect.TypeOf(float64(0))

	_go_array_t reflect.Type = reflect.TypeOf([1]int{0})
	_go_slice_t reflect.Type = reflect.TypeOf([]int{0})

	_go_struct_t reflect.Type = reflect.TypeOf(struct{}{})

	_go_ptr_t reflect.Type = reflect.PtrTo(_go_int_t)
)

type typeClassToType map[TypeClass]reflect.Type

var (
	// mapping of type-class to go-type
	_type_cls_to_go_type typeClassToType = typeClassToType{
		T_NO_CLASS:  nil,
		T_INTEGER:   _go_int_t,
		T_FLOAT:     _go_float32_t,
		T_TIME:      nil,
		T_STRING:    _go_string_t,
		T_BITFIELD:  nil,
		T_OPAQUE:    nil,
		T_COMPOUND:  _go_struct_t,
		T_REFERENCE: _go_ptr_t,
		T_ENUM:      _go_int_t,
		T_VLEN:      _go_slice_t,
		T_ARRAY:     _go_array_t,
	}
)

func openDatatype(loc_id C.hid_t, name string, tapl_id int) (*Datatype, error) {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))

	id := C.H5Topen2(C.hid_t(loc_id), c_name, C.hid_t(tapl_id))
	err := h5err(C.herr_t(id))
	if err != nil {
		return nil, err
	}
	dt := &Datatype{Location{id}, nil}
	runtime.SetFinalizer(dt, (*Datatype).finalizer)
	return dt, err
}

func NewDatatype(id C.hid_t, rt reflect.Type) *Datatype {
	t := &Datatype{Location{id}, rt}
	runtime.SetFinalizer(t, (*Datatype).finalizer)
	return t
}

// Creates a new datatype.
func CreateDatatype(class TypeClass, size int) (t *Datatype, err error) {
	t = nil
	err = nil

	hid := C.H5Tcreate(C.H5T_class_t(class), C.size_t(size))
	err = h5err(C.herr_t(int(hid)))
	if err != nil {
		return
	}
	t = NewDatatype(hid, _type_cls_to_go_type[class])
	return
}

func (t *Datatype) finalizer() {
	err := t.Close()
	if err != nil {
		panic(fmt.Sprintf("error closing datatype: %s", err))
	}
}

// Releases a datatype.
func (t *Datatype) Close() error {
	if t.id > 0 {
		err := h5err(C.H5Tclose(t.id))
		t.id = 0
		return err
	}
	return nil
}

// Determines whether a datatype is a named type or a transient type.
func (t *Datatype) Committed() bool {
	o := int(C.H5Tcommitted(t.id))
	if o > 0 {
		return true
	}
	return false
}

// Copies an existing datatype.
func (t *Datatype) Copy() (*Datatype, error) {
	hid := C.H5Tcopy(t.id)
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	o := NewDatatype(hid, t.rt)
	return o, err
}

// Determines whether two datatype identifiers refer to the same datatype.
func (t *Datatype) Equal(o *Datatype) bool {
	v := int(C.H5Tequal(t.id, o.id))
	if v > 0 {
		return true
	}
	return false
}

// Locks a datatype.
func (t *Datatype) Lock() error {
	return h5err(C.H5Tlock(t.id))
}

// Size returns the size of the Datatype.
func (t *Datatype) Size() uint {
	return uint(C.H5Tget_size(t.id))
}

// SetSize sets the total size of a Datatype.
func (t *Datatype) SetSize(sz uint) error {
	err := C.H5Tset_size(t.id, C.size_t(sz))
	return h5err(err)
}

type ArrayType struct {
	Datatype
}

func new_array_type(id C.hid_t) *ArrayType {
	t := &ArrayType{Datatype{Location{id}, nil}}
	return t
}

func NewArrayType(base_type *Datatype, dims []int) (*ArrayType, error) {
	ndims := C.uint(len(dims))
	c_dims := (*C.hsize_t)(unsafe.Pointer(&dims[0]))

	hid := C.H5Tarray_create2(base_type.id, ndims, c_dims)
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	t := new_array_type(hid)
	return t, err
}

// Returns the rank of an array datatype.
func (t *ArrayType) NDims() int {
	return int(C.H5Tget_array_ndims(t.id))
}

// Retrieves sizes of array dimensions.
func (t *ArrayType) ArrayDims() []int {
	rank := t.NDims()
	dims := make([]int, rank)
	// fixme: int/hsize_t size!
	c_dims := (*C.hsize_t)(unsafe.Pointer(&dims[0]))
	c_rank := int(C.H5Tget_array_dims2(t.id, c_dims))
	if c_rank == rank {
		return dims
	}
	return nil
}

type VarLenType struct {
	Datatype
}

func NewVarLenType(base_type *Datatype) (*VarLenType, error) {
	id := C.H5Tvlen_create(base_type.id)
	err := h5err(C.herr_t(int(id)))
	if err != nil {
		return nil, err
	}
	t := &VarLenType{Datatype{Location{id}, nil}}
	runtime.SetFinalizer(t, (*VarLenType).finalizer)
	return t, err
}

// Determines whether datatype is a variable-length string.
// htri_t H5Tis_variable_str( hid_t dtype_id )
func (vl *VarLenType) IsVariableStr() bool {
	o := int(C.H5Tis_variable_str(vl.id))
	if o > 0 {
		return true
	}
	return false
}

type CompoundType struct {
	Datatype
}

// Retrieves the number of elements in a compound or enumeration datatype.
func (t *CompoundType) NMembers() int {
	return int(C.H5Tget_nmembers(t.id))
}

// Returns datatype class of compound datatype member.
func (t *CompoundType) MemberClass(mbr_idx int) TypeClass {
	return TypeClass(C.H5Tget_member_class(t.id, C.uint(mbr_idx)))
}

// Retrieves the name of a compound or enumeration datatype member.
func (t *CompoundType) MemberName(mbr_idx int) string {
	c_name := C.H5Tget_member_name(t.id, C.uint(mbr_idx))
	return C.GoString(c_name)
}

// Retrieves the index of a compound or enumeration datatype member.
func (t *CompoundType) MemberIndex(name string) int {
	c_name := C.CString(name)
	defer C.free(unsafe.Pointer(c_name))
	return int(C.H5Tget_member_index(t.id, c_name))
}

// Retrieves the offset of a field of a compound datatype.
func (t *CompoundType) MemberOffset(mbr_idx int) int {
	return int(C.H5Tget_member_offset(t.id, C.uint(mbr_idx)))
}

// Returns the datatype of the specified member.
func (t *CompoundType) MemberType(mbr_idx int) (*Datatype, error) {
	hid := C.H5Tget_member_type(t.id, C.uint(mbr_idx))
	err := h5err(C.herr_t(int(hid)))
	if err != nil {
		return nil, err
	}
	dt := NewDatatype(hid, t.rt.Field(mbr_idx).Type)
	return dt, nil
}

// Adds a new member to a compound datatype.
func (t *CompoundType) Insert(name string, offset int, field *Datatype) error {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))
	return h5err(C.H5Tinsert(t.id, cname, C.size_t(offset), field.id))
}

// Recursively removes padding from within a compound datatype.
func (t *CompoundType) Pack() error {
	return h5err(C.H5Tpack(t.id))
}

type OpaqueDatatype struct {
	Datatype
}

// Tags an opaque datatype.
func (t *OpaqueDatatype) SetTag(tag string) error {
	ctag := C.CString(tag)
	defer C.free(unsafe.Pointer(ctag))
	return h5err(C.H5Tset_tag(t.id, ctag))
}

// Gets the tag associated with an opaque datatype.
func (t *OpaqueDatatype) Tag() string {
	cname := C.H5Tget_tag(t.id)
	if cname != nil {
		return C.GoString(cname)
	}
	return ""
}

// NewDatatypeFromValue creates  a datatype from a value in an interface.
func NewDatatypeFromValue(v interface{}) *Datatype {
	t := reflect.TypeOf(v)
	return newDataTypeFromType(t)
}

func newDataTypeFromType(t reflect.Type) *Datatype {

	var dt *Datatype = nil

	switch t.Kind() {

	case reflect.Int:
		dt = T_NATIVE_INT // FIXME: .Copy() instead ?

	case reflect.Int8:
		dt = T_NATIVE_INT8

	case reflect.Int16:
		dt = T_NATIVE_INT16

	case reflect.Int32:
		dt = T_NATIVE_INT32

	case reflect.Int64:
		dt = T_NATIVE_INT64

	case reflect.Uint:
		dt = T_NATIVE_UINT // FIXME: .Copy() instead ?

	case reflect.Uint8:
		dt = T_NATIVE_UINT8

	case reflect.Uint16:
		dt = T_NATIVE_UINT16

	case reflect.Uint32:
		dt = T_NATIVE_UINT32

	case reflect.Uint64:
		dt = T_NATIVE_UINT64

	case reflect.Float32:
		dt = T_NATIVE_FLOAT

	case reflect.Float64:
		dt = T_NATIVE_DOUBLE

	case reflect.String:
		dt = T_GO_STRING

	case reflect.Array:
		elem_type := newDataTypeFromType(t.Elem())
		dims := getArrayDims(t)
		adt, err := NewArrayType(elem_type, dims)
		if err != nil {
			panic(err)
		}
		dt, err = adt.Copy()
		if err != nil {
			panic(err)
		}

	case reflect.Struct:
		sz := int(t.Size())
		hdf_dt, err := CreateDatatype(T_COMPOUND, sz)
		if err != nil {
			panic(err)
		}
		cdt := &CompoundType{*hdf_dt}
		n := t.NumField()
		for i := 0; i < n; i++ {
			f := t.Field(i)
			var field_dt *Datatype = nil
			field_dt = newDataTypeFromType(f.Type)
			offset := int(f.Offset + 0)
			if field_dt == nil {
				panic(fmt.Sprintf("pb with field [%d-%s]", i, f.Name))
			}
			field_name := string(f.Tag)
			if len(field_name) == 0 {
				field_name = f.Name
			}
			err = cdt.Insert(field_name, offset, field_dt)
			if err != nil {
				panic(fmt.Sprintf("pb with field [%d-%s]: %s", i, f.Name, err))
			}
		}
		cdt.Lock()
		dt, err = cdt.Copy()
		if err != nil {
			panic(err)
		}

	default:
		panic(fmt.Sprintf("unhandled kind (%v)", t.Kind()))
	}

	return dt
}

func getArrayDims(dt reflect.Type) []int {
	result := []int{}
	if dt.Kind() == reflect.Array {
		result = append(result, dt.Len())
		for _, dim := range getArrayDims(dt.Elem()) {
			result = append(result, dim)
		}
	}
	return result
}
<|endoftext|>


<commit_before>package main

import (
	"sync"

	"github.com/hexbotio/hex/core"
	"github.com/hexbotio/hex/models"
)

var version string

func main() {
	var wg sync.WaitGroup
	var config models.Config
	config.Version = version
	core.Params(&config)
	core.Config(&config)
	core.Starter(&config)
	inputMsgs := make(chan models.Message, 1)
	outputMsgs := make(chan models.Message, 1)
	wg.Add(core.ActiveServices(&config) + 3)
	go core.Inputs(inputMsgs, &config)
	go core.Pipeline(inputMsgs, outputMsgs, &config)
	go core.Outputs(outputMsgs, &config)
	defer wg.Done()
	wg.Wait()
}
<commit_msg>simplifying the wait group to a fixed number<commit_after>package main

import (
	"sync"

	"github.com/hexbotio/hex/core"
	"github.com/hexbotio/hex/models"
)

var version string

func main() {
	var wg sync.WaitGroup
	var config models.Config
	config.Version = version
	core.Params(&config)
	core.Config(&config)
	core.Starter(&config)
	inputMsgs := make(chan models.Message, 1)
	outputMsgs := make(chan models.Message, 1)
	wg.Add(3)
	go core.Inputs(inputMsgs, &config)
	go core.Pipeline(inputMsgs, outputMsgs, &config)
	go core.Outputs(outputMsgs, &config)
	defer wg.Done()
	wg.Wait()
}
<|endoftext|>


<commit_before>package gopivo

import (
	"errors"
	"io"
	"sync"
	"time"
)
const defaultJoinLimitRatePerSecond = 64
const defaultJoinLimitRateBurst     = 32
const defaultJoinMaxQueueSize       = 256

var (
	ErrJoinQueueIsFull     = errors.New("join queue is full")
	ErrNoSuchConnector     = errors.New("no such connector")
	ErrReceiverHasGoneAway = errors.New("receiver has gone away")
)

type Connector interface {
	Closer(error) error
	Initialize() (chan []byte, error)
	Receiver(io.Reader) error
	Sender()
}

type Hub struct {
	Name      string
	lock      *sync.Mutex
	ports     Port
	queue     chan chan bool
	throttle  chan time.Time
}

type Port map[Connector]chan []byte

func NewHub(name string) *Hub {
	h := &Hub{
		Name:      name,
		lock:      &sync.Mutex{},
		ports:     make(Port),
		queue:     make(chan chan bool, defaultJoinMaxQueueSize),
		throttle:  make(chan time.Time, defaultJoinLimitRateBurst),
	}
	go h.run()
	return h
}

func (h Hub) run() {
	go h.ticker(defaultJoinLimitRatePerSecond)
	for waiter := range h.queue {
		<-h.throttle
		waiter <- true
	}
}

func (h Hub) ticker(rate time.Duration) {
	for ns := range time.Tick(time.Second / rate) {
		h.throttle <- ns
	}
}

func (h Hub) waitQueue() error {
	waiter := make(chan bool)
	defer close(waiter)

	select {
	case h.queue <- waiter:
	default:
		return ErrJoinQueueIsFull
	}
	<-waiter
	return nil
}

func (h Hub) Broadcast() chan []byte {
	messages := make(chan []byte)
	go func() {
		defer close(messages)
		for msg := range messages {
			h.lock.Lock()
			for c, port := range h.ports {
				select {
				case port <- msg:
				default:
					go h.Leave(c)
				}
			}
			h.lock.Unlock()
		}
	}()
	return messages
}

func (h Hub) Join(c Connector, r io.Reader) error {
	if err := h.waitQueue(); err != nil {
		c.Closer(err)
		return err
	}

	port, err := c.Initialize()
	if err != nil {
		c.Closer(err)
		return err
	}
	go c.Sender()

	h.lock.Lock()
	h.ports[c] = port
	h.lock.Unlock()
	defer h.Leave(c)

	if err := c.Receiver(r); err != nil {
		return ErrReceiverHasGoneAway
	}
	return nil
}

func (h Hub) Leave(c Connector) error {
	h.lock.Lock()
	defer h.lock.Unlock()
	if port, ok := h.ports[c]; ok {
		delete(h.ports, c)
		close(port)
		return nil
	}
	return ErrNoSuchConnector
}

/*
func (h Hub) Kill() (error, []error) {
	atomic.AddUint32(&h.state, 1)
	var errors []error
	for conn, _ := range h.ports {
		if err := conn.Closer(); err != nil {
			errors = append(errors, err)
		}
	}

	if len(errors) > 0 {
		return ErrHubKillWentBad, errors
	}
	return nil, nil
}
*/
<commit_msg>gofmt<commit_after>package gopivo

import (
	"errors"
	"io"
	"sync"
	"time"
)

const defaultJoinLimitRatePerSecond = 64
const defaultJoinLimitRateBurst = 32
const defaultJoinMaxQueueSize = 256

var (
	ErrJoinQueueIsFull     = errors.New("join queue is full")
	ErrNoSuchConnector     = errors.New("no such connector")
	ErrReceiverHasGoneAway = errors.New("receiver has gone away")
)

type Connector interface {
	Closer(error) error
	Initialize() (chan []byte, error)
	Receiver(io.Reader) error
	Sender()
}

type Hub struct {
	Name     string
	lock     *sync.Mutex
	ports    Port
	queue    chan chan bool
	throttle chan time.Time
}

type Port map[Connector]chan []byte

func NewHub(name string) *Hub {
	h := &Hub{
		Name:     name,
		lock:     &sync.Mutex{},
		ports:    make(Port),
		queue:    make(chan chan bool, defaultJoinMaxQueueSize),
		throttle: make(chan time.Time, defaultJoinLimitRateBurst),
	}
	go h.run()
	return h
}

func (h Hub) run() {
	go h.ticker(defaultJoinLimitRatePerSecond)
	for waiter := range h.queue {
		<-h.throttle
		waiter <- true
	}
}

func (h Hub) ticker(rate time.Duration) {
	for ns := range time.Tick(time.Second / rate) {
		h.throttle <- ns
	}
}

func (h Hub) waitQueue() error {
	waiter := make(chan bool)
	defer close(waiter)

	select {
	case h.queue <- waiter:
	default:
		return ErrJoinQueueIsFull
	}
	<-waiter
	return nil
}

func (h Hub) Broadcast() chan []byte {
	messages := make(chan []byte)
	go func() {
		defer close(messages)
		for msg := range messages {
			h.lock.Lock()
			for c, port := range h.ports {
				select {
				case port <- msg:
				default:
					go h.Leave(c)
				}
			}
			h.lock.Unlock()
		}
	}()
	return messages
}

func (h Hub) Join(c Connector, r io.Reader) error {
	if err := h.waitQueue(); err != nil {
		c.Closer(err)
		return err
	}

	port, err := c.Initialize()
	if err != nil {
		c.Closer(err)
		return err
	}
	go c.Sender()

	h.lock.Lock()
	h.ports[c] = port
	h.lock.Unlock()
	defer h.Leave(c)

	if err := c.Receiver(r); err != nil {
		return ErrReceiverHasGoneAway
	}
	return nil
}

func (h Hub) Leave(c Connector) error {
	h.lock.Lock()
	defer h.lock.Unlock()
	if port, ok := h.ports[c]; ok {
		delete(h.ports, c)
		close(port)
		return nil
	}
	return ErrNoSuchConnector
}

/*
func (h Hub) Kill() (error, []error) {
	atomic.AddUint32(&h.state, 1)
	var errors []error
	for conn, _ := range h.ports {
		if err := conn.Closer(); err != nil {
			errors = append(errors, err)
		}
	}

	if len(errors) > 0 {
		return ErrHubKillWentBad, errors
	}
	return nil, nil
}
*/
<|endoftext|>


<commit_before>package main
import "fmt"
import "strings"
// import "strconv"
import "time"
import "runtime/pprof"
import "os"

const printDebug = false
const upto = Int64Label(1000000)

var label_hole KLabel = StringLabel(0)
var label_semi KLabel = StringLabel(100)
var label_var KLabel = StringLabel(101)
var label_skip KLabel = StringLabel(102)
var label_plus KLabel = StringLabel(103)
var label_neg KLabel = StringLabel(104)
var label_true KLabel = StringLabel(105)
var label_false KLabel = StringLabel(106)
var label_assign KLabel = StringLabel(107)
var label_while KLabel = StringLabel(108)
var label_if KLabel = StringLabel(109)
var label_not KLabel = StringLabel(110)
var label_lte KLabel = StringLabel(111)
var label_n KLabel = StringLabel(1000)
var label_s KLabel = StringLabel(1001)

var names map[StringLabel]string = map[StringLabel]string{
	0: "hole",
	100: ";",
	101: "var",
	102: "skip",
	103: "plus",
	104: "-",
	105: "true",
	106: "false",
	107: ":=",
	108: "while",
	109: "if",
	110: "not",
	111: "<=",
	1000: "n",
	1001: "s",
}

var hole *K = &K{label_hole, nil, false, false}

type KLabel interface{
	String() string
}

type StringLabel uint32
type Int64Label int64

func (s StringLabel) String() string {
	return names[s]
}
func (s Int64Label) String() string {
	return fmt.Sprintf("%d", s)
}

type K struct {
	label KLabel
	args ListK
	value bool
	variable bool
}
func (k K) String() string {
	return k.label.String() + "(" + k.args.String() + ")"
}
func (k *K) Copy() *K {
	if len(k.args) == 0 {
		return k
	}
	newArgs := make(ListK, len(k.args))
	for i, arg := range k.args {
		// newArgs = append(newArgs, arg.Copy())
		newArgs[i] = arg.Copy()
	}
	copy := &K{k.label, newArgs, k.value, k.variable}
	return copy
}

// func (kl KLabel) String() string {
// 	return string(kl)
// }

type ListK []*K
func (lk ListK) String() string {
	var args []string = nil
	for _, item := range lk {
		args = append(args, item.String())
	}
	return strings.Join(args, ",")
}

type Continuation []*K
func (c Continuation) String() string {
	var args []string = nil
	if len(c) == 0 {
		return "k()"
	}
	for i := len(c) - 1; i >= 0; i-- {
		args = append(args, c[i].String())
	}
	return "k(" + strings.Join(args, "\n  ~> ") + ")"
}

var stateCell map[KLabel]*K = make(map[KLabel]*K)
var kCell Continuation = nil

func stateString() string {
	var state []string = nil
	for variable, value := range stateCell {
		state = append(state, variable.String() + " -> " + value.label.String())
	}
	return "state(" + strings.Join(state, "\n      ") + ")\n" + kCell.String()
}

func main() {
	fmt.Printf("foo\n")

	f, err := os.Create("profiling.dat")
    if err != nil { panic(err) }

	kCell = append(kCell, prog1())
	t0 := time.Now()

    pprof.StartCPUProfile(f)
	repl()
	pprof.StopCPUProfile()
	delta := time.Since(t0)
	fmt.Printf("Took %v\n", delta)
	result := stateCell[label_s]
	fmt.Printf("Result: %d\n", result)
}

/*
var n, s ;
n := 100 ;
s := 0 ;
while not(n <= 0) do (
	s := s + n ;
	n := n + -1
)
*/
func prog1() *K {
	n := &K{label_n, nil, false, true}
	s := &K{label_s, nil, false, true}
	l1 := &K{label_var, []*K{n, s}, false, false}
	hundred := &K{upto, nil, true, false}
	l2 := &K{label_assign, []*K{n, hundred}, false, false}
	zero := &K{Int64Label(0), nil, true, false}
	l3 := &K{label_assign, []*K{s, zero}, false, false}
	sPn := &K{label_plus, []*K{s, n}, false, false}
	l5 := &K{label_assign, []*K{s, sPn}, false, false}
	negOne := &K{label_neg, []*K{&K{Int64Label(1), nil, true, false}}, false, false}
	nPno := &K{label_plus, []*K{n, negOne}, false, false}
	l6 := &K{label_assign, []*K{n, nPno}, false, false}
	body := &K{label_semi, []*K{l5, l6}, false, false}

	nLTzero := &K{label_lte, []*K{n, zero}, false, false}
	guard := &K{label_not, []*K{nLTzero}, false, false}
	l4 := &K{label_while, []*K{guard, body}, false, false}

	pgm := &K{label_semi, []*K{l3, l4}, false, false}
	pgm = &K{label_semi, []*K{l2, pgm}, false, false}
	pgm = &K{label_semi, []*K{l1, pgm}, false, false}
	return pgm
}

func repl() {
	change := true
	for change {
		change = false
		if printDebug {
			fmt.Printf(stateString() + "\n-----------------\n")
		}
		if len(kCell) == 0 {
			break
		}
		if len(kCell) > 10 {
			panic("Safety check!")
		}
		topSpot := len(kCell) - 1
		top := kCell[topSpot]

		if top.value == true {
			if len(kCell) == 1 {
				continue
			}
			next := kCell[topSpot - 1]
			for i, arg := range next.args {
				if arg.label == label_hole {
					if printDebug { fmt.Printf("Applying 'cooling' rule\n") }
					change = true
					kCell = kCell[:topSpot]
					kCell[topSpot-1] = next
					kCell[topSpot-1].args[i] = top
					break
				}
			}
		} else if top.variable == true {
			variable := top.label
			if value, ok := stateCell[variable]; ok {
				if printDebug { fmt.Printf("Applying 'lookup' rule\n") }
				change = true
				kCell[topSpot] = value
			}
		} else if top.label == label_semi {
			if printDebug { fmt.Printf("Applying ';' rule\n") }
			change = true
			kCell = append(kCell, top.args[0])
			kCell[topSpot] = top.args[1]
		} else if top.label == label_var {
			if len(top.args) == 0 {
				if printDebug { fmt.Printf("Applying 'var-empty' rule\n") }
				change = true
				kCell = kCell[:topSpot]
			} else {
				if printDebug { fmt.Printf("Applying 'var-something' rule\n") }
				change = true
				stateCell[top.args[0].label] = &K{Int64Label(0), nil, true, false}
				newTop := top
				newTop.args = newTop.args[1:]
				kCell[topSpot] = newTop
			}
		} else if top.label == label_assign {
			right := top.args[1]
			if !right.value {
				if printDebug { fmt.Printf("Applying ':=-heat' rule\n") }
				change = true
				kCell = append(kCell, right)
				newTop := top
				newTop.args[1] = hole
				kCell[topSpot] = newTop
			} else {
				if printDebug { fmt.Printf("Applying 'assign' rule\n") }
				change = true
				variable := top.args[0].label
				stateCell[variable] = right
				kCell = kCell[:topSpot]
			}
		} else if top.label == label_while {
			if printDebug { fmt.Printf("Applying 'while' rule\n") }
			change = true
			skip := &K{label_skip, nil, false, false}
			guard := top.args[0]
			body := top.args[1]
			then := &K{label_semi, []*K{body, top.Copy()}, false, false}
			theIf := &K{label_if, []*K{guard, then, skip}, false, false}
			kCell[topSpot] = theIf
		} else if top.label == label_if {
			guard := top.args[0]
			if !guard.value {
				if printDebug { fmt.Printf("Applying 'if-heat' rule\n") }
				change = true
				kCell = append(kCell, guard)
				newTop := top
				newTop.args[0] = hole
				kCell[topSpot] = newTop
			} else {
				if guard.label == label_true {
					if printDebug { fmt.Printf("Applying 'if-true' rule\n") }
					change = true
					kCell[topSpot] = top.args[1]
				} else if guard.label == label_false {
					if printDebug { fmt.Printf("Applying 'if-false' rule\n") }
					change = true
					kCell[topSpot] = top.args[2]
				}
			}
		} else if top.label == label_not {
			body := top.args[0]
			if !body.value {
				if printDebug { fmt.Printf("Applying 'not-heat' rule\n") }
				change = true
				kCell = append(kCell, body)
				newTop := top
				newTop.args[0] = hole
				kCell[topSpot] = newTop
			} else {
				if top.args[0].label == label_false {
					if printDebug { fmt.Printf("Applying 'not-false' rule\n") }
					change = true
					kCell[topSpot] = &K{label_true, nil, true, false}
				} else if top.args[0].label == label_true {
					if printDebug { fmt.Printf("Applying 'not-true' rule\n") }
					change = true
					kCell[topSpot] = &K{label_false, nil, true, false}
				}
			}
		} else if top.label == label_lte {
			left := top.args[0]
			right := top.args[1]
			if !left.value {
				if printDebug { fmt.Printf("Applying '<=-heat-left' rule\n") }
				change = true
				kCell = append(kCell, left)
				newTop := top
				newTop.args[0] = hole
				kCell[topSpot] = newTop
			} else if !right.value {
				if printDebug { fmt.Printf("Applying '<=-heat-right' rule\n") }
				change = true
				kCell = append(kCell, right)
				newTop := top
				newTop.args[1] = hole
				kCell[topSpot] = newTop
			} else {
				if printDebug { fmt.Printf("Applying '<=' rule\n") }
				change = true
				leftv := left.label.(Int64Label)
				rightv := right.label.(Int64Label)
				if leftv <= rightv {
					kCell[topSpot] = &K{label_true, nil, true, false}
				} else {
					kCell[topSpot] = &K{label_false, nil, true, false}
				}
			}
		} else if top.label == label_plus {
			left := top.args[0]
			right := top.args[1]
			if !left.value {
				if printDebug { fmt.Printf("Applying '+-heat-left' rule\n") }
				change = true
				kCell = append(kCell, left)
				newTop := top
				newTop.args[0] = hole
				kCell[topSpot] = newTop
			} else if !right.value {
				if printDebug { fmt.Printf("Applying '+-heat-right' rule\n") }
				change = true
				kCell = append(kCell, right)
				newTop := top
				newTop.args[1] = hole
				kCell[topSpot] = newTop
			} else {
				if printDebug { fmt.Printf("Applying '+' rule\n") }
				change = true
				leftv := left.label.(Int64Label)
				rightv := right.label.(Int64Label)
				kCell[topSpot] = &K{Int64Label(leftv + rightv), nil, true, false}
			}
		} else if top.label == label_neg {
			body := top.args[0]
			if !body.value {
				if printDebug { fmt.Printf("Applying 'neg-heat' rule\n") }
				change = true
				kCell = append(kCell, body)
				newTop := top
				newTop.args[0] = hole
				kCell[topSpot] = newTop
			} else {
				if printDebug { fmt.Printf("Applying 'minus' rule\n") }
				change = true
				value := body.label.(Int64Label)
				newValue := -value
				kCell[topSpot] = &K{Int64Label(newValue), nil, true, false}
			}
		} else if top.label == label_skip {
			if printDebug { fmt.Printf("Applying 'skip' rule\n") }
			change = true
			kCell = kCell[:topSpot]
		}
	}
}
<commit_msg>more changes to imp<commit_after>package main
import "fmt"
import "strings"
// import "strconv"
import "time"
import "runtime/pprof"
import "os"

const printDebug = false
const upto = Int64Label(1000000)

var label_hole StringLabel = StringLabel(0)
var label_semi StringLabel = StringLabel(100)
var label_var StringLabel = StringLabel(101)
var label_skip StringLabel = StringLabel(102)
var label_plus StringLabel = StringLabel(103)
var label_neg StringLabel = StringLabel(104)
var label_true StringLabel = StringLabel(105)
var label_false StringLabel = StringLabel(106)
var label_assign StringLabel = StringLabel(107)
var label_while StringLabel = StringLabel(108)
var label_if StringLabel = StringLabel(109)
var label_not StringLabel = StringLabel(110)
var label_lte StringLabel = StringLabel(111)
var label_n StringLabel = StringLabel(1000)
var label_s StringLabel = StringLabel(1001)

var names map[StringLabel]string = map[StringLabel]string{
	0: "hole",
	100: ";",
	101: "var",
	102: "skip",
	103: "plus",
	104: "-",
	105: "true",
	106: "false",
	107: ":=",
	108: "while",
	109: "if",
	110: "not",
	111: "<=",
	1000: "n",
	1001: "s",
}

var hole *K = &K{label_hole, nil, false, false}

type KLabel interface{
	String() string
}

type StringLabel uint32
type Int64Label int64

func (s StringLabel) String() string {
	return names[s]
}
func (s Int64Label) String() string {
	return fmt.Sprintf("%d", s)
}

type K struct {
	label KLabel
	args ListK
	value bool
	variable bool
}
func (k K) String() string {
	return k.label.String() + "(" + k.args.String() + ")"
}
func (k *K) Copy() *K {
	if len(k.args) == 0 {
		return k
	}
	newArgs := make(ListK, len(k.args))
	for i, arg := range k.args {
		newArgs[i] = arg.Copy()
	}
	copy := &K{k.label, newArgs, k.value, k.variable}
	return copy
}

type ListK []*K
func (lk ListK) String() string {
	var args []string = nil
	for _, item := range lk {
		args = append(args, item.String())
	}
	return strings.Join(args, ",")
}

type Continuation []*K
func (c Continuation) String() string {
	var args []string = nil
	if len(c) == 0 {
		return "k()"
	}
	for i := len(c) - 1; i >= 0; i-- {
		args = append(args, c[i].String())
	}
	return "k(" + strings.Join(args, "\n  ~> ") + ")"
}

var stateCell map[KLabel]*K = make(map[KLabel]*K)
var kCell Continuation = nil

func stateString() string {
	var state []string = nil
	for variable, value := range stateCell {
		state = append(state, variable.String() + " -> " + value.label.String())
	}
	return "state(" + strings.Join(state, "\n      ") + ")\n" + kCell.String()
}

func main() {
	fmt.Printf("foo\n")

	f, err := os.Create("profiling.dat")
    if err != nil { panic(err) }

	kCell = append(kCell, prog1())
	t0 := time.Now()

    pprof.StartCPUProfile(f)
	repl()
	pprof.StopCPUProfile()
	delta := time.Since(t0)
	fmt.Printf("Took %v\n", delta)
	result := stateCell[label_s].label.(Int64Label)
	fmt.Printf("Result: %d\n", result)
}

/*
var n, s ;
n := 100 ;
s := 0 ;
while not(n <= 0) do (
	s := s + n ;
	n := n + -1
)
*/
func prog1() *K {
	n := &K{label_n, nil, false, true}
	s := &K{label_s, nil, false, true}
	l1 := &K{label_var, []*K{n, s}, false, false}
	hundred := &K{upto, nil, true, false}
	l2 := &K{label_assign, []*K{n, hundred}, false, false}
	zero := &K{Int64Label(0), nil, true, false}
	l3 := &K{label_assign, []*K{s, zero}, false, false}
	sPn := &K{label_plus, []*K{s, n}, false, false}
	l5 := &K{label_assign, []*K{s, sPn}, false, false}
	negOne := &K{label_neg, []*K{&K{Int64Label(1), nil, true, false}}, false, false}
	nPno := &K{label_plus, []*K{n, negOne}, false, false}
	l6 := &K{label_assign, []*K{n, nPno}, false, false}
	body := &K{label_semi, []*K{l5, l6}, false, false}

	nLTzero := &K{label_lte, []*K{n, zero}, false, false}
	guard := &K{label_not, []*K{nLTzero}, false, false}
	l4 := &K{label_while, []*K{guard, body}, false, false}

	pgm := &K{label_semi, []*K{l3, l4}, false, false}
	pgm = &K{label_semi, []*K{l2, pgm}, false, false}
	pgm = &K{label_semi, []*K{l1, pgm}, false, false}
	return pgm
}

func repl() {
	change := true
	for change {
		change = false
		if printDebug {
			fmt.Printf(stateString() + "\n-----------------\n")
		}
		if len(kCell) == 0 {
			break
		}
		if len(kCell) > 10 {
			panic("Safety check!")
		}
		topSpot := len(kCell) - 1
		top := kCell[topSpot]

		if top.value == true {
			if len(kCell) == 1 {
				continue
			}
			next := kCell[topSpot - 1]
			for i, arg := range next.args {
				if arg.label == label_hole {
					if printDebug { fmt.Printf("Applying 'cooling' rule\n") }
					change = true
					kCell = kCell[:topSpot]
					newTop := next
					newTop.args[i] = top
					kCell[topSpot-1] = newTop
					break
				}
			}
		} else if top.variable == true {
			variable := top.label
			if value, ok := stateCell[variable]; ok {
				if printDebug { fmt.Printf("Applying 'lookup' rule\n") }
				change = true
				kCell[topSpot] = value
			}
		} else {
			var topLabel StringLabel = top.label.(StringLabel)
			if topLabel == label_semi {
				if printDebug { fmt.Printf("Applying ';' rule\n") }
				change = true
				kCell = append(kCell, top.args[0])
				kCell[topSpot] = top.args[1]
			} else if topLabel == label_var {
				if len(top.args) == 0 {
					if printDebug { fmt.Printf("Applying 'var-empty' rule\n") }
					change = true
					kCell = kCell[:topSpot]
				} else {
					if printDebug { fmt.Printf("Applying 'var-something' rule\n") }
					change = true
					stateCell[top.args[0].label] = &K{Int64Label(0), nil, true, false}
					newTop := top
					newTop.args = newTop.args[1:]
					kCell[topSpot] = newTop
				}
			} else if topLabel == label_assign {
				right := top.args[1]
				if !right.value {
					if printDebug { fmt.Printf("Applying ':=-heat' rule\n") }
					change = true
					kCell = append(kCell, right)
					newTop := top
					newTop.args[1] = hole
					kCell[topSpot] = newTop
				} else {
					if printDebug { fmt.Printf("Applying 'assign' rule\n") }
					change = true
					variable := top.args[0].label
					stateCell[variable] = right
					kCell = kCell[:topSpot]
				}
			} else if topLabel == label_while {
				if printDebug { fmt.Printf("Applying 'while' rule\n") }
				change = true
				skip := &K{label_skip, nil, false, false}
				guard := top.args[0].Copy()
				body := top.args[1].Copy()
				then := &K{label_semi, []*K{body, top}, false, false}
				// then := &K{label_semi, []*K{body, top}, false, false}
				theIf := &K{label_if, []*K{guard, then, skip}, false, false}
				kCell[topSpot] = theIf
			} else if topLabel == label_if {
				guard := top.args[0]
				if !guard.value {
					if printDebug { fmt.Printf("Applying 'if-heat' rule\n") }
					change = true
					kCell = append(kCell, guard)
					newTop := top
					newTop.args[0] = hole
					kCell[topSpot] = newTop
				} else {
					if guard.label == label_true {
						if printDebug { fmt.Printf("Applying 'if-true' rule\n") }
						change = true
						kCell[topSpot] = top.args[1]
					} else if guard.label == label_false {
						if printDebug { fmt.Printf("Applying 'if-false' rule\n") }
						change = true
						kCell[topSpot] = top.args[2]
					}
				}
			} else if topLabel == label_not {
				body := top.args[0]
				if !body.value {
					if printDebug { fmt.Printf("Applying 'not-heat' rule\n") }
					change = true
					kCell = append(kCell, body)
					newTop := top
					newTop.args[0] = hole
					kCell[topSpot] = newTop
				} else {
					if top.args[0].label == label_false {
						if printDebug { fmt.Printf("Applying 'not-false' rule\n") }
						change = true
						kCell[topSpot] = &K{label_true, nil, true, false}
					} else if top.args[0].label == label_true {
						if printDebug { fmt.Printf("Applying 'not-true' rule\n") }
						change = true
						kCell[topSpot] = &K{label_false, nil, true, false}
					}
				}
			} else if topLabel == label_lte {
				left := top.args[0]
				right := top.args[1]
				if !left.value {
					if printDebug { fmt.Printf("Applying '<=-heat-left' rule\n") }
					change = true
					kCell = append(kCell, left)
					newTop := top
					newTop.args[0] = hole
					kCell[topSpot] = newTop
				} else if !right.value {
					if printDebug { fmt.Printf("Applying '<=-heat-right' rule\n") }
					change = true
					kCell = append(kCell, right)
					newTop := top
					newTop.args[1] = hole
					kCell[topSpot] = newTop
				} else {
					if printDebug { fmt.Printf("Applying '<=' rule\n") }
					change = true
					leftv := left.label.(Int64Label)
					rightv := right.label.(Int64Label)
					if leftv <= rightv {
						kCell[topSpot] = &K{label_true, nil, true, false}
					} else {
						kCell[topSpot] = &K{label_false, nil, true, false}
					}
				}
			} else if topLabel == label_plus {
				left := top.args[0]
				right := top.args[1]
				if !left.value {
					if printDebug { fmt.Printf("Applying '+-heat-left' rule\n") }
					change = true
					kCell = append(kCell, left)
					newTop := top
					newTop.args[0] = hole
					kCell[topSpot] = newTop
				} else if !right.value {
					if printDebug { fmt.Printf("Applying '+-heat-right' rule\n") }
					change = true
					kCell = append(kCell, right)
					newTop := top
					newTop.args[1] = hole
					kCell[topSpot] = newTop
				} else {
					if printDebug { fmt.Printf("Applying '+' rule\n") }
					change = true
					leftv := left.label.(Int64Label)
					rightv := right.label.(Int64Label)
					kCell[topSpot] = &K{Int64Label(leftv + rightv), nil, true, false}
				}
			} else if topLabel == label_neg {
				body := top.args[0]
				if !body.value {
					if printDebug { fmt.Printf("Applying 'neg-heat' rule\n") }
					change = true
					kCell = append(kCell, body)
					newTop := top
					newTop.args[0] = hole
					kCell[topSpot] = newTop
				} else {
					if printDebug { fmt.Printf("Applying 'minus' rule\n") }
					change = true
					value := body.label.(Int64Label)
					newValue := -value
					kCell[topSpot] = &K{Int64Label(newValue), nil, true, false}
				}
			} else if topLabel == label_skip {
				if printDebug { fmt.Printf("Applying 'skip' rule\n") }
				change = true
				kCell = kCell[:topSpot]
			}
		}
	}
}
<|endoftext|>


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

package main

import (
	"context"
	"crypto/tls"
	"strconv"
	"strings"
	"sync"
	"time"

	irc "github.com/fluffle/goirc/client"
	"github.com/google/alertmanager-irc-relay/logging"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

const (
	pingFrequencySecs          = 60
	connectionTimeoutSecs      = 30
	nickservWaitSecs           = 10
	ircConnectMaxBackoffSecs   = 300
	ircConnectBackoffResetSecs = 1800
)

var (
	ircConnectedGauge = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "irc_connected",
		Help: "Whether the IRC connection is established",
	})
	ircSentMsgs = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "irc_sent_msgs",
		Help: "Number of IRC messages sent"},
		[]string{"ircchannel"},
	)
	ircSendMsgErrors = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "irc_send_msg_errors",
		Help: "Errors while sending IRC messages"},
		[]string{"ircchannel", "error"},
	)
)

func loggerHandler(_ *irc.Conn, line *irc.Line) {
	logging.Info("Received: '%s'", line.Raw)
}

func makeGOIRCConfig(config *Config) *irc.Config {
	ircConfig := irc.NewConfig(config.IRCNick)
	ircConfig.Me.Ident = config.IRCNick
	ircConfig.Me.Name = config.IRCRealName
	ircConfig.Server = strings.Join(
		[]string{config.IRCHost, strconv.Itoa(config.IRCPort)}, ":")
	ircConfig.Pass = config.IRCHostPass
	ircConfig.SSL = config.IRCUseSSL
	ircConfig.SSLConfig = &tls.Config{
		ServerName:         config.IRCHost,
		InsecureSkipVerify: !config.IRCVerifySSL,
	}
	ircConfig.PingFreq = pingFrequencySecs * time.Second
	ircConfig.Timeout = connectionTimeoutSecs * time.Second
	ircConfig.NewNick = func(n string) string { return n + "^" }

	return ircConfig
}

type IRCNotifier struct {
	// Nick stores the nickname specified in the config, because irc.Client
	// might change its copy.
	Nick         string
	NickPassword string

	NickservIdentifyPatterns []string

	Client    *irc.Conn
	AlertMsgs chan AlertMsg

	// irc.Conn has a Connected() method that can tell us wether the TCP
	// connection is up, and thus if we should trigger connect/disconnect.
	// We need to track the session establishment also at a higher level to
	// understand when the server has accepted us and thus when we can join
	// channels, send notices, etc.
	sessionUp         bool
	sessionUpSignal   chan bool
	sessionDownSignal chan bool
	sessionWg         sync.WaitGroup

	channelReconciler *ChannelReconciler

	UsePrivmsg bool

	NickservDelayWait time.Duration
	BackoffCounter    Delayer
	timeTeller        TimeTeller
}

func NewIRCNotifier(config *Config, alertMsgs chan AlertMsg, delayerMaker DelayerMaker, timeTeller TimeTeller) (*IRCNotifier, error) {

	ircConfig := makeGOIRCConfig(config)

	client := irc.Client(ircConfig)

	backoffCounter := delayerMaker.NewDelayer(
		ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs,
		time.Second)

	channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)

	notifier := &IRCNotifier{
		Nick:                     config.IRCNick,
		NickPassword:             config.IRCNickPass,
		NickservIdentifyPatterns: config.NickservIdentifyPatterns,
		Client:                   client,
		AlertMsgs:                alertMsgs,
		sessionUpSignal:          make(chan bool),
		sessionDownSignal:        make(chan bool),
		channelReconciler:        channelReconciler,
		UsePrivmsg:               config.UsePrivmsg,
		NickservDelayWait:        nickservWaitSecs * time.Second,
		BackoffCounter:           backoffCounter,
		timeTeller:               timeTeller,
	}

	notifier.registerHandlers()

	return notifier, nil
}

func (n *IRCNotifier) registerHandlers() {
	n.Client.HandleFunc(irc.CONNECTED,
		func(*irc.Conn, *irc.Line) {
			logging.Info("Session established")
			n.sessionUpSignal <- true
		})

	n.Client.HandleFunc(irc.DISCONNECTED,
		func(*irc.Conn, *irc.Line) {
			logging.Info("Disconnected from IRC")
			n.sessionDownSignal <- false
		})

	n.Client.HandleFunc(irc.NOTICE,
		func(_ *irc.Conn, line *irc.Line) {
			n.HandleNotice(line.Nick, line.Text())
		})

	for _, event := range []string{"433"} {
		n.Client.HandleFunc(event, loggerHandler)
	}
}

func (n *IRCNotifier) HandleNotice(nick string, msg string) {
	logging.Info("Received NOTICE from %s: %s", nick, msg)
	if strings.ToLower(nick) == "nickserv" {
		n.HandleNickservMsg(msg)
	}
}

func (n *IRCNotifier) HandleNickservMsg(msg string) {
	if n.NickPassword == "" {
		logging.Debug("Skip processing NickServ request, no password configured")
		return
	}

	// Remove most common formatting options from NickServ messages
	cleaner := strings.NewReplacer(
		"\001", "", // bold
		"\002", "", // faint
		"\004", "", // underline
	)
	cleanedMsg := cleaner.Replace(msg)

	for _, identifyPattern := range n.NickservIdentifyPatterns {
		logging.Debug("Checking if NickServ message matches identify request '%s'", identifyPattern)
		if strings.Contains(cleanedMsg, identifyPattern) {
			logging.Info("Handling NickServ request to IDENTIFY")
			n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
			return
		}
	}
}

func (n *IRCNotifier) MaybeGhostNick() {
	if n.NickPassword == "" {
		logging.Debug("Skip GHOST check, no password configured")
		return
	}

	currentNick := n.Client.Me().Nick
	if currentNick != n.Nick {
		logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
			currentNick, n.Nick)
		n.Client.Privmsgf("NickServ", "GHOST %s %s", n.Nick,
			n.NickPassword)
		time.Sleep(n.NickservDelayWait)

		logging.Info("Changing nick to '%s'", n.Nick)
		n.Client.Nick(n.Nick)
		time.Sleep(n.NickservDelayWait)
	}
}

func (n *IRCNotifier) MaybeWaitForNickserv() {
	if n.NickPassword == "" {
		logging.Debug("Skip NickServ wait, no password configured")
		return
	}

	// Very lazy/optimistic, but this is good enough for my irssi config,
	// so it should work here as well.
	logging.Info("Waiting for NickServ to notice us and issue an identify request")
	time.Sleep(n.NickservDelayWait)
}

func (n *IRCNotifier) ChannelJoined(ctx context.Context, channel string) bool {

	isJoined, waitJoined := n.channelReconciler.JoinChannel(channel)
	if isJoined {
		return true
	}

	select {
	case <-waitJoined:
		return true
	case <-n.timeTeller.After(ircJoinWaitSecs * time.Second):
		logging.Warn("Channel %s not joined after %d seconds, giving bad news to caller", channel, ircJoinWaitSecs)
		return false
	case <-ctx.Done():
		logging.Info("Context canceled while waiting for join on channel %s", channel)
		return false
	}
}

func (n *IRCNotifier) SendAlertMsg(ctx context.Context, alertMsg *AlertMsg) {
	if !n.sessionUp {
		logging.Error("Cannot send alert to %s : IRC not connected", alertMsg.Channel)
		ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_connected").Inc()
		return
	}
	if !n.ChannelJoined(ctx, alertMsg.Channel) {
		logging.Error("Cannot send alert to %s : cannot join channel", alertMsg.Channel)
		ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_joined").Inc()
		return
	}

	if n.UsePrivmsg {
		n.Client.Privmsg(alertMsg.Channel, alertMsg.Alert)
	} else {
		n.Client.Notice(alertMsg.Channel, alertMsg.Alert)
	}
	ircSentMsgs.WithLabelValues(alertMsg.Channel).Inc()
}

func (n *IRCNotifier) ShutdownPhase() {
	if n.sessionUp {
		logging.Info("IRC client connected, quitting")
		n.Client.Quit("see ya")

		logging.Info("Wait for IRC disconnect to complete")
		select {
		case <-n.sessionDownSignal:
		case <-n.timeTeller.After(n.Client.Config().Timeout):
			logging.Warn("Timeout while waiting for IRC disconnect to complete, stopping anyway")
		}
		n.sessionWg.Done()
	}
	logging.Info("IRC shutdown complete")
}

func (n *IRCNotifier) ConnectedPhase(ctx context.Context) {
	select {
	case alertMsg := <-n.AlertMsgs:
		n.SendAlertMsg(ctx, &alertMsg)
	case <-n.sessionDownSignal:
		n.sessionUp = false
		n.sessionWg.Done()
		n.channelReconciler.Stop()
		n.Client.Quit("see ya")
		ircConnectedGauge.Set(0)
	case <-ctx.Done():
		logging.Info("IRC routine asked to terminate")
	}
}

func (n *IRCNotifier) SetupPhase(ctx context.Context) {
	if !n.Client.Connected() {
		logging.Info("Connecting to IRC %s", n.Client.Config().Server)
		if ok := n.BackoffCounter.DelayContext(ctx); !ok {
			return
		}
		if err := n.Client.ConnectContext(WithWaitGroup(ctx, &n.sessionWg)); err != nil {
			logging.Error("Could not connect to IRC: %s", err)
			return
		}
		logging.Info("Connected to IRC server, waiting to establish session")
	}
	select {
	case <-n.sessionUpSignal:
		n.sessionUp = true
		n.sessionWg.Add(1)
		n.MaybeGhostNick()
		n.MaybeWaitForNickserv()
		n.channelReconciler.Start(ctx)
		ircConnectedGauge.Set(1)
	case <-n.sessionDownSignal:
		logging.Warn("Receiving a session down before the session is up, this is odd")
	case <-ctx.Done():
		logging.Info("IRC routine asked to terminate")
	}
}

func (n *IRCNotifier) Run(ctx context.Context, stopWg *sync.WaitGroup) {
	defer stopWg.Done()

	for ctx.Err() != context.Canceled {
		if !n.sessionUp {
			n.SetupPhase(ctx)
		} else {
			n.ConnectedPhase(ctx)
		}
	}
	n.ShutdownPhase()
}
<commit_msg>add underline cleanup<commit_after>// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"context"
	"crypto/tls"
	"strconv"
	"strings"
	"sync"
	"time"

	irc "github.com/fluffle/goirc/client"
	"github.com/google/alertmanager-irc-relay/logging"
	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promauto"
)

const (
	pingFrequencySecs          = 60
	connectionTimeoutSecs      = 30
	nickservWaitSecs           = 10
	ircConnectMaxBackoffSecs   = 300
	ircConnectBackoffResetSecs = 1800
)

var (
	ircConnectedGauge = promauto.NewGauge(prometheus.GaugeOpts{
		Name: "irc_connected",
		Help: "Whether the IRC connection is established",
	})
	ircSentMsgs = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "irc_sent_msgs",
		Help: "Number of IRC messages sent"},
		[]string{"ircchannel"},
	)
	ircSendMsgErrors = promauto.NewCounterVec(prometheus.CounterOpts{
		Name: "irc_send_msg_errors",
		Help: "Errors while sending IRC messages"},
		[]string{"ircchannel", "error"},
	)
)

func loggerHandler(_ *irc.Conn, line *irc.Line) {
	logging.Info("Received: '%s'", line.Raw)
}

func makeGOIRCConfig(config *Config) *irc.Config {
	ircConfig := irc.NewConfig(config.IRCNick)
	ircConfig.Me.Ident = config.IRCNick
	ircConfig.Me.Name = config.IRCRealName
	ircConfig.Server = strings.Join(
		[]string{config.IRCHost, strconv.Itoa(config.IRCPort)}, ":")
	ircConfig.Pass = config.IRCHostPass
	ircConfig.SSL = config.IRCUseSSL
	ircConfig.SSLConfig = &tls.Config{
		ServerName:         config.IRCHost,
		InsecureSkipVerify: !config.IRCVerifySSL,
	}
	ircConfig.PingFreq = pingFrequencySecs * time.Second
	ircConfig.Timeout = connectionTimeoutSecs * time.Second
	ircConfig.NewNick = func(n string) string { return n + "^" }

	return ircConfig
}

type IRCNotifier struct {
	// Nick stores the nickname specified in the config, because irc.Client
	// might change its copy.
	Nick         string
	NickPassword string

	NickservIdentifyPatterns []string

	Client    *irc.Conn
	AlertMsgs chan AlertMsg

	// irc.Conn has a Connected() method that can tell us wether the TCP
	// connection is up, and thus if we should trigger connect/disconnect.
	// We need to track the session establishment also at a higher level to
	// understand when the server has accepted us and thus when we can join
	// channels, send notices, etc.
	sessionUp         bool
	sessionUpSignal   chan bool
	sessionDownSignal chan bool
	sessionWg         sync.WaitGroup

	channelReconciler *ChannelReconciler

	UsePrivmsg bool

	NickservDelayWait time.Duration
	BackoffCounter    Delayer
	timeTeller        TimeTeller
}

func NewIRCNotifier(config *Config, alertMsgs chan AlertMsg, delayerMaker DelayerMaker, timeTeller TimeTeller) (*IRCNotifier, error) {

	ircConfig := makeGOIRCConfig(config)

	client := irc.Client(ircConfig)

	backoffCounter := delayerMaker.NewDelayer(
		ircConnectMaxBackoffSecs, ircConnectBackoffResetSecs,
		time.Second)

	channelReconciler := NewChannelReconciler(config, client, delayerMaker, timeTeller)

	notifier := &IRCNotifier{
		Nick:                     config.IRCNick,
		NickPassword:             config.IRCNickPass,
		NickservIdentifyPatterns: config.NickservIdentifyPatterns,
		Client:                   client,
		AlertMsgs:                alertMsgs,
		sessionUpSignal:          make(chan bool),
		sessionDownSignal:        make(chan bool),
		channelReconciler:        channelReconciler,
		UsePrivmsg:               config.UsePrivmsg,
		NickservDelayWait:        nickservWaitSecs * time.Second,
		BackoffCounter:           backoffCounter,
		timeTeller:               timeTeller,
	}

	notifier.registerHandlers()

	return notifier, nil
}

func (n *IRCNotifier) registerHandlers() {
	n.Client.HandleFunc(irc.CONNECTED,
		func(*irc.Conn, *irc.Line) {
			logging.Info("Session established")
			n.sessionUpSignal <- true
		})

	n.Client.HandleFunc(irc.DISCONNECTED,
		func(*irc.Conn, *irc.Line) {
			logging.Info("Disconnected from IRC")
			n.sessionDownSignal <- false
		})

	n.Client.HandleFunc(irc.NOTICE,
		func(_ *irc.Conn, line *irc.Line) {
			n.HandleNotice(line.Nick, line.Text())
		})

	for _, event := range []string{"433"} {
		n.Client.HandleFunc(event, loggerHandler)
	}
}

func (n *IRCNotifier) HandleNotice(nick string, msg string) {
	logging.Info("Received NOTICE from %s: %s", nick, msg)
	if strings.ToLower(nick) == "nickserv" {
		n.HandleNickservMsg(msg)
	}
}

func (n *IRCNotifier) HandleNickservMsg(msg string) {
	if n.NickPassword == "" {
		logging.Debug("Skip processing NickServ request, no password configured")
		return
	}

	// Remove most common formatting options from NickServ messages
	cleaner := strings.NewReplacer(
		"\001", "", // bold
		"\002", "", // faint
		"\004", "", // underline
		"\037", "", // underline
	)
	cleanedMsg := cleaner.Replace(msg)

	for _, identifyPattern := range n.NickservIdentifyPatterns {
		logging.Debug("Checking if NickServ message matches identify request '%s'", identifyPattern)
		if strings.Contains(cleanedMsg, identifyPattern) {
			logging.Info("Handling NickServ request to IDENTIFY")
			n.Client.Privmsgf("NickServ", "IDENTIFY %s", n.NickPassword)
			return
		}
	}
}

func (n *IRCNotifier) MaybeGhostNick() {
	if n.NickPassword == "" {
		logging.Debug("Skip GHOST check, no password configured")
		return
	}

	currentNick := n.Client.Me().Nick
	if currentNick != n.Nick {
		logging.Info("My nick is '%s', sending GHOST to NickServ to get '%s'",
			currentNick, n.Nick)
		n.Client.Privmsgf("NickServ", "GHOST %s %s", n.Nick,
			n.NickPassword)
		time.Sleep(n.NickservDelayWait)

		logging.Info("Changing nick to '%s'", n.Nick)
		n.Client.Nick(n.Nick)
		time.Sleep(n.NickservDelayWait)
	}
}

func (n *IRCNotifier) MaybeWaitForNickserv() {
	if n.NickPassword == "" {
		logging.Debug("Skip NickServ wait, no password configured")
		return
	}

	// Very lazy/optimistic, but this is good enough for my irssi config,
	// so it should work here as well.
	logging.Info("Waiting for NickServ to notice us and issue an identify request")
	time.Sleep(n.NickservDelayWait)
}

func (n *IRCNotifier) ChannelJoined(ctx context.Context, channel string) bool {

	isJoined, waitJoined := n.channelReconciler.JoinChannel(channel)
	if isJoined {
		return true
	}

	select {
	case <-waitJoined:
		return true
	case <-n.timeTeller.After(ircJoinWaitSecs * time.Second):
		logging.Warn("Channel %s not joined after %d seconds, giving bad news to caller", channel, ircJoinWaitSecs)
		return false
	case <-ctx.Done():
		logging.Info("Context canceled while waiting for join on channel %s", channel)
		return false
	}
}

func (n *IRCNotifier) SendAlertMsg(ctx context.Context, alertMsg *AlertMsg) {
	if !n.sessionUp {
		logging.Error("Cannot send alert to %s : IRC not connected", alertMsg.Channel)
		ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_connected").Inc()
		return
	}
	if !n.ChannelJoined(ctx, alertMsg.Channel) {
		logging.Error("Cannot send alert to %s : cannot join channel", alertMsg.Channel)
		ircSendMsgErrors.WithLabelValues(alertMsg.Channel, "not_joined").Inc()
		return
	}

	if n.UsePrivmsg {
		n.Client.Privmsg(alertMsg.Channel, alertMsg.Alert)
	} else {
		n.Client.Notice(alertMsg.Channel, alertMsg.Alert)
	}
	ircSentMsgs.WithLabelValues(alertMsg.Channel).Inc()
}

func (n *IRCNotifier) ShutdownPhase() {
	if n.sessionUp {
		logging.Info("IRC client connected, quitting")
		n.Client.Quit("see ya")

		logging.Info("Wait for IRC disconnect to complete")
		select {
		case <-n.sessionDownSignal:
		case <-n.timeTeller.After(n.Client.Config().Timeout):
			logging.Warn("Timeout while waiting for IRC disconnect to complete, stopping anyway")
		}
		n.sessionWg.Done()
	}
	logging.Info("IRC shutdown complete")
}

func (n *IRCNotifier) ConnectedPhase(ctx context.Context) {
	select {
	case alertMsg := <-n.AlertMsgs:
		n.SendAlertMsg(ctx, &alertMsg)
	case <-n.sessionDownSignal:
		n.sessionUp = false
		n.sessionWg.Done()
		n.channelReconciler.Stop()
		n.Client.Quit("see ya")
		ircConnectedGauge.Set(0)
	case <-ctx.Done():
		logging.Info("IRC routine asked to terminate")
	}
}

func (n *IRCNotifier) SetupPhase(ctx context.Context) {
	if !n.Client.Connected() {
		logging.Info("Connecting to IRC %s", n.Client.Config().Server)
		if ok := n.BackoffCounter.DelayContext(ctx); !ok {
			return
		}
		if err := n.Client.ConnectContext(WithWaitGroup(ctx, &n.sessionWg)); err != nil {
			logging.Error("Could not connect to IRC: %s", err)
			return
		}
		logging.Info("Connected to IRC server, waiting to establish session")
	}
	select {
	case <-n.sessionUpSignal:
		n.sessionUp = true
		n.sessionWg.Add(1)
		n.MaybeGhostNick()
		n.MaybeWaitForNickserv()
		n.channelReconciler.Start(ctx)
		ircConnectedGauge.Set(1)
	case <-n.sessionDownSignal:
		logging.Warn("Receiving a session down before the session is up, this is odd")
	case <-ctx.Done():
		logging.Info("IRC routine asked to terminate")
	}
}

func (n *IRCNotifier) Run(ctx context.Context, stopWg *sync.WaitGroup) {
	defer stopWg.Done()

	for ctx.Err() != context.Canceled {
		if !n.sessionUp {
			n.SetupPhase(ctx)
		} else {
			n.ConnectedPhase(ctx)
		}
	}
	n.ShutdownPhase()
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"net/textproto"
	"os"
	"regexp"
	"runtime"
	"sync"
	"time"
)

//output err
func errOut(err error, quit chan bool) {
	fmt.Println("ERROR: ", err.Error())
	var trace []byte
	runtime.Stack(trace, false)
	fmt.Print(trace)
	if err.Error() == "EOF" {
		fmt.Println("EXITING")
		for i := 0; i < 2; i++ {
			quit <- true
		}
		fmt.Println("QUITS SENT")
	}
}

//take input from srvChan and send to server
func writeToServer(socket *textproto.Conn, srvChan chan string, wg *sync.WaitGroup, quit chan bool) {
	defer wg.Done()
	defer fmt.Println("WTS")
	w := socket.Writer
	err := w.PrintfLine(<-srvChan)
	for ; err == nil; {
		select {
		case <- quit:
			return
		case str := <- srvChan:
			err = w.PrintfLine(str)
		}
	}
	if err != nil {
		errOut(err, quit)
		socket.Close()
	}
}

//take input from connection and write out to console, also handle PING/PONG
func writeToConsole(socket *textproto.Conn, srvChan chan string, wg *sync.WaitGroup, quit chan bool) {
	defer wg.Done()
	defer fmt.Println("WTC")
	r := socket.Reader
	pingRegex := regexp.MustCompile("^PING (.*)")
	line, line_err := r.ReadLine()
	for ; line_err == nil; line, line_err = r.ReadLine() {
		select {
		case <- quit:
			return
		default:
		}
		fmt.Println(line)
		if match := pingRegex.FindStringSubmatch(line); match != nil {
			srvChan <- ("PONG " + match[1])
			fmt.Println("PONG", match[1])
		}
	}
	if line_err != nil {
		errOut(line_err, quit)
		socket.Close()
	}
}

//read input from console and send to srvChan
func readFromConsole(srvChan chan string, wg *sync.WaitGroup, quit chan bool) {
	defer wg.Done()
	defer fmt.Println("RFC")
	in := bufio.NewScanner(os.Stdin)
	for in.Scan() {
		str := in.Text()
		srvChan <- str
		if str == "QUIT" {
			return
		}
	}
	if err := in.Err(); err != nil {
		errOut(err, quit)
	}
}

func main() {
	//funcMap := initMap()
	srvChan := make(chan string)
	var wg sync.WaitGroup
	quit := make(chan bool, 2)
	//initiate connection
	go readFromConsole(srvChan, &wg, quit) //doesnt get restarted on connection EOF
	for {
		socket, err := textproto.Dial("tcp", "irc.tamu.edu:6667")
		if err != nil {
			errOut(err, quit)
			return
		}
		//make writer/reader to/from server
		//send initial IRC messages, NICK and USER
		err = socket.Writer.PrintfLine("NICK yaircb")
		if err != nil {
			errOut(err, quit)
		}
		wg.Add(1)
		//launch routine to write server output to console
		go writeToConsole(socket, srvChan, &wg, quit)
		err = socket.Writer.PrintfLine("USER yaircb * * yaircb")
		if err != nil {
			errOut(err, quit)
		}
		//join first channel
		err = socket.Writer.PrintfLine("JOIN #ttestt")
		if err != nil {
			errOut(err, quit)
		}
		wg.Add(2)
		//launch routine to send to server and get input from console
		go writeToServer(socket, srvChan, &wg, quit)
		wg.Wait()
		time.Sleep(60 * time.Second)
	}
}
<commit_msg>now exits on QUIT being typed in<commit_after>package main

import (
	"bufio"
	"fmt"
	"net/textproto"
	"os"
	"regexp"
	"runtime"
	"strings"
	"sync"
)

//output err
func errOut(err error, quit chan bool) {
	fmt.Println("ERROR: ", err.Error())
	var trace []byte
	runtime.Stack(trace, false)
	fmt.Print(trace)
	if err.Error() == "EOF" {
		fmt.Println("EXITING")
		for i := 0; i < 2; i++ {
			quit <- true
		}
		fmt.Println("QUITS SENT")
	}
}

//take input from srvChan and send to server
func writeToServer(socket *textproto.Conn, srvChan chan string, wg *sync.WaitGroup, quit chan bool) {
	defer wg.Done()
	defer fmt.Println("WTS")
	w := socket.Writer
	err := w.PrintfLine(<-srvChan)
	for ; err == nil; {
		select {
		case <- quit:
			return
		case str := <- srvChan:
			err = w.PrintfLine(str)
		}
	}
	if err != nil {
		errOut(err, quit)
		socket.Close()
	}
}

//take input from connection and write out to console, also handle PING/PONG
func writeToConsole(socket *textproto.Conn, srvChan chan string, wg *sync.WaitGroup, quit chan bool) {
	defer wg.Done()
	defer fmt.Println("WTC")
	r := socket.Reader
	pingRegex := regexp.MustCompile("^PING (.*)")
	line, line_err := r.ReadLine()
	for ; line_err == nil; line, line_err = r.ReadLine() {
		select {
		case <- quit:
			return
		default:
		}
		fmt.Println(line)
		if match := pingRegex.FindStringSubmatch(line); match != nil {
			srvChan <- ("PONG " + match[1])
			fmt.Println("PONG", match[1])
		}
	}
	if line_err != nil {
		errOut(line_err, quit)
		socket.Close()
	}
}

//read input from console and send to srvChan
func readFromConsole(srvChan chan string, wg *sync.WaitGroup, quit chan bool, error chan bool) {
	defer wg.Done()
	defer fmt.Println("RFC")
	in := bufio.NewScanner(os.Stdin)
	for in.Scan() {
		str := in.Text()
		srvChan <- str
		if strings.TrimSpace(str) == "QUIT" {
			error <- true
			return
		}
	}
	if err := in.Err(); err != nil {
		errOut(err, quit)
	}
}

func main() {
	//funcMap := initMap()
	srvChan := make(chan string)
	var wgSrv, wg sync.WaitGroup
	quit := make(chan bool, 2)
	error := make(chan bool, 1)
	//initiate connection
	wg.Add(1)
	go readFromConsole(srvChan, &wg, quit, error) //doesnt get restarted on connection EOF
connectionLoop:
	for {
		select {
		case <-error: //if readFromConsole got a "QUIT", exit program
			break connectionLoop
		default: //otherwise restart connections
			socket, err := textproto.Dial("tcp", "irc.tamu.edu:6667")
			if err != nil {
				errOut(err, quit)
				return
			}
			//make writer/reader to/from server
			//send initial IRC messages, NICK and USER
			err = socket.Writer.PrintfLine("NICK yaircb")
			if err != nil {
				errOut(err, quit)
			}
			wgSrv.Add(1)
			//launch routine to write server output to console
			go writeToConsole(socket, srvChan, &wgSrv, quit)
			err = socket.Writer.PrintfLine("USER yaircb * * yaircb")
			if err != nil {
				errOut(err, quit)
			}
			//join first channel
			err = socket.Writer.PrintfLine("JOIN #ttestt")
			if err != nil {
				errOut(err, quit)
			}
			wgSrv.Add(1)
			//launch routine to send to server and get input from console
			go writeToServer(socket, srvChan, &wgSrv, quit)
			wgSrv.Wait()
		}
	}
	wg.Wait()
}
<|endoftext|>