<commit_before>// This is Free Software covered by the terms of the MIT license.
// See LICENSE file for details.
// Copyright 2017 by Intevation GmbH
package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
	"strings"
)

var htmlEscape = strings.NewReplacer(
	"<", "<",
	">", ">",
	"&", "&").Replace

type xhtml struct {
	out bytes.Buffer
}

func (x *xhtml) tag(name string) func(*node) error {
	tag := "<" + name + "/>"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) open(name string) func(*node) error {
	tag := "<" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) close(name string) func(*node) error {
	tag := "</" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) element(name string) *visitor {
	return &visitor{x.open(name), x.close(name)}
}

func (x *xhtml) heading(level int) *visitor {
	tag := fmt.Sprintf("h%d", level)
	return &visitor{x.open(tag), x.close(tag)}
}

func (x *xhtml) text(n *node) error {
	x.out.WriteString(htmlEscape(n.value.(string)))
	return nil
}

func (x *xhtml) noWikiInline(n *node) error {
	x.out.WriteString("<tt>")
	x.out.WriteString(htmlEscape(n.value.(string)))
	x.out.WriteString("</tt>")
	return nil
}

func (x *xhtml) noWiki(n *node) error {
	x.out.WriteString("
")
	x.noWikiInline(n)
	x.out.WriteString("
\n")
	return nil
}

func (x *xhtml) link(n *node) error {
	href := n.value.(string)
	enc := xml.NewEncoder(&x.out)
	enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "a"},
		Attr: []xml.Attr{{xml.Name{Local: "href"}, href}},
	})
	enc.Flush()
	return nil
}

func exportXHTML(doc *document, out io.Writer) error {
	// TODO: Implement me!

	var x xhtml

	x.out.WriteString("\n\n")

	err := doc.traverse(map[nodeType]*visitor{
		orderedListNode:     x.element("ol"),
		unorderedListNode:   x.element("ul"),
		listItemNode:        x.element("li"),
		textNode:            &visitor{enter: x.text},
		boldNode:            x.element("strong"),
		italicsNode:         x.element("i"),
		underlinedNode:      x.element("em"),
		strikeNode:          x.element("del"),
		superscriptNode:     x.element("sup"),
		subscriptNode:       x.element("sub"),
		tableNode:           x.element("table"),
		tableRowNode:        x.element("tr"),
		tableCellNode:       x.element("td"),
		tableHeaderRowNode:  x.element("th"),
		tableHeaderCellNode: x.element("td"),
		heading1Node:        x.heading(1),
		heading2Node:        x.heading(2),
		heading3Node:        x.heading(3),
		heading4Node:        x.heading(4),
		heading5Node:        x.heading(5),
		heading6Node:        x.heading(6),
		paragraphNode:       x.element("p"),
		lineBreakNode:       &visitor{enter: x.tag("br")},
		escapeNode:          &visitor{enter: x.text},
		noWikiNode:          &visitor{enter: x.noWiki},
		noWikiInlineNode:    &visitor{enter: x.noWikiInline},
		// placeholderNode not supported, yet.
		// TODO: Implement image node.
		linkNode:           &visitor{enter: x.link, leave: x.close("a")},
		horizontalLineNode: &visitor{enter: x.tag("hr")},
	})
	if err != nil {
		x.out.WriteString("\n\n\n")
		_, err = x.out.WriteTo(out)
	}
	return err
}
<commit_msg>Replaced home-brew escaping of text in XHTML exporter with encoding from stdlib.<commit_after>// This is Free Software covered by the terms of the MIT license.
// See LICENSE file for details.
// Copyright 2017 by Intevation GmbH
package main

import (
	"bytes"
	"encoding/xml"
	"fmt"
	"io"
)

type xhtml struct {
	out bytes.Buffer
}

func (x *xhtml) tag(name string) func(*node) error {
	tag := "<" + name + "/>"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) open(name string) func(*node) error {
	tag := "<" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) close(name string) func(*node) error {
	tag := "</" + name + ">"
	return func(*node) error {
		x.out.WriteString(tag)
		return nil
	}
}

func (x *xhtml) element(name string) *visitor {
	return &visitor{x.open(name), x.close(name)}
}

func (x *xhtml) heading(level int) *visitor {
	tag := fmt.Sprintf("h%d", level)
	return &visitor{x.open(tag), x.close(tag)}
}

func (x *xhtml) text(n *node) error {
	enc := xml.NewEncoder(&x.out)
	txt := n.value.(string)
	enc.EncodeToken(xml.CharData(txt))
	enc.Flush()
	return nil
}

func (x *xhtml) noWikiInline(n *node) error {
	x.out.WriteString("<tt>")
	x.text(n)
	x.out.WriteString("</tt>")
	return nil
}

func (x *xhtml) noWiki(n *node) error {
	x.out.WriteString("
")
	x.noWikiInline(n)
	x.out.WriteString("
\n")
	return nil
}

func (x *xhtml) link(n *node) error {
	href := n.value.(string)
	enc := xml.NewEncoder(&x.out)
	enc.EncodeToken(xml.StartElement{
		Name: xml.Name{Local: "a"},
		Attr: []xml.Attr{{xml.Name{Local: "href"}, href}},
	})
	enc.Flush()
	return nil
}

func exportXHTML(doc *document, out io.Writer) error {
	// TODO: Implement me!

	var x xhtml

	x.out.WriteString("\n\n")

	err := doc.traverse(map[nodeType]*visitor{
		orderedListNode:     x.element("ol"),
		unorderedListNode:   x.element("ul"),
		listItemNode:        x.element("li"),
		textNode:            &visitor{enter: x.text},
		boldNode:            x.element("strong"),
		italicsNode:         x.element("i"),
		underlinedNode:      x.element("em"),
		strikeNode:          x.element("del"),
		superscriptNode:     x.element("sup"),
		subscriptNode:       x.element("sub"),
		tableNode:           x.element("table"),
		tableRowNode:        x.element("tr"),
		tableCellNode:       x.element("td"),
		tableHeaderRowNode:  x.element("th"),
		tableHeaderCellNode: x.element("td"),
		heading1Node:        x.heading(1),
		heading2Node:        x.heading(2),
		heading3Node:        x.heading(3),
		heading4Node:        x.heading(4),
		heading5Node:        x.heading(5),
		heading6Node:        x.heading(6),
		paragraphNode:       x.element("p"),
		lineBreakNode:       &visitor{enter: x.tag("br")},
		escapeNode:          &visitor{enter: x.text},
		noWikiNode:          &visitor{enter: x.noWiki},
		noWikiInlineNode:    &visitor{enter: x.noWikiInline},
		// placeholderNode not supported, yet.
		// TODO: Implement image node.
		linkNode:           &visitor{enter: x.link, leave: x.close("a")},
		horizontalLineNode: &visitor{enter: x.tag("hr")},
	})
	if err != nil {
		x.out.WriteString("\n\n\n")
		_, err = x.out.WriteTo(out)
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"log"
	"os/exec"

	"github.com/BurntSushi/xgb/xinerama"
	xp "github.com/BurntSushi/xgb/xproto"
)

var (
	atomNetActiveWindow xp.Atom
	atomNetWMName       xp.Atom
	atomWindow          xp.Atom
	atomWMClass         xp.Atom
	atomWMDeleteWindow  xp.Atom
	atomWMProtocols     xp.Atom
	atomWMTakeFocus     xp.Atom
	atomWMTransientFor  xp.Atom

	desktopXWin   xp.Window
	desktopXGC    xp.Gcontext
	desktopWidth  uint16
	desktopHeight uint16

	keysyms [256][2]xp.Keysym
)

func becomeTheWM() {
	if err := xp.ChangeWindowAttributesChecked(xConn, rootXWin, xp.CwEventMask, []uint32{
		xp.EventMaskButtonPress |
			xp.EventMaskButtonRelease |
			xp.EventMaskPointerMotion |
			xp.EventMaskStructureNotify |
			xp.EventMaskSubstructureRedirect,
	}).Check(); err != nil {
		if _, ok := err.(xp.AccessError); ok {
			log.Fatal("could not become the window manager. Is another window manager running?")
		}
		log.Fatal(err)
	}
}

func initAtoms() {
	atomNetActiveWindow = internAtom("_NET_ACTIVE_WINDOW")
	atomNetWMName = internAtom("_NET_WM_NAME")
	atomWindow = internAtom("WINDOW")
	atomWMClass = internAtom("WM_CLASS")
	atomWMDeleteWindow = internAtom("WM_DELETE_WINDOW")
	atomWMProtocols = internAtom("WM_PROTOCOLS")
	atomWMTakeFocus = internAtom("WM_TAKE_FOCUS")
	atomWMTransientFor = internAtom("WM_TRANSIENT_FOR")
}

func internAtom(name string) xp.Atom {
	r, err := xp.InternAtom(xConn, false, uint16(len(name)), name).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if r == nil {
		return 0
	}
	return r.Atom
}

func initDesktop(xScreen *xp.ScreenInfo) {
	xCursorFont, err := xp.NewFontId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	xCursor, err := xp.NewCursorId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	err = xp.OpenFontChecked(xConn, xCursorFont, uint16(len("cursor")), "cursor").Check()
	if err != nil {
		log.Fatal(err)
	}
	const xcLeftPtr = 68 // XC_left_ptr from cursorfont.h.
	err = xp.CreateGlyphCursorChecked(
		xConn, xCursor, xCursorFont, xCursorFont, xcLeftPtr, xcLeftPtr+1,
		0xffff, 0xffff, 0xffff, 0, 0, 0).Check()
	if err != nil {
		log.Fatal(err)
	}
	err = xp.CloseFontChecked(xConn, xCursorFont).Check()
	if err != nil {
		log.Fatal(err)
	}

	xTextFont, err := xp.NewFontId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	err = xp.OpenFontChecked(xConn, xTextFont, uint16(len(fontName)), fontName).Check()
	if err != nil {
		log.Fatal(err)
	}
	defer xp.CloseFont(xConn, xTextFont)

	desktopXWin, err = xp.NewWindowId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	desktopXGC, err = xp.NewGcontextId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	desktopWidth = xScreen.WidthInPixels
	desktopHeight = xScreen.HeightInPixels

	if err := xp.CreateWindowChecked(
		xConn, xScreen.RootDepth, desktopXWin, xScreen.Root,
		0, 0, desktopWidth, desktopHeight, 0,
		xp.WindowClassInputOutput,
		xScreen.RootVisual,
		xp.CwOverrideRedirect|xp.CwEventMask,
		[]uint32{
			1,
			xp.EventMaskExposure,
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if len(xSettings) != 0 {
		initXSettings()
	}

	if err := xp.ConfigureWindowChecked(
		xConn,
		desktopXWin,
		xp.ConfigWindowStackMode,
		[]uint32{
			xp.StackModeBelow,
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.ChangeWindowAttributesChecked(
		xConn,
		desktopXWin,
		xp.CwBackPixel|xp.CwCursor,
		[]uint32{
			xScreen.BlackPixel,
			uint32(xCursor),
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.CreateGCChecked(
		xConn,
		desktopXGC,
		xp.Drawable(xScreen.Root),
		xp.GcFont,
		[]uint32{
			uint32(xTextFont),
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.MapWindowChecked(xConn, desktopXWin).Check(); err != nil {
		log.Fatal(err)
	}
}

func initKeyboardMapping() {
	const (
		keyLo = 8
		keyHi = 255
	)
	km, err := xp.GetKeyboardMapping(xConn, keyLo, keyHi-keyLo+1).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if km == nil {
		log.Fatal("couldn't get keyboard mapping")
	}
	n := int(km.KeysymsPerKeycode)
	if n < 2 {
		log.Fatalf("too few keysyms per keycode: %d", n)
	}
	for i := keyLo; i <= keyHi; i++ {
		keysyms[i][0] = km.Keysyms[(i-keyLo)*n+0]
		keysyms[i][1] = km.Keysyms[(i-keyLo)*n+1]
	}

	toGrabs := []xp.Keysym{wmKeysym}
	if doAudioActions {
		toGrabs = append(toGrabs, xkAudioLowerVolume, xkAudioMute, xkAudioRaiseVolume)
	}
	for _, toGrab := range toGrabs {
		keycode := xp.Keycode(0)
		for i := keyLo; i <= keyHi; i++ {
			if keysyms[i][0] == toGrab || keysyms[i][1] == toGrab {
				keycode = xp.Keycode(i)
				break
			}
		}
		if keycode == 0 {
			if toGrab != wmKeysym {
				continue
			}
			log.Fatalf("could not find the window manager key %s", keysymString(toGrab))
		}
		if err := xp.GrabKeyChecked(xConn, false, rootXWin, xp.ModMaskAny, keycode,
			xp.GrabModeAsync, xp.GrabModeAsync).Check(); err != nil {
			log.Fatal(err)
		}
	}

	// Disable Caps Lock if it is the wmKeysym.
	if wmKeysym == xkCapsLock {
		// On Ubuntu 12.04, disabling Caps Lock involved the equivalent of
		// `xmodmap -e "clear lock"`. On Ubuntu 14.04, XKB has replaced xmodmap,
		// possibly because this facilitates per-window keyboard layouts, so the
		// equivalent of `xmodmap -e "clear lock"` doesn't work. As of October
		// 2014, github.com/BurntSushi/xgb doesn't support XKB, so we exec the
		// setxkbmap program instead of speaking the X11 protocol directly to
		// disable Caps Lock.
		if err := exec.Command("setxkbmap", "-option", "caps:none").Run(); err != nil {
			log.Fatalf("setxkbmap failed: %v", err)
		}
	}
}

func findKeycode(keysym xp.Keysym) (keycode xp.Keycode, shift bool) {
	for i, k := range keysyms {
		if k[0] == keysym {
			return xp.Keycode(i), false
		}
		if k[1] == keysym {
			return xp.Keycode(i), true
		}
	}
	return 0, false
}

func initScreens() {
	oldScreens := screens

	xine, err := xinerama.QueryScreens(xConn).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if len(xine.ScreenInfo) > 0 {
		screens = make([]*screen, len(xine.ScreenInfo))
		for i, si := range xine.ScreenInfo {
			screens[i] = &screen{
				rect: xp.Rectangle{
					X:      si.XOrg,
					Y:      si.YOrg,
					Width:  si.Width - 1,
					Height: si.Height - 1,
				},
			}
		}
	} else {
		screens = make([]*screen, 1)
		screens[0] = &screen{
			rect: xp.Rectangle{
				X:      0,
				Y:      0,
				Width:  desktopWidth - 1,
				Height: desktopHeight - 1,
			},
		}
	}

	for i, s := range screens {
		k := (*workspace)(nil)
		if i < len(oldScreens) {
			k = oldScreens[i].workspace
			oldScreens[i].workspace = nil
		} else {
			k = newWorkspace(s.rect, dummyWorkspace.link[prev])
		}
		s.workspace, k.screen = k, s
	}

	if len(oldScreens) > len(screens) {
		for _, s := range oldScreens[len(screens):] {
			s.workspace.screen = nil
			s.workspace = nil
		}
	}
}

func initXSettings() {
	a0 := internAtom("_XSETTINGS_S0")
	if err := xp.SetSelectionOwnerChecked(xConn, desktopXWin, a0,
		xp.TimeCurrentTime).Check(); err != nil {
		log.Printf("could not set xsettings: %v", err)
		return
	}
	a1 := internAtom("_XSETTINGS_SETTINGS")
	encoded := makeEncodedXSettings()
	if err := xp.ChangePropertyChecked(xConn, xp.PropModeReplace, desktopXWin, a1, a1,
		8, uint32(len(encoded)), encoded).Check(); err != nil {
		log.Printf("could not set xsettings: %v", err)
		return
	}
}

func makeEncodedXSettings() []byte {
	b := new(bytes.Buffer)
	b.WriteString("\x00\x00\x00\x00") // Zero means little-endian.
	b.WriteString("\x00\x00\x00\x00") // Serial number.
	writeUint32(b, uint32(len(xSettings)))
	for _, s := range xSettings {
		switch s.value.(type) {
		case int:
			b.WriteString("\x00\x00")
		case string:
			b.WriteString("\x01\x00")
		default:
			log.Fatalf("unsupported XSettings type %T", s.value)
		}
		writeUint16(b, uint16(len(s.name)))
		b.WriteString(s.name)
		if x := len(s.name) % 4; x != 0 {
			b.WriteString("\x00\x00\x00\x00"[:4-x]) // Padding.
		}
		b.WriteString("\x00\x00\x00\x00") // Serial number.
		switch v := s.value.(type) {
		case int:
			writeUint32(b, uint32(v))
		case string:
			writeUint32(b, uint32(len(v)))
			b.WriteString(v)
			if x := len(v) % 4; x != 0 {
				b.WriteString("\x00\x00\x00\x00"[:4-x]) // Padding.
			}
		}
	}
	return b.Bytes()
}

func writeUint16(b *bytes.Buffer, u uint16) {
	b.WriteByte(byte(u >> 0))
	b.WriteByte(byte(u >> 8))
}

func writeUint32(b *bytes.Buffer, u uint32) {
	b.WriteByte(byte(u >> 0))
	b.WriteByte(byte(u >> 8))
	b.WriteByte(byte(u >> 16))
	b.WriteByte(byte(u >> 24))
}

func u32(b []byte) uint32 {
	return uint32(b[0])<<0 | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
<commit_msg>Allow float64 xSettings value<commit_after>package main

import (
	"bytes"
	"log"
	"os/exec"

	"github.com/BurntSushi/xgb/xinerama"
	xp "github.com/BurntSushi/xgb/xproto"
)

var (
	atomNetActiveWindow xp.Atom
	atomNetWMName       xp.Atom
	atomWindow          xp.Atom
	atomWMClass         xp.Atom
	atomWMDeleteWindow  xp.Atom
	atomWMProtocols     xp.Atom
	atomWMTakeFocus     xp.Atom
	atomWMTransientFor  xp.Atom

	desktopXWin   xp.Window
	desktopXGC    xp.Gcontext
	desktopWidth  uint16
	desktopHeight uint16

	keysyms [256][2]xp.Keysym
)

func becomeTheWM() {
	if err := xp.ChangeWindowAttributesChecked(xConn, rootXWin, xp.CwEventMask, []uint32{
		xp.EventMaskButtonPress |
			xp.EventMaskButtonRelease |
			xp.EventMaskPointerMotion |
			xp.EventMaskStructureNotify |
			xp.EventMaskSubstructureRedirect,
	}).Check(); err != nil {
		if _, ok := err.(xp.AccessError); ok {
			log.Fatal("could not become the window manager. Is another window manager running?")
		}
		log.Fatal(err)
	}
}

func initAtoms() {
	atomNetActiveWindow = internAtom("_NET_ACTIVE_WINDOW")
	atomNetWMName = internAtom("_NET_WM_NAME")
	atomWindow = internAtom("WINDOW")
	atomWMClass = internAtom("WM_CLASS")
	atomWMDeleteWindow = internAtom("WM_DELETE_WINDOW")
	atomWMProtocols = internAtom("WM_PROTOCOLS")
	atomWMTakeFocus = internAtom("WM_TAKE_FOCUS")
	atomWMTransientFor = internAtom("WM_TRANSIENT_FOR")
}

func internAtom(name string) xp.Atom {
	r, err := xp.InternAtom(xConn, false, uint16(len(name)), name).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if r == nil {
		return 0
	}
	return r.Atom
}

func initDesktop(xScreen *xp.ScreenInfo) {
	xCursorFont, err := xp.NewFontId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	xCursor, err := xp.NewCursorId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	err = xp.OpenFontChecked(xConn, xCursorFont, uint16(len("cursor")), "cursor").Check()
	if err != nil {
		log.Fatal(err)
	}
	const xcLeftPtr = 68 // XC_left_ptr from cursorfont.h.
	err = xp.CreateGlyphCursorChecked(
		xConn, xCursor, xCursorFont, xCursorFont, xcLeftPtr, xcLeftPtr+1,
		0xffff, 0xffff, 0xffff, 0, 0, 0).Check()
	if err != nil {
		log.Fatal(err)
	}
	err = xp.CloseFontChecked(xConn, xCursorFont).Check()
	if err != nil {
		log.Fatal(err)
	}

	xTextFont, err := xp.NewFontId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	err = xp.OpenFontChecked(xConn, xTextFont, uint16(len(fontName)), fontName).Check()
	if err != nil {
		log.Fatal(err)
	}
	defer xp.CloseFont(xConn, xTextFont)

	desktopXWin, err = xp.NewWindowId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	desktopXGC, err = xp.NewGcontextId(xConn)
	if err != nil {
		log.Fatal(err)
	}
	desktopWidth = xScreen.WidthInPixels
	desktopHeight = xScreen.HeightInPixels

	if err := xp.CreateWindowChecked(
		xConn, xScreen.RootDepth, desktopXWin, xScreen.Root,
		0, 0, desktopWidth, desktopHeight, 0,
		xp.WindowClassInputOutput,
		xScreen.RootVisual,
		xp.CwOverrideRedirect|xp.CwEventMask,
		[]uint32{
			1,
			xp.EventMaskExposure,
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if len(xSettings) != 0 {
		initXSettings()
	}

	if err := xp.ConfigureWindowChecked(
		xConn,
		desktopXWin,
		xp.ConfigWindowStackMode,
		[]uint32{
			xp.StackModeBelow,
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.ChangeWindowAttributesChecked(
		xConn,
		desktopXWin,
		xp.CwBackPixel|xp.CwCursor,
		[]uint32{
			xScreen.BlackPixel,
			uint32(xCursor),
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.CreateGCChecked(
		xConn,
		desktopXGC,
		xp.Drawable(xScreen.Root),
		xp.GcFont,
		[]uint32{
			uint32(xTextFont),
		},
	).Check(); err != nil {
		log.Fatal(err)
	}

	if err := xp.MapWindowChecked(xConn, desktopXWin).Check(); err != nil {
		log.Fatal(err)
	}
}

func initKeyboardMapping() {
	const (
		keyLo = 8
		keyHi = 255
	)
	km, err := xp.GetKeyboardMapping(xConn, keyLo, keyHi-keyLo+1).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if km == nil {
		log.Fatal("couldn't get keyboard mapping")
	}
	n := int(km.KeysymsPerKeycode)
	if n < 2 {
		log.Fatalf("too few keysyms per keycode: %d", n)
	}
	for i := keyLo; i <= keyHi; i++ {
		keysyms[i][0] = km.Keysyms[(i-keyLo)*n+0]
		keysyms[i][1] = km.Keysyms[(i-keyLo)*n+1]
	}

	toGrabs := []xp.Keysym{wmKeysym}
	if doAudioActions {
		toGrabs = append(toGrabs, xkAudioLowerVolume, xkAudioMute, xkAudioRaiseVolume)
	}
	for _, toGrab := range toGrabs {
		keycode := xp.Keycode(0)
		for i := keyLo; i <= keyHi; i++ {
			if keysyms[i][0] == toGrab || keysyms[i][1] == toGrab {
				keycode = xp.Keycode(i)
				break
			}
		}
		if keycode == 0 {
			if toGrab != wmKeysym {
				continue
			}
			log.Fatalf("could not find the window manager key %s", keysymString(toGrab))
		}
		if err := xp.GrabKeyChecked(xConn, false, rootXWin, xp.ModMaskAny, keycode,
			xp.GrabModeAsync, xp.GrabModeAsync).Check(); err != nil {
			log.Fatal(err)
		}
	}

	// Disable Caps Lock if it is the wmKeysym.
	if wmKeysym == xkCapsLock {
		// On Ubuntu 12.04, disabling Caps Lock involved the equivalent of
		// `xmodmap -e "clear lock"`. On Ubuntu 14.04, XKB has replaced xmodmap,
		// possibly because this facilitates per-window keyboard layouts, so the
		// equivalent of `xmodmap -e "clear lock"` doesn't work. As of October
		// 2014, github.com/BurntSushi/xgb doesn't support XKB, so we exec the
		// setxkbmap program instead of speaking the X11 protocol directly to
		// disable Caps Lock.
		if err := exec.Command("setxkbmap", "-option", "caps:none").Run(); err != nil {
			log.Fatalf("setxkbmap failed: %v", err)
		}
	}
}

func findKeycode(keysym xp.Keysym) (keycode xp.Keycode, shift bool) {
	for i, k := range keysyms {
		if k[0] == keysym {
			return xp.Keycode(i), false
		}
		if k[1] == keysym {
			return xp.Keycode(i), true
		}
	}
	return 0, false
}

func initScreens() {
	oldScreens := screens

	xine, err := xinerama.QueryScreens(xConn).Reply()
	if err != nil {
		log.Fatal(err)
	}
	if len(xine.ScreenInfo) > 0 {
		screens = make([]*screen, len(xine.ScreenInfo))
		for i, si := range xine.ScreenInfo {
			screens[i] = &screen{
				rect: xp.Rectangle{
					X:      si.XOrg,
					Y:      si.YOrg,
					Width:  si.Width - 1,
					Height: si.Height - 1,
				},
			}
		}
	} else {
		screens = make([]*screen, 1)
		screens[0] = &screen{
			rect: xp.Rectangle{
				X:      0,
				Y:      0,
				Width:  desktopWidth - 1,
				Height: desktopHeight - 1,
			},
		}
	}

	for i, s := range screens {
		k := (*workspace)(nil)
		if i < len(oldScreens) {
			k = oldScreens[i].workspace
			oldScreens[i].workspace = nil
		} else {
			k = newWorkspace(s.rect, dummyWorkspace.link[prev])
		}
		s.workspace, k.screen = k, s
	}

	if len(oldScreens) > len(screens) {
		for _, s := range oldScreens[len(screens):] {
			s.workspace.screen = nil
			s.workspace = nil
		}
	}
}

func initXSettings() {
	a0 := internAtom("_XSETTINGS_S0")
	if err := xp.SetSelectionOwnerChecked(xConn, desktopXWin, a0,
		xp.TimeCurrentTime).Check(); err != nil {
		log.Printf("could not set xsettings: %v", err)
		return
	}
	a1 := internAtom("_XSETTINGS_SETTINGS")
	encoded := makeEncodedXSettings()
	if err := xp.ChangePropertyChecked(xConn, xp.PropModeReplace, desktopXWin, a1, a1,
		8, uint32(len(encoded)), encoded).Check(); err != nil {
		log.Printf("could not set xsettings: %v", err)
		return
	}
}

func makeEncodedXSettings() []byte {
	b := new(bytes.Buffer)
	b.WriteString("\x00\x00\x00\x00") // Zero means little-endian.
	b.WriteString("\x00\x00\x00\x00") // Serial number.
	writeUint32(b, uint32(len(xSettings)))
	for _, s := range xSettings {
		switch s.value.(type) {
		case int:
			b.WriteString("\x00\x00")
		case float64:
			b.WriteString("\x00\x00")
		case string:
			b.WriteString("\x01\x00")
		default:
			log.Fatalf("unsupported XSettings type %T", s.value)
		}
		writeUint16(b, uint16(len(s.name)))
		b.WriteString(s.name)
		if x := len(s.name) % 4; x != 0 {
			b.WriteString("\x00\x00\x00\x00"[:4-x]) // Padding.
		}
		b.WriteString("\x00\x00\x00\x00") // Serial number.
		switch v := s.value.(type) {
		case int:
			writeUint32(b, uint32(v))
		case float64:
			writeUint32(b, uint32(v))
		case string:
			writeUint32(b, uint32(len(v)))
			b.WriteString(v)
			if x := len(v) % 4; x != 0 {
				b.WriteString("\x00\x00\x00\x00"[:4-x]) // Padding.
			}
		}
	}
	return b.Bytes()
}

func writeUint16(b *bytes.Buffer, u uint16) {
	b.WriteByte(byte(u >> 0))
	b.WriteByte(byte(u >> 8))
}

func writeUint32(b *bytes.Buffer, u uint32) {
	b.WriteByte(byte(u >> 0))
	b.WriteByte(byte(u >> 8))
	b.WriteByte(byte(u >> 16))
	b.WriteByte(byte(u >> 24))
}

func u32(b []byte) uint32 {
	return uint32(b[0])<<0 | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
<|endoftext|>


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

// Package xurls extracts urls from plain text using regular expressions.
package xurls

import (
	"regexp"
	"strings"
	"unicode/utf8"
)

//go:generate go run ./generate/tldsgen
//go:generate go run ./generate/schemesgen
//go:generate go run ./generate/unicodegen

const (
	// pathCont is based on https://www.rfc-editor.org/rfc/rfc3987#section-2.2
	// but does not match separators anywhere or most puncutation in final position,
	// to avoid creating asymmetries like
	// `Did you know that **<a href="...">https://example.com/**</a> is reserved for documentation?`
	// from `Did you know that **https://example.com/** is reserved for documentation?`.
	subDelimChar        = `!$&'()*+,;=`
	midSubDelimChar     = `!$&'*+,;=`
	endSubDelimChar     = `$&+=`
	midIPathSegmentChar = `a-zA-Z0-9\-._~` + `%` + midSubDelimChar + `:@` + allowedUcsChar
	endIPathSegmentChar = `a-zA-Z0-9\-_~` + `%` + endSubDelimChar + allowedUcsCharMinusPunc
	iPrivateChar        = `\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}\x{100000}-\x{10FFFD}`
	midIChar            = `/?#\\` + midIPathSegmentChar + iPrivateChar
	endIChar            = `/#` + endIPathSegmentChar + iPrivateChar
	wellParen           = `\([` + midIChar + `]*(\([` + midIChar + `]*\)[` + midIChar + `]*)*\)`
	wellBrack           = `\[[` + midIChar + `]*(\[[` + midIChar + `]*\][` + midIChar + `]*)*\]`
	wellBrace           = `\{[` + midIChar + `]*(\{[` + midIChar + `]*\}[` + midIChar + `]*)*\}`
	wellAll             = wellParen + `|` + wellBrack + `|` + wellBrace
	pathCont            = `([` + midIChar + `]*(` + wellAll + `|[` + endIChar + `])+)+`

	letter   = `\p{L}`
	mark     = `\p{M}`
	number   = `\p{N}`
	iriChar  = letter + mark + number
	iri      = `[` + iriChar + `]([` + iriChar + `\-]*[` + iriChar + `])?`
	domain   = `(` + iri + `\.)+`
	octet    = `(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])`
	ipv4Addr = `\b` + octet + `\.` + octet + `\.` + octet + `\.` + octet + `\b`
	ipv6Addr = `([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:[0-9a-fA-F]{0,4}|:[0-9a-fA-F]{1,4})?|(:[0-9a-fA-F]{1,4}){0,2})|(:[0-9a-fA-F]{1,4}){0,3})|(:[0-9a-fA-F]{1,4}){0,4})|:(:[0-9a-fA-F]{1,4}){0,5})((:[0-9a-fA-F]{1,4}){2}|:(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])(\.(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])){3})|(([0-9a-fA-F]{1,4}:){1,6}|:):[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){7}:`
	ipAddr   = `(` + ipv4Addr + `|` + ipv6Addr + `)`
	port     = `(:[0-9]*)?`
)

// AnyScheme can be passed to StrictMatchingScheme to match any possibly valid
// scheme, and not just the known ones.
var AnyScheme = `([a-zA-Z][a-zA-Z.\-+]*://|` + anyOf(SchemesNoAuthority...) + `:)`

// SchemesNoAuthority is a sorted list of some well-known url schemes that are
// followed by ":" instead of "://". The list includes both officially
// registered and unofficial schemes.
var SchemesNoAuthority = []string{
	`bitcoin`, // Bitcoin
	`cid`,     // Content-ID
	`file`,    // Files
	`magnet`,  // Torrent magnets
	`mailto`,  // Mail
	`mid`,     // Message-ID
	`sms`,     // SMS
	`tel`,     // Telephone
	`xmpp`,    // XMPP
}

// SchemesUnofficial is a sorted list of some well-known url schemes which
// aren't officially registered just yet. They tend to correspond to software.
//
// Mostly collected from https://en.wikipedia.org/wiki/List_of_URI_schemes#Unofficial_but_common_URI_schemes.
var SchemesUnofficial = []string{
	`gemini`,        // gemini
	`jdbc`,          // Java database Connectivity
	`moz-extension`, // Firefox extension
	`postgres`,      // PostgreSQL (short form)
	`postgresql`,    // PostgreSQL
	`slack`,         // Slack
	`zoommtg`,       // Zoom (desktop)
	`zoomus`,        // Zoom (mobile)
}

var strictRe = regexp.MustCompile(strictExp())
var relaxedRe = regexp.MustCompile(relaxedExp())

func anyOf(strs ...string) string {
	var b strings.Builder
	b.WriteByte('(')
	for i, s := range strs {
		if i != 0 {
			b.WriteByte('|')
		}
		b.WriteString(regexp.QuoteMeta(s))
	}
	b.WriteByte(')')
	return b.String()
}

func strictExp() string {
	schemes := `((` + anyOf(Schemes...) + `|` + anyOf(SchemesUnofficial...) + `)://|` + anyOf(SchemesNoAuthority...) + `:)`
	return `(?i)` + schemes + `(?-i)` + pathCont
}

func relaxedExp() string {
	var asciiTLDs, unicodeTLDs []string
	for i, tld := range TLDs {
		if tld[0] >= utf8.RuneSelf {
			asciiTLDs = TLDs[:i:i]
			unicodeTLDs = TLDs[i:]
			break
		}
	}
	punycode := `xn--[a-z0-9-]+`

	// Use \b to make sure ASCII TLDs are immediately followed by a word break.
	// We can't do that with unicode TLDs, as they don't see following
	// whitespace as a word break.
	tlds := `(?i)(` + punycode + `|` + anyOf(append(asciiTLDs, PseudoTLDs...)...) + `\b|` + anyOf(unicodeTLDs...) + `)(?-i)`
	site := domain + tlds

	hostName := `(` + site + `|` + ipAddr + `)`
	webURL := hostName + port + `(/|/` + pathCont + `)?`
	email := `[a-zA-Z0-9._%\-+]+@` + site
	return strictExp() + `|` + webURL + `|` + email
}

// Strict produces a regexp that matches any URL with a scheme in either the
// Schemes or SchemesNoAuthority lists.
func Strict() *regexp.Regexp {
	strictRe.Longest()
	return strictRe.Copy()
}

// Relaxed produces a regexp that matches any URL matched by Strict, plus any
// URL with no scheme or email address.
func Relaxed() *regexp.Regexp {
	relaxedRe.Longest()
	return relaxedRe.Copy()
}

// StrictMatchingScheme produces a regexp similar to Strict, but requiring that
// the scheme match the given regular expression. See AnyScheme too.
func StrictMatchingScheme(exp string) (*regexp.Regexp, error) {
	strictMatching := `(?i)(` + exp + `)(?-i)` + pathCont
	re, err := regexp.Compile(strictMatching)
	if err != nil {
		return nil, err
	}
	re.Longest()
	return re, nil
}
<commit_msg>simplify regular expression patterns<commit_after>// Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

// Package xurls extracts urls from plain text using regular expressions.
package xurls

import (
	"regexp"
	"strings"
	"unicode/utf8"
)

//go:generate go run ./generate/tldsgen
//go:generate go run ./generate/schemesgen
//go:generate go run ./generate/unicodegen

const (
	// pathCont is based on https://www.rfc-editor.org/rfc/rfc3987#section-2.2
	// but does not match separators anywhere or most puncutation in final position,
	// to avoid creating asymmetries like
	// `Did you know that **<a href="...">https://example.com/**</a> is reserved for documentation?`
	// from `Did you know that **https://example.com/** is reserved for documentation?`.
	subDelimChar        = `!$&'()*+,;=`
	midSubDelimChar     = `!$&'*+,;=`
	endSubDelimChar     = `$&+=`
	midIPathSegmentChar = `a-zA-Z0-9\-._~` + `%` + midSubDelimChar + `:@` + allowedUcsChar
	endIPathSegmentChar = `a-zA-Z0-9\-_~` + `%` + endSubDelimChar + allowedUcsCharMinusPunc
	iPrivateChar        = `\x{E000}-\x{F8FF}\x{F0000}-\x{FFFFD}\x{100000}-\x{10FFFD}`
	midIChar            = `/?#\\` + midIPathSegmentChar + iPrivateChar
	endIChar            = `/#` + endIPathSegmentChar + iPrivateChar
	wellParen           = `\(([` + midIChar + `]|\([` + midIChar + `]*\))*\)`
	wellBrack           = `\[([` + midIChar + `]|\[[` + midIChar + `]*\])*\]`
	wellBrace           = `\{([` + midIChar + `]|\{[` + midIChar + `]*\})*\}`
	wellAll             = wellParen + `|` + wellBrack + `|` + wellBrace
	pathCont            = `([` + midIChar + `]*(` + wellAll + `|[` + endIChar + `]))+`

	letter   = `\p{L}`
	mark     = `\p{M}`
	number   = `\p{N}`
	iriChar  = letter + mark + number
	iri      = `[` + iriChar + `]([` + iriChar + `\-]*[` + iriChar + `])?`
	domain   = `(` + iri + `\.)+`
	octet    = `(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]|[0-9])`
	ipv4Addr = `\b` + octet + `\.` + octet + `\.` + octet + `\.` + octet + `\b`
	ipv6Addr = `([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:([0-9a-fA-F]{1,4}:[0-9a-fA-F]{0,4}|:[0-9a-fA-F]{1,4})?|(:[0-9a-fA-F]{1,4}){0,2})|(:[0-9a-fA-F]{1,4}){0,3})|(:[0-9a-fA-F]{1,4}){0,4})|:(:[0-9a-fA-F]{1,4}){0,5})((:[0-9a-fA-F]{1,4}){2}|:(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])(\.(25[0-5]|(2[0-4]|1[0-9]|[1-9])?[0-9])){3})|(([0-9a-fA-F]{1,4}:){1,6}|:):[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){7}:`
	ipAddr   = `(` + ipv4Addr + `|` + ipv6Addr + `)`
	port     = `(:[0-9]*)?`
)

// AnyScheme can be passed to StrictMatchingScheme to match any possibly valid
// scheme, and not just the known ones.
var AnyScheme = `([a-zA-Z][a-zA-Z.\-+]*://|` + anyOf(SchemesNoAuthority...) + `:)`

// SchemesNoAuthority is a sorted list of some well-known url schemes that are
// followed by ":" instead of "://". The list includes both officially
// registered and unofficial schemes.
var SchemesNoAuthority = []string{
	`bitcoin`, // Bitcoin
	`cid`,     // Content-ID
	`file`,    // Files
	`magnet`,  // Torrent magnets
	`mailto`,  // Mail
	`mid`,     // Message-ID
	`sms`,     // SMS
	`tel`,     // Telephone
	`xmpp`,    // XMPP
}

// SchemesUnofficial is a sorted list of some well-known url schemes which
// aren't officially registered just yet. They tend to correspond to software.
//
// Mostly collected from https://en.wikipedia.org/wiki/List_of_URI_schemes#Unofficial_but_common_URI_schemes.
var SchemesUnofficial = []string{
	`gemini`,        // gemini
	`jdbc`,          // Java database Connectivity
	`moz-extension`, // Firefox extension
	`postgres`,      // PostgreSQL (short form)
	`postgresql`,    // PostgreSQL
	`slack`,         // Slack
	`zoommtg`,       // Zoom (desktop)
	`zoomus`,        // Zoom (mobile)
}

var strictRe = regexp.MustCompile(strictExp())
var relaxedRe = regexp.MustCompile(relaxedExp())

func anyOf(strs ...string) string {
	var b strings.Builder
	b.WriteByte('(')
	for i, s := range strs {
		if i != 0 {
			b.WriteByte('|')
		}
		b.WriteString(regexp.QuoteMeta(s))
	}
	b.WriteByte(')')
	return b.String()
}

func strictExp() string {
	schemes := `((` + anyOf(Schemes...) + `|` + anyOf(SchemesUnofficial...) + `)://|` + anyOf(SchemesNoAuthority...) + `:)`
	return `(?i)` + schemes + `(?-i)` + pathCont
}

func relaxedExp() string {
	var asciiTLDs, unicodeTLDs []string
	for i, tld := range TLDs {
		if tld[0] >= utf8.RuneSelf {
			asciiTLDs = TLDs[:i:i]
			unicodeTLDs = TLDs[i:]
			break
		}
	}
	punycode := `xn--[a-z0-9-]+`

	// Use \b to make sure ASCII TLDs are immediately followed by a word break.
	// We can't do that with unicode TLDs, as they don't see following
	// whitespace as a word break.
	tlds := `(?i)(` + punycode + `|` + anyOf(append(asciiTLDs, PseudoTLDs...)...) + `\b|` + anyOf(unicodeTLDs...) + `)(?-i)`
	site := domain + tlds

	hostName := `(` + site + `|` + ipAddr + `)`
	webURL := hostName + port + `(/|/` + pathCont + `)?`
	email := `[a-zA-Z0-9._%\-+]+@` + site
	return strictExp() + `|` + webURL + `|` + email
}

// Strict produces a regexp that matches any URL with a scheme in either the
// Schemes or SchemesNoAuthority lists.
func Strict() *regexp.Regexp {
	strictRe.Longest()
	return strictRe.Copy()
}

// Relaxed produces a regexp that matches any URL matched by Strict, plus any
// URL with no scheme or email address.
func Relaxed() *regexp.Regexp {
	relaxedRe.Longest()
	return relaxedRe.Copy()
}

// StrictMatchingScheme produces a regexp similar to Strict, but requiring that
// the scheme match the given regular expression. See AnyScheme too.
func StrictMatchingScheme(exp string) (*regexp.Regexp, error) {
	strictMatching := `(?i)(` + exp + `)(?-i)` + pathCont
	re, err := regexp.Compile(strictMatching)
	if err != nil {
		return nil, err
	}
	re.Longest()
	return re, nil
}
<|endoftext|>


<commit_before>package centralserver

import (
	"distributed2048/rpc/centralrpc"
	"distributed2048/util"
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"sync"
)

const (
	ERROR_LOG bool = true
	DEBUG_LOG bool = true
)

var LOGV = util.NewLogger(DEBUG_LOG, "DEBUG", os.Stdout)
var LOGE = util.NewLogger(ERROR_LOG, "ERROR", os.Stderr)

type gameServer struct {
	info        centralrpc.Node
	clientCount int
}

type centralServer struct {
	nextGameServerID     uint32
	gameServersLock      sync.Mutex
	gameServers          map[uint32]*gameServer
	hostPortToGameServer map[string]*gameServer
	gameServersSlice     []centralrpc.Node
	numGameServers       int
}

func NewCentralServer(port, numGameServers int) (CentralServer, error) {
	LOGV.Println("New Central Server is starting up")
	if numGameServers < 1 {
		return nil, errors.New("numGameServers must be at least 1")
	}

	cs := &centralServer{
		numGameServers:   numGameServers,
		gameServers:      make(map[uint32]*gameServer),
		gameServersSlice: nil,
	}

	// Serve up information for the game client
	http.HandleFunc("/", cs.gameClientViewHandler)
	go http.ListenAndServe(fmt.Sprintf(":%d", port), nil)

	rpc.RegisterName("CentralServer", centralrpc.Wrap(cs))
	rpc.HandleHTTP()
	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return nil, err
	}
	go http.Serve(l, nil)

	return cs, nil
}

func (cs *centralServer) GetGameServerForClient(args *centralrpc.GetGameServerForClientArgs, reply *centralrpc.GetGameServerForClientReply) error {
	cs.gameServersLock.Lock()
	if len(cs.gameServers) < cs.numGameServers {
		// Not all game servers have connected to the ring, so reply with NotReady
		reply.Status = centralrpc.NotReady
	} else {
		id := cs.getGameServerIDMinClients()
		cs.gameServers[id].clientCount++
		reply.Status = centralrpc.OK
		reply.HostPort = cs.gameServers[id].info.HostPort
	}
	cs.gameServersLock.Unlock()

	return nil
}

func (cs *centralServer) RegisterGameServer(args *centralrpc.RegisterGameServerArgs, reply *centralrpc.RegisterGameServerReply) error {
	cs.gameServersLock.Lock()

	var id uint32
	if gs, exists := cs.hostPortToGameServer[args.HostPort]; !exists {

		// Get a new ID
		id = cs.nextGameServerID
		cs.nextGameServerID++

		// Get host:port
		hostport := args.HostPort

		// Add new server object to map
		gs = &gameServer{centralrpc.Node{id, hostport}, 0}
		cs.gameServers[id] = gs
		cs.hostPortToGameServer[hostport] = gs
	} else {
		id = gs.info.NodeID
	}

	// Check if all the game servers in the ring have registered. If they
	// haven't, then reply with not ready. Otherwise, reply with OK, send back
	// to the unique ID, and the list of all game servers.
	if len(cs.gameServers) < cs.numGameServers {
		reply.Status = centralrpc.NotReady
	} else {
		reply.Status = centralrpc.OK
		reply.GameServerID = id
		// Check if the game servers sliced has been cached. If it hasn't, make it.
		if cs.gameServersSlice == nil {
			cs.gameServersSlice = make([]centralrpc.Node, len(cs.gameServers))
			i := 0
			for _, node := range cs.gameServers {
				cs.gameServersSlice[i] = node.info
				i++
			}
		}
		reply.Servers = cs.gameServersSlice
	}

	cs.gameServersLock.Unlock()

	return nil
}

type HttpReply struct {
	Status   string
	Hostport string
}

func (cs *centralServer) gameClientViewHandler(w http.ResponseWriter, r *http.Request) {
	LOGV.Println("a new request was made with URI " + r.RequestURI)
	reply := HttpReply{}
	cs.gameServersLock.Lock()
	if len(cs.gameServers) < cs.numGameServers {
		// Not all game servers have connected to the ring, so reply with NotReady
		reply.Status = "NotReady"
		reply.Hostport = ""
	} else {
		id := cs.getGameServerIDMinClients()
		cs.gameServers[id].clientCount++
		reply.Status = "OK"
		reply.Hostport = cs.gameServers[id].info.HostPort
	}
	cs.gameServersLock.Unlock()
	buf, err := json.Marshal(reply)
	if err == nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Connection", "Keep-Alive")
		_, err = w.Write(buf)
	} else {
		LOGE.Printf("Error with marshalling reply: " + err.Error())
	}
}

func (cs *centralServer) getGameServerIDMinClients() uint32 {
	// Must be called with the LOCK acquired
	min := math.MaxInt32
	var resultID uint32
	for _, gs := range cs.gameServers {
		if gs.clientCount < min {
			min = gs.clientCount
			resultID = gs.info.NodeID
		}
	}
	return resultID
}
<commit_msg>Slight changes to prevent messing with go rpc<commit_after>package centralserver

import (
	"distributed2048/rpc/centralrpc"
	"distributed2048/util"
	"encoding/json"
	"errors"
	"fmt"
	"math"
	"net"
	"net/http"
	"net/rpc"
	"os"
	"sync"
)

const (
	ERROR_LOG bool = true
	DEBUG_LOG bool = true
)

var LOGV = util.NewLogger(DEBUG_LOG, "DEBUG", os.Stdout)
var LOGE = util.NewLogger(ERROR_LOG, "ERROR", os.Stderr)

type gameServer struct {
	info        centralrpc.Node
	clientCount int
}

type centralServer struct {
	nextGameServerID     uint32
	gameServersLock      sync.Mutex
	gameServers          map[uint32]*gameServer
	hostPortToGameServer map[string]*gameServer
	gameServersSlice     []centralrpc.Node
	numGameServers       int
}

func NewCentralServer(port, numGameServers int) (CentralServer, error) {
	LOGV.Println("New Central Server is starting up")
	if numGameServers < 1 {
		return nil, errors.New("numGameServers must be at least 1")
	}

	cs := &centralServer{
		numGameServers:   numGameServers,
		gameServers:      make(map[uint32]*gameServer),
		gameServersSlice: nil,
	}

	// Serve up information for the game client
	http.HandleFunc("/", cs.gameClientViewHandler)
	// go http.ListenAndServe(fmt.Sprintf(":%d", port), nil)

	rpc.RegisterName("CentralServer", centralrpc.Wrap(cs))
	rpc.HandleHTTP()
	l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
	if err != nil {
		return nil, err
	}
	go http.Serve(l, nil)

	return cs, nil
}

func (cs *centralServer) GetGameServerForClient(args *centralrpc.GetGameServerForClientArgs, reply *centralrpc.GetGameServerForClientReply) error {
	cs.gameServersLock.Lock()
	if len(cs.gameServers) < cs.numGameServers {
		// Not all game servers have connected to the ring, so reply with NotReady
		reply.Status = centralrpc.NotReady
	} else {
		id := cs.getGameServerIDMinClients()
		cs.gameServers[id].clientCount++
		reply.Status = centralrpc.OK
		reply.HostPort = cs.gameServers[id].info.HostPort
	}
	cs.gameServersLock.Unlock()

	return nil
}

func (cs *centralServer) RegisterGameServer(args *centralrpc.RegisterGameServerArgs, reply *centralrpc.RegisterGameServerReply) error {
	cs.gameServersLock.Lock()

	var id uint32
	if gs, exists := cs.hostPortToGameServer[args.HostPort]; !exists {

		// Get a new ID
		id = cs.nextGameServerID
		cs.nextGameServerID++

		// Get host:port
		hostport := args.HostPort

		// Add new server object to map
		gs = &gameServer{centralrpc.Node{id, hostport}, 0}
		cs.gameServers[id] = gs
		cs.hostPortToGameServer[hostport] = gs
	} else {
		id = gs.info.NodeID
	}

	// Check if all the game servers in the ring have registered. If they
	// haven't, then reply with not ready. Otherwise, reply with OK, send back
	// to the unique ID, and the list of all game servers.
	if len(cs.gameServers) < cs.numGameServers {
		reply.Status = centralrpc.NotReady
	} else {
		reply.Status = centralrpc.OK
		reply.GameServerID = id
		// Check if the game servers sliced has been cached. If it hasn't, make it.
		if cs.gameServersSlice == nil {
			cs.gameServersSlice = make([]centralrpc.Node, len(cs.gameServers))
			i := 0
			for _, node := range cs.gameServers {
				cs.gameServersSlice[i] = node.info
				i++
			}
		}
		reply.Servers = cs.gameServersSlice
	}

	cs.gameServersLock.Unlock()

	return nil
}

type HttpReply struct {
	Status   string
	Hostport string
}

func (cs *centralServer) gameClientViewHandler(w http.ResponseWriter, r *http.Request) {
	LOGV.Println("a new request was made with URI " + r.RequestURI)
	reply := HttpReply{}
	cs.gameServersLock.Lock()
	if len(cs.gameServers) < cs.numGameServers {
		// Not all game servers have connected to the ring, so reply with NotReady
		reply.Status = "NotReady"
		reply.Hostport = ""
	} else {
		id := cs.getGameServerIDMinClients()
		cs.gameServers[id].clientCount++
		reply.Status = "OK"
		reply.Hostport = cs.gameServers[id].info.HostPort
	}
	cs.gameServersLock.Unlock()
	buf, err := json.Marshal(reply)
	if err == nil {
		w.Header().Set("Content-Type", "text/plain; charset=utf-8")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Connection", "Keep-Alive")
		_, err = w.Write(buf)
	} else {
		LOGE.Printf("Error with marshalling reply: " + err.Error())
	}
}

func (cs *centralServer) getGameServerIDMinClients() uint32 {
	// Must be called with the LOCK acquired
	min := math.MaxInt32
	var resultID uint32
	for _, gs := range cs.gameServers {
		if gs.clientCount < min {
			min = gs.clientCount
			resultID = gs.info.NodeID
		}
	}
	return resultID
}
<|endoftext|>


<commit_before>package agent

import (
	"errors"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/rpcplus"
	"github.com/flynn/go-flynn/attempt"
	rpc "github.com/flynn/rpcplus/comborpc"
)

const (
	HeartbeatIntervalSecs = 5
	MissedHearbeatTTL     = 5
)

type ServiceUpdate struct {
	Name    string
	Addr    string
	Online  bool
	Attrs   map[string]string
	Created uint
}

type Args struct {
	Name  string
	Addr  string
	Attrs map[string]string
}

type UpdateStream interface {
	Chan() chan *ServiceUpdate
	Close()
}

type DiscoveryBackend interface {
	Subscribe(name string) (UpdateStream, error)
	Register(name string, addr string, attrs map[string]string) error
	Unregister(name string, addr string) error
}

type Agent struct {
	Backend DiscoveryBackend
	Address string
}

// Attempts is the attempt strategy that is used to connect to etcd.
var Attempts = attempt.Strategy{
	Min:   5,
	Total: 5 * time.Second,
	Delay: 200 * time.Millisecond,
}

func NewServer(addr string, etcdAddrs []string) *Agent {
	client := etcd.NewClient(etcdAddrs)

	// check to make sure that etcd is online and accepting connections
	// etcd takes a while to come online, so we attempt a GET multiple times
	err := Attempts.Run(func() (err error) {
		_, err = client.Get("/", false, false)
		return
	})
	if err != nil {
		log.Fatalf("Failed to connect to etcd at %v: %q", etcdAddrs, err)
	}

	return &Agent{
		Backend: &EtcdBackend{Client: client},
		Address: addr,
	}
}

func ListenAndServe(server *Agent) error {
	rpc.HandleHTTP()
	if err := rpc.Register(server); err != nil {
		return err
	}
	return http.ListenAndServe(server.Address, nil)
}

var externalIP = os.Getenv("EXTERNAL_IP")

func expandAddr(addr string) string {
	if addr[0] == ':' {
		return externalIP + addr
	}
	return addr
}

func (s *Agent) Subscribe(args *Args, stream rpcplus.Stream) error {
	updates, err := s.Backend.Subscribe(args.Name)
	if err != nil {
		log.Println("Subscribe: error:", err)
		stream.Send <- &ServiceUpdate{} // be sure to unblock client
		return err
	}
	log.Println("Subscribe:", args.Name)
	for update := range updates.Chan() {
		select {
		case stream.Send <- update:
		case <-stream.Error:
			updates.Close()
			return nil
		}
	}
	return nil
}

func (s *Agent) Register(args *Args, ret *string) error {
	if len(args.Addr) == 0 {
		return errors.New("discoverd: Addr must be set")
	}
	addr := expandAddr(args.Addr)
	if addr[0] == ':' {
		return errors.New("discoverd: Addr must have address or EXTERNAL_IP must be set")
	}

	err := s.Backend.Register(args.Name, addr, args.Attrs)
	if err != nil {
		log.Println("Register: error:", err)
		return err
	}
	*ret = addr
	log.Println("Register:", args.Name, addr, args.Attrs)
	return nil
}

func (s *Agent) Unregister(args *Args, ret *struct{}) error {
	addr := expandAddr(args.Addr)
	err := s.Backend.Unregister(args.Name, addr)
	if err != nil {
		log.Println("Unregister: error:", err)
		return err
	}
	log.Println("Unregister:", args.Name, addr)
	return nil
}
<commit_msg>fmt<commit_after>package agent

import (
	"errors"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/coreos/go-etcd/etcd"
	"github.com/flynn/go-flynn/attempt"
	"github.com/flynn/rpcplus"
	rpc "github.com/flynn/rpcplus/comborpc"
)

const (
	HeartbeatIntervalSecs = 5
	MissedHearbeatTTL     = 5
)

type ServiceUpdate struct {
	Name    string
	Addr    string
	Online  bool
	Attrs   map[string]string
	Created uint
}

type Args struct {
	Name  string
	Addr  string
	Attrs map[string]string
}

type UpdateStream interface {
	Chan() chan *ServiceUpdate
	Close()
}

type DiscoveryBackend interface {
	Subscribe(name string) (UpdateStream, error)
	Register(name string, addr string, attrs map[string]string) error
	Unregister(name string, addr string) error
}

type Agent struct {
	Backend DiscoveryBackend
	Address string
}

// Attempts is the attempt strategy that is used to connect to etcd.
var Attempts = attempt.Strategy{
	Min:   5,
	Total: 5 * time.Second,
	Delay: 200 * time.Millisecond,
}

func NewServer(addr string, etcdAddrs []string) *Agent {
	client := etcd.NewClient(etcdAddrs)

	// check to make sure that etcd is online and accepting connections
	// etcd takes a while to come online, so we attempt a GET multiple times
	err := Attempts.Run(func() (err error) {
		_, err = client.Get("/", false, false)
		return
	})
	if err != nil {
		log.Fatalf("Failed to connect to etcd at %v: %q", etcdAddrs, err)
	}

	return &Agent{
		Backend: &EtcdBackend{Client: client},
		Address: addr,
	}
}

func ListenAndServe(server *Agent) error {
	rpc.HandleHTTP()
	if err := rpc.Register(server); err != nil {
		return err
	}
	return http.ListenAndServe(server.Address, nil)
}

var externalIP = os.Getenv("EXTERNAL_IP")

func expandAddr(addr string) string {
	if addr[0] == ':' {
		return externalIP + addr
	}
	return addr
}

func (s *Agent) Subscribe(args *Args, stream rpcplus.Stream) error {
	updates, err := s.Backend.Subscribe(args.Name)
	if err != nil {
		log.Println("Subscribe: error:", err)
		stream.Send <- &ServiceUpdate{} // be sure to unblock client
		return err
	}
	log.Println("Subscribe:", args.Name)
	for update := range updates.Chan() {
		select {
		case stream.Send <- update:
		case <-stream.Error:
			updates.Close()
			return nil
		}
	}
	return nil
}

func (s *Agent) Register(args *Args, ret *string) error {
	if len(args.Addr) == 0 {
		return errors.New("discoverd: Addr must be set")
	}
	addr := expandAddr(args.Addr)
	if addr[0] == ':' {
		return errors.New("discoverd: Addr must have address or EXTERNAL_IP must be set")
	}

	err := s.Backend.Register(args.Name, addr, args.Attrs)
	if err != nil {
		log.Println("Register: error:", err)
		return err
	}
	*ret = addr
	log.Println("Register:", args.Name, addr, args.Attrs)
	return nil
}

func (s *Agent) Unregister(args *Args, ret *struct{}) error {
	addr := expandAddr(args.Addr)
	err := s.Backend.Unregister(args.Name, addr)
	if err != nil {
		log.Println("Unregister: error:", err)
		return err
	}
	log.Println("Unregister:", args.Name, addr)
	return nil
}
<|endoftext|>


<commit_before>package brewerydb

import (
	"fmt"
	"net/http"
)

// YeastService provides access to the BreweryDB Yeast API. Use Client.Yeast.
type YeastService struct {
	c *Client
}

// YeastType represents a specific type/usage of yeast.
type YeastType string

// Specific Yeast types.
const (
	YeastTypeAle       YeastType = "ale"
	YeastTypeWheat               = "wheat"
	YeastTypeLager               = "lager"
	YeastTypeWine                = "wine"
	YeastTypeChampagne           = "champagne"
)

// Yeast represents a type of yeast used in making a Beer.
type Yeast struct {
	ID                  int
	Name                string
	Description         string
	Category            string // This will always be set to "yeast"
	CategoryDisplay     string // This will always be set to "Yeast"
	YeastType           YeastType
	AttenuationMin      float64
	AttenuationMax      float64
	FermentTempMin      float64
	FermentTempMax      float64
	AlcoholToleranceMin float64
	AlcoholToleranceMax float64
	ProductID           string
	Supplier            string
	YeastFormat         string
	CreateDate          string
	UpdateDate          string
}

// YeastList represents a single "page" containing a slice of Yeasts.
type YeastList struct {
	CurrentPage   int
	NumberOfPages int
	TotalResults  int
	Yeasts        []Yeast `json:"data"`
}

// List returns all Yeasts on the given page.
func (ys *YeastService) List(page int) (yl YeastList, err error) {
	// GET: /yeasts
	var req *http.Request
	req, err = ys.c.NewRequest("GET", "/yeasts", &Page{page})
	if err != nil {
		return
	}

	err = ys.c.Do(req, &yl)
	return
}

// Get obtains the Yeast with the given Yeast ID.
func (ys *YeastService) Get(id int) (y Yeast, err error) {
	// GET: /yeast/:yeastID
	var req *http.Request
	req, err = ys.c.NewRequest("GET", fmt.Sprintf("/yeast/%d", id), nil)
	if err != nil {
		return
	}

	resp := struct {
		Status  string
		Data    Yeast
		Message string
	}{}
	err = ys.c.Do(req, &resp)
	return resp.Data, err
}
<commit_msg>api links for yeast.go<commit_after>package brewerydb

import (
	"fmt"
	"net/http"
)

// YeastService provides access to the BreweryDB Yeast API. Use Client.Yeast.
//
// See: http://www.brewerydb.com/developers/docs-endpoint/yeast_index
type YeastService struct {
	c *Client
}

// YeastType represents a specific type/usage of yeast.
type YeastType string

// Specific Yeast types.
const (
	YeastTypeAle       YeastType = "ale"
	YeastTypeWheat               = "wheat"
	YeastTypeLager               = "lager"
	YeastTypeWine                = "wine"
	YeastTypeChampagne           = "champagne"
)

// Yeast represents a type of yeast used in making a Beer.
type Yeast struct {
	ID                  int
	Name                string
	Description         string
	Category            string // This will always be set to "yeast"
	CategoryDisplay     string // This will always be set to "Yeast"
	YeastType           YeastType
	AttenuationMin      float64
	AttenuationMax      float64
	FermentTempMin      float64
	FermentTempMax      float64
	AlcoholToleranceMin float64
	AlcoholToleranceMax float64
	ProductID           string
	Supplier            string
	YeastFormat         string
	CreateDate          string
	UpdateDate          string
}

// YeastList represents a single "page" containing a slice of Yeasts.
type YeastList struct {
	CurrentPage   int
	NumberOfPages int
	TotalResults  int
	Yeasts        []Yeast `json:"data"`
}

// List returns all Yeasts on the given page.
//
// See: http://www.brewerydb.com/developers/docs-endpoint/yeast_index#1
func (ys *YeastService) List(page int) (yl YeastList, err error) {
	// GET: /yeasts
	var req *http.Request
	req, err = ys.c.NewRequest("GET", "/yeasts", &Page{page})
	if err != nil {
		return
	}

	err = ys.c.Do(req, &yl)
	return
}

// Get obtains the Yeast with the given Yeast ID.
//
// See: http://www.brewerydb.com/developers/docs-endpoint/yeast_index#2
func (ys *YeastService) Get(id int) (y Yeast, err error) {
	// GET: /yeast/:yeastID
	var req *http.Request
	req, err = ys.c.NewRequest("GET", fmt.Sprintf("/yeast/%d", id), nil)
	if err != nil {
		return
	}

	resp := struct {
		Status  string
		Data    Yeast
		Message string
	}{}
	err = ys.c.Do(req, &resp)
	return resp.Data, err
}
<|endoftext|>


<commit_before>package agents

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"time"

	. "github.com/eyetoe/foobarbaz/colors"
	. "github.com/eyetoe/foobarbaz/items"
	. "github.com/eyetoe/foobarbaz/locations"
	. "github.com/eyetoe/foobarbaz/util"
)

// HOME is the game working dir
// SAVE is the game save file under HOME
var HOME, SAVES = FbbDirs()

type Stat struct {
	Name string
	Val  int
}

type Agent struct {
	Name        string
	Description string
	// 3 base attributes Str, Int, Dex
	Str Stat
	Int Stat
	Dex Stat
	// Max Health and Health
	MxHp Stat
	Hp   Stat
	Dead bool
	// 3 item slots: Weapon, Armor, Trinket
	Weap  Item
	Armor string
	Trink string
	// Special Abilities
	Abl1 string
	Abl2 string
	Abl3 string
	//
	Exp        int
	DropChance int
	Inv        []Item
	Loc        Loc
	File       string
	Art        string
}

func (c Agent) ExpDrop() int {
	t := c.Str.Val + c.Int.Val + c.Dex.Val + c.MxHp.Val
	return t
}

func (c *Agent) Describe() {
	fmt.Printf(YellowU("\n%s:\n"), c.Name)
	fmt.Printf(Cyan("You consider the %s, "), c.Name)
	fmt.Printf(Cyan("%s\n"), c.Description)
	fmt.Printf("  %s:\n", YellowU(c.Weap.Name))
	fmt.Printf(Cyan("  %s\n"), c.Weap.Description)
	if c.DropChance > 0 {
		fmt.Printf("  %s%% %s\n\n", Yellow(strconv.Itoa(c.DropChance)), Cyan("drop chance"))
	}
	Continue()
}

// Adjust Hp "hit points"
func (c *Agent) AdjHp(a int) {
	c.Hp.Val = c.Hp.Val + a
	if c.Hp.Val > c.MxHp.Val {
		c.Hp.Val = c.MxHp.Val
	}
	if c.Hp.Val <= 0 {
		c.Dead = true
		c.Exp = 0
	}
}

// Load Character from json file
func (c *Agent) Load() {
	if c.File != "" {
		d, err := os.Open(SAVES + c.File + ".json")

		if err != nil {
			fmt.Println("Can't open file:", err.Error())
		}
		jp := json.NewDecoder(d)
		if err = jp.Decode(&c); err != nil {
			fmt.Println("Can't parse json:", err.Error())
		} else {
			fmt.Println(Black(SAVES + c.File + ".json loaded."))
		}
	}
	return
}

// Save Character to json file
func (c *Agent) Save() {
	if c.File != "" {
		//Oh sweet MarshalIndent, you make my json look pretty
		i, _ := json.MarshalIndent(c, "", "    ")

		//if err := ioutil.WriteFile("save/"+c.File+".json", i, 0644); err != nil {
		if err := ioutil.WriteFile(SAVES+c.File+".json", i, 0644); err != nil {
			fmt.Println("Can't write file:", err.Error())
		} else {
			fmt.Println(Black(SAVES + c.File + ".json saved."))
		}
	}
	return
}

// Render character status bar
func (c Agent) StatusBar() {
	// For sanity layout the StatusBar vertically while printing horizonal
	fmt.Println()
	fmt.Printf("%s", Fbb("FooBarBaz:"))
	fmt.Printf("%s", Yellow(" "))
	fmt.Printf("%s", BlueU(c.Name))
	fmt.Printf("%s", Yellow(" S:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Str.Val)))
	fmt.Printf("%s", Yellow(" I:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Int.Val)))
	fmt.Printf("%s", Yellow(" D:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Dex.Val)))
	fmt.Printf("%s", Yellow(" HP:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.MxHp.Val)))
	fmt.Printf("%s", Yellow("|"))

	if c.Hp.Val < c.MxHp.Val {
		fmt.Printf("%s", Red(strconv.Itoa(c.Hp.Val)))
	} else {
		fmt.Printf("%s", Green(strconv.Itoa(c.Hp.Val)))
	}

	fmt.Printf("%s", Yellow(" XP:"))
	if StatCost(c) <= c.Exp {
		fmt.Printf("%s", Green(strconv.Itoa(c.Exp)))
	} else {
		fmt.Printf("%s", strconv.Itoa(c.Exp))
	}

	fmt.Printf("%s", Yellow(" W:"))
	fmt.Printf("%s", ItemC(c.Weap.Name))
	fmt.Printf("%s", Yellow(" A:"))
	fmt.Printf("%s", ItemC(c.Armor))
	fmt.Printf("%s", Yellow(" T:"))
	fmt.Printf("%s", ItemC(c.Trink))
	//	if c.Dead == false {
	//		fmt.Printf("%s", Yellow(" E?:"))
	//		fmt.Printf("%s", MagentaU("none"))
	//	} else {
	//		fmt.Printf("%s", Red(" Dead :("))
	//	}
	fmt.Println()
	//	if c.Dead == true {
	//		fmt.Printf(Red("\n%s collapsed in a sobbing frightned lump and expired.\n\n"), c.Name)
	//
	//		for t := 0; t < 5; t++ {
	//			fmt.Printf(Blue("."))
	//			time.Sleep(100 * time.Millisecond)
	//		}
	//		fmt.Println()
	//		if Confirm(Yellow("Retry?")) == true {
	//			Resurrect(&c)
	//		} else {
	//			os.Exit(0)
	//		}
	//	}
	Meter(c.Hp.Val, c.MxHp.Val, "Health", "=")
}

// StatCost cost increases with the fibonacci number position.
// The average stat is compared with the position in the fibonacci sequence
// then the product of the postion and the multiplier var = Stat Cost
func StatCost(c Agent) int {
	m := 1 // multiplier
	tStat := c.Str.Val + c.Int.Val + c.Dex.Val + c.MxHp.Val
	avStat := tStat / 4

	switch {
	case avStat <= 1:
		return 1 * m
	case avStat <= 2:
		return 2 * m
	case avStat <= 3:
		return 3 * m
	case avStat <= 5:
		return 5 * m
	case avStat <= 8:
		return 8 * m
	case avStat <= 13:
		return 13 * m
	case avStat <= 21:
		return 21 * m
	case avStat <= 34:
		return 34 * m
	case avStat <= 55:
		return 55 * m
	case avStat <= 89:
		return 89 * m
	case avStat <= 144:
		return 144 * m
	default:
		return 200 * m
	}
}

// FbbDirs returns the HOME and SAVE vars values
func FbbDirs() (string, string) {
	// Is the ENV var $HOME set?
	home, ok := os.LookupEnv("HOME")
	if ok != true {
		fmt.Println("Please set your $HOME environment variable.")
		os.Exit(1)
	}
	return home + "/.foobarbaz/", home + "/.foobarbaz/saves/"
}

// EnvSetup prepares the game HOME directory
func EnvSetup() {
	// Is the client dir already there?
	if _, err := os.Stat(HOME); err != nil {
		if os.IsNotExist(err) {
			// Make the dir
			if err := os.Mkdir(HOME, 0744); err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	}

	// Is the saves dir already there?
	if _, err := os.Stat(SAVES); err != nil {
		if os.IsNotExist(err) {
			// Make the dir
			if err := os.Mkdir(SAVES, 0744); err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	}
	// set working dir to $HOME/.foobarbaz
	if err := os.Chdir(HOME); err != nil {
		fmt.Println("Can't move to working directory", err)
	}

	// TESTING: list files in working dir
	files, _ := ioutil.ReadDir("./")
	for _, f := range files {
		fmt.Println("list files:", f.Name())
	}
	return
}

func Resurrect(c *Agent) {
	if c.Dead == true {
		fmt.Printf("")
		time.Sleep(1000 * time.Millisecond)
		fmt.Printf(Blue("\n A mystical light shines down on %s's lifeless corpse.\n\n A sulfurous effluvium expands from the body.\n\n %s takes a gasping breath, and lives!\n\n"), c.Name, c.Name)
		c.Hp.Val = c.MxHp.Val
		c.Dead = false
		c.Save()
		for t := 0; t < 5; t++ {
			fmt.Printf(Blue("."))
			time.Sleep(250 * time.Millisecond)
		}
		Continue()
	} else {
		return
	}
}
<commit_msg>add attack and damage details to agent describe<commit_after>package agents

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"strconv"
	"time"

	. "github.com/eyetoe/foobarbaz/colors"
	. "github.com/eyetoe/foobarbaz/items"
	. "github.com/eyetoe/foobarbaz/locations"
	. "github.com/eyetoe/foobarbaz/util"
)

// HOME is the game working dir
// SAVE is the game save file under HOME
var HOME, SAVES = FbbDirs()

type Stat struct {
	Name string
	Val  int
}

type Agent struct {
	Name        string
	Description string
	// 3 base attributes Str, Int, Dex
	Str Stat
	Int Stat
	Dex Stat
	// Max Health and Health
	MxHp Stat
	Hp   Stat
	Dead bool
	// 3 item slots: Weapon, Armor, Trinket
	Weap  Item
	Armor string
	Trink string
	// Special Abilities
	Abl1 string
	Abl2 string
	Abl3 string
	//
	Exp        int
	DropChance int
	Inv        []Item
	Loc        Loc
	File       string
	Art        string
}

func (c Agent) ExpDrop() int {
	t := c.Str.Val + c.Int.Val + c.Dex.Val + c.MxHp.Val
	return t
}

func (c *Agent) Describe() {
	fmt.Printf(YellowU("\n%s:\n"), c.Name)
	fmt.Printf(Cyan("You consider the %s, "), c.Name)
	fmt.Printf(Cyan("%s\n"), c.Description)
	fmt.Printf("  %s: Attack: +%s, Damage: 1d%s\n", YellowU(c.Weap.Name), Yellow(c.Weap.Attack), Yellow(c.Weap.Damage))
	fmt.Printf(Cyan("  %s\n"), c.Weap.Description)
	if c.DropChance > 0 {
		fmt.Printf("  %s%% %s\n\n", Yellow(strconv.Itoa(c.DropChance)), Cyan("drop chance"))
	}
	Continue()
}

// Adjust Hp "hit points"
func (c *Agent) AdjHp(a int) {
	c.Hp.Val = c.Hp.Val + a
	if c.Hp.Val > c.MxHp.Val {
		c.Hp.Val = c.MxHp.Val
	}
	if c.Hp.Val <= 0 {
		c.Dead = true
		c.Exp = 0
	}
}

// Load Character from json file
func (c *Agent) Load() {
	if c.File != "" {
		d, err := os.Open(SAVES + c.File + ".json")

		if err != nil {
			fmt.Println("Can't open file:", err.Error())
		}
		jp := json.NewDecoder(d)
		if err = jp.Decode(&c); err != nil {
			fmt.Println("Can't parse json:", err.Error())
		} else {
			fmt.Println(Black(SAVES + c.File + ".json loaded."))
		}
	}
	return
}

// Save Character to json file
func (c *Agent) Save() {
	if c.File != "" {
		//Oh sweet MarshalIndent, you make my json look pretty
		i, _ := json.MarshalIndent(c, "", "    ")

		//if err := ioutil.WriteFile("save/"+c.File+".json", i, 0644); err != nil {
		if err := ioutil.WriteFile(SAVES+c.File+".json", i, 0644); err != nil {
			fmt.Println("Can't write file:", err.Error())
		} else {
			fmt.Println(Black(SAVES + c.File + ".json saved."))
		}
	}
	return
}

// Render character status bar
func (c Agent) StatusBar() {
	// For sanity layout the StatusBar vertically while printing horizonal
	fmt.Println()
	fmt.Printf("%s", Fbb("FooBarBaz:"))
	fmt.Printf("%s", Yellow(" "))
	fmt.Printf("%s", BlueU(c.Name))
	fmt.Printf("%s", Yellow(" S:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Str.Val)))
	fmt.Printf("%s", Yellow(" I:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Int.Val)))
	fmt.Printf("%s", Yellow(" D:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.Dex.Val)))
	fmt.Printf("%s", Yellow(" HP:"))
	fmt.Printf("%s", Green(strconv.Itoa(c.MxHp.Val)))
	fmt.Printf("%s", Yellow("|"))

	if c.Hp.Val < c.MxHp.Val {
		fmt.Printf("%s", Red(strconv.Itoa(c.Hp.Val)))
	} else {
		fmt.Printf("%s", Green(strconv.Itoa(c.Hp.Val)))
	}

	fmt.Printf("%s", Yellow(" XP:"))
	if StatCost(c) <= c.Exp {
		fmt.Printf("%s", Green(strconv.Itoa(c.Exp)))
	} else {
		fmt.Printf("%s", strconv.Itoa(c.Exp))
	}

	fmt.Printf("%s", Yellow(" W:"))
	fmt.Printf("%s", ItemC(c.Weap.Name))
	fmt.Printf("%s", Yellow(" A:"))
	fmt.Printf("%s", ItemC(c.Armor))
	fmt.Printf("%s", Yellow(" T:"))
	fmt.Printf("%s", ItemC(c.Trink))
	//	if c.Dead == false {
	//		fmt.Printf("%s", Yellow(" E?:"))
	//		fmt.Printf("%s", MagentaU("none"))
	//	} else {
	//		fmt.Printf("%s", Red(" Dead :("))
	//	}
	fmt.Println()
	//	if c.Dead == true {
	//		fmt.Printf(Red("\n%s collapsed in a sobbing frightned lump and expired.\n\n"), c.Name)
	//
	//		for t := 0; t < 5; t++ {
	//			fmt.Printf(Blue("."))
	//			time.Sleep(100 * time.Millisecond)
	//		}
	//		fmt.Println()
	//		if Confirm(Yellow("Retry?")) == true {
	//			Resurrect(&c)
	//		} else {
	//			os.Exit(0)
	//		}
	//	}
	Meter(c.Hp.Val, c.MxHp.Val, "Health", "=")
}

// StatCost cost increases with the fibonacci number position.
// The average stat is compared with the position in the fibonacci sequence
// then the product of the postion and the multiplier var = Stat Cost
func StatCost(c Agent) int {
	m := 1 // multiplier
	tStat := c.Str.Val + c.Int.Val + c.Dex.Val + c.MxHp.Val
	avStat := tStat / 4

	switch {
	case avStat <= 1:
		return 1 * m
	case avStat <= 2:
		return 2 * m
	case avStat <= 3:
		return 3 * m
	case avStat <= 5:
		return 5 * m
	case avStat <= 8:
		return 8 * m
	case avStat <= 13:
		return 13 * m
	case avStat <= 21:
		return 21 * m
	case avStat <= 34:
		return 34 * m
	case avStat <= 55:
		return 55 * m
	case avStat <= 89:
		return 89 * m
	case avStat <= 144:
		return 144 * m
	default:
		return 200 * m
	}
}

// FbbDirs returns the HOME and SAVE vars values
func FbbDirs() (string, string) {
	// Is the ENV var $HOME set?
	home, ok := os.LookupEnv("HOME")
	if ok != true {
		fmt.Println("Please set your $HOME environment variable.")
		os.Exit(1)
	}
	return home + "/.foobarbaz/", home + "/.foobarbaz/saves/"
}

// EnvSetup prepares the game HOME directory
func EnvSetup() {
	// Is the client dir already there?
	if _, err := os.Stat(HOME); err != nil {
		if os.IsNotExist(err) {
			// Make the dir
			if err := os.Mkdir(HOME, 0744); err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	}

	// Is the saves dir already there?
	if _, err := os.Stat(SAVES); err != nil {
		if os.IsNotExist(err) {
			// Make the dir
			if err := os.Mkdir(SAVES, 0744); err != nil {
				fmt.Println(err)
				os.Exit(1)
			}
		}
	}
	// set working dir to $HOME/.foobarbaz
	if err := os.Chdir(HOME); err != nil {
		fmt.Println("Can't move to working directory", err)
	}

	// TESTING: list files in working dir
	files, _ := ioutil.ReadDir("./")
	for _, f := range files {
		fmt.Println("list files:", f.Name())
	}
	return
}

func Resurrect(c *Agent) {
	if c.Dead == true {
		fmt.Printf("")
		time.Sleep(1000 * time.Millisecond)
		fmt.Printf(Blue("\n A mystical light shines down on %s's lifeless corpse.\n\n A sulfurous effluvium expands from the body.\n\n %s takes a gasping breath, and lives!\n\n"), c.Name, c.Name)
		c.Hp.Val = c.MxHp.Val
		c.Dead = false
		c.Save()
		for t := 0; t < 5; t++ {
			fmt.Printf(Blue("."))
			time.Sleep(250 * time.Millisecond)
		}
		Continue()
	} else {
		return
	}
}
<|endoftext|>


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

package pubsub

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/api/iterator"
)

func TestReturnsDoneOnStop(t *testing.T) {
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	type testCase struct {
		abort func(*messageIterator, context.CancelFunc)
		want  error
	}

	for _, tc := range []testCase{
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				it.Stop()
			},
			want: iterator.Done,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				cancel()
			},
			want: context.Canceled,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				it.Stop()
				cancel()
			},
			want: iterator.Done,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				cancel()
				it.Stop()
			},
			want: iterator.Done,
		},
	} {
		s := &blockingFetch{}
		ctx, cancel := context.WithCancel(context.Background())
		it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: time.Hour})
		defer it.Stop()
		tc.abort(it, cancel)

		_, err := it.Next()
		if err != tc.want {
			t.Errorf("iterator Next error after abort: got:\n%v\nwant:\n%v", err, tc.want)
		}
	}
}

// blockingFetch implements message fetching by not returning until its context is cancelled.
type blockingFetch struct {
	service
}

func (s *blockingFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
	<-ctx.Done()
	return nil, ctx.Err()
}

func (s *blockingFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

// justInTimeFetch simulates the situation where the iterator is aborted just after the fetch RPC
// succeeds, so the rest of puller.Next will continue to execute and return sucessfully.
type justInTimeFetch struct {
	service
}

func (s *justInTimeFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
	<-ctx.Done()
	// The context was cancelled, but let's pretend that this happend just after our RPC returned.

	var result []*Message
	for i := 0; i < int(maxMessages); i++ {
		val := fmt.Sprintf("msg%v", i)
		result = append(result, &Message{Data: []byte(val), ackID: val})
	}
	return result, nil
}

func (s *justInTimeFetch) splitAckIDs(ids []string) ([]string, []string) {
	return nil, nil
}

func (s *justInTimeFetch) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
	return nil
}

func (s *justInTimeFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

func TestAfterAbortReturnsNoMoreThanOneMessage(t *testing.T) {
	// Each test case is excercised by making two concurrent blocking calls on a
	// messageIterator, and then aborting the iterator.
	// The result should be one call to Next returning a message, and the other returning an error.
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	type testCase struct {
		abort func(*messageIterator, context.CancelFunc)
		// want is the error that should be returned from one Next invocation.
		want error
	}
	for n := 1; n < 3; n++ {
		for _, tc := range []testCase{
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					it.Stop()
				},
				want: iterator.Done,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					cancel()
				},
				want: context.Canceled,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					it.Stop()
					cancel()
				},
				want: iterator.Done,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					cancel()
					it.Stop()
				},
				want: iterator.Done,
			},
		} {
			s := &justInTimeFetch{}
			ctx, cancel := context.WithCancel(context.Background())

			// if maxPrefetch == 1, there will be no messages in the puller buffer when Next is invoked the second time.
			// if maxPrefetch == 2, there will be 1 message in the puller buffer when Next is invoked the second time.
			po := &pullOptions{
				ackDeadline:  time.Second * 10,
				maxExtension: time.Hour,
				maxPrefetch:  int32(n),
			}
			it := newMessageIterator(ctx, s, "subname", po)
			defer it.Stop()

			type result struct {
				m   *Message
				err error
			}
			results := make(chan *result, 2)

			for i := 0; i < 2; i++ {
				go func() {
					m, err := it.Next()
					results <- &result{m, err}
					if err == nil {
						m.Nack()
					}
				}()
			}
			// Wait for goroutines to block on it.Next().
			time.Sleep(50 * time.Millisecond)
			tc.abort(it, cancel)

			result1 := <-results
			result2 := <-results

			// There should be one error result, and one non-error result.
			// Make result1 be the non-error result.
			if result1.err != nil {
				result1, result2 = result2, result1
			}

			if string(result1.m.Data) != "msg0" {
				t.Errorf("After abort, got message: %v, want %v", result1.m.Data, "msg0")
			}
			if result1.err != nil {
				t.Errorf("After abort, got : %v, want nil", result1.err)
			}
			if result2.m != nil {
				t.Errorf("After abort, got message: %v, want nil", result2.m)
			}
			if result2.err != tc.want {
				t.Errorf("After abort, got err: %v, want %v", result2.err, tc.want)
			}
		}
	}
}

type fetcherServiceWithModifyAckDeadline struct {
	fetcherService
	events chan string
}

func (f *fetcherServiceWithModifyAckDeadline) modifyAckDeadline(_ context.Context, _ string, d time.Duration, ids []string) error {
	// Different versions of Go use different representations for time.Duration(0).
	var ds string
	if d == 0 {
		ds = "0s"
	} else {
		ds = d.String()
	}
	f.events <- fmt.Sprintf("modAck(%v, %s)", ids, ds)
	return nil
}

func (f *fetcherServiceWithModifyAckDeadline) splitAckIDs(ackIDs []string) ([]string, []string) {
	return ackIDs, nil
}

func (f *fetcherServiceWithModifyAckDeadline) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

func TestMultipleStopCallsBlockUntilMessageDone(t *testing.T) {
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	events := make(chan string, 3)
	s := &fetcherServiceWithModifyAckDeadline{
		fetcherService{
			results: []fetchResult{
				{
					msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
				},
			},
		},
		events,
	}

	ctx := context.Background()
	it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: 0})

	m, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}

	go func() {
		it.Stop()
		events <- "stopped"
	}()
	go func() {
		it.Stop()
		events <- "stopped"
	}()

	select {
	case <-events:
		t.Fatal("Stop is not blocked")
	case <-time.After(100 * time.Millisecond):
	}
	m.Nack()

	got := []string{<-events, <-events, <-events}
	want := []string{"modAck([a], 0s)", "stopped", "stopped"}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("stopping iterator, got: %v ; want: %v", got, want)
	}

	// The iterator is stopped, so should not return another message.
	m, err = it.Next()
	if m != nil {
		t.Errorf("message got: %v ; want: nil", m)
	}
	if err != iterator.Done {
		t.Errorf("err got: %v ; want: %v", err, iterator.Done)
	}
}

func TestFastNack(t *testing.T) {
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	events := make(chan string, 3)
	s := &fetcherServiceWithModifyAckDeadline{
		fetcherService{
			results: []fetchResult{
				{
					msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
				},
			},
		},
		events,
	}

	ctx := context.Background()
	it := newMessageIterator(ctx, s, "subname", &pullOptions{
		ackDeadline:  time.Second * 6,
		maxExtension: time.Second * 10,
	})
	// Get both messages.
	_, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}
	m2, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}
	// Ignore the first, nack the second.
	m2.Nack()

	got := []string{<-events, <-events}
	// The nack should happen before the deadline extension.
	want := []string{"modAck([b], 0s)", "modAck([a], 6s)"}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got: %v ; want: %v", got, want)
	}
}
<commit_msg>pubsub: disable flaky iterator test<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pubsub

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"golang.org/x/net/context"

	"google.golang.org/api/iterator"
)

func TestReturnsDoneOnStop(t *testing.T) {
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	type testCase struct {
		abort func(*messageIterator, context.CancelFunc)
		want  error
	}

	for _, tc := range []testCase{
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				it.Stop()
			},
			want: iterator.Done,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				cancel()
			},
			want: context.Canceled,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				it.Stop()
				cancel()
			},
			want: iterator.Done,
		},
		{
			abort: func(it *messageIterator, cancel context.CancelFunc) {
				cancel()
				it.Stop()
			},
			want: iterator.Done,
		},
	} {
		s := &blockingFetch{}
		ctx, cancel := context.WithCancel(context.Background())
		it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: time.Hour})
		defer it.Stop()
		tc.abort(it, cancel)

		_, err := it.Next()
		if err != tc.want {
			t.Errorf("iterator Next error after abort: got:\n%v\nwant:\n%v", err, tc.want)
		}
	}
}

// blockingFetch implements message fetching by not returning until its context is cancelled.
type blockingFetch struct {
	service
}

func (s *blockingFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
	<-ctx.Done()
	return nil, ctx.Err()
}

func (s *blockingFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

// justInTimeFetch simulates the situation where the iterator is aborted just after the fetch RPC
// succeeds, so the rest of puller.Next will continue to execute and return sucessfully.
type justInTimeFetch struct {
	service
}

func (s *justInTimeFetch) fetchMessages(ctx context.Context, subName string, maxMessages int32) ([]*Message, error) {
	<-ctx.Done()
	// The context was cancelled, but let's pretend that this happend just after our RPC returned.

	var result []*Message
	for i := 0; i < int(maxMessages); i++ {
		val := fmt.Sprintf("msg%v", i)
		result = append(result, &Message{Data: []byte(val), ackID: val})
	}
	return result, nil
}

func (s *justInTimeFetch) splitAckIDs(ids []string) ([]string, []string) {
	return nil, nil
}

func (s *justInTimeFetch) modifyAckDeadline(ctx context.Context, subName string, deadline time.Duration, ackIDs []string) error {
	return nil
}

func (s *justInTimeFetch) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

func TestAfterAbortReturnsNoMoreThanOneMessage(t *testing.T) {
	// Each test case is excercised by making two concurrent blocking calls on a
	// messageIterator, and then aborting the iterator.
	// The result should be one call to Next returning a message, and the other returning an error.
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	type testCase struct {
		abort func(*messageIterator, context.CancelFunc)
		// want is the error that should be returned from one Next invocation.
		want error
	}
	for n := 1; n < 3; n++ {
		for _, tc := range []testCase{
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					it.Stop()
				},
				want: iterator.Done,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					cancel()
				},
				want: context.Canceled,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					it.Stop()
					cancel()
				},
				want: iterator.Done,
			},
			{
				abort: func(it *messageIterator, cancel context.CancelFunc) {
					cancel()
					it.Stop()
				},
				want: iterator.Done,
			},
		} {
			s := &justInTimeFetch{}
			ctx, cancel := context.WithCancel(context.Background())

			// if maxPrefetch == 1, there will be no messages in the puller buffer when Next is invoked the second time.
			// if maxPrefetch == 2, there will be 1 message in the puller buffer when Next is invoked the second time.
			po := &pullOptions{
				ackDeadline:  time.Second * 10,
				maxExtension: time.Hour,
				maxPrefetch:  int32(n),
			}
			it := newMessageIterator(ctx, s, "subname", po)
			defer it.Stop()

			type result struct {
				m   *Message
				err error
			}
			results := make(chan *result, 2)

			for i := 0; i < 2; i++ {
				go func() {
					m, err := it.Next()
					results <- &result{m, err}
					if err == nil {
						m.Nack()
					}
				}()
			}
			// Wait for goroutines to block on it.Next().
			time.Sleep(50 * time.Millisecond)
			tc.abort(it, cancel)

			result1 := <-results
			result2 := <-results

			// There should be one error result, and one non-error result.
			// Make result1 be the non-error result.
			if result1.err != nil {
				result1, result2 = result2, result1
			}

			if string(result1.m.Data) != "msg0" {
				t.Errorf("After abort, got message: %v, want %v", result1.m.Data, "msg0")
			}
			if result1.err != nil {
				t.Errorf("After abort, got : %v, want nil", result1.err)
			}
			if result2.m != nil {
				t.Errorf("After abort, got message: %v, want nil", result2.m)
			}
			if result2.err != tc.want {
				t.Errorf("After abort, got err: %v, want %v", result2.err, tc.want)
			}
		}
	}
}

type fetcherServiceWithModifyAckDeadline struct {
	fetcherService
	events chan string
}

func (f *fetcherServiceWithModifyAckDeadline) modifyAckDeadline(_ context.Context, _ string, d time.Duration, ids []string) error {
	// Different versions of Go use different representations for time.Duration(0).
	var ds string
	if d == 0 {
		ds = "0s"
	} else {
		ds = d.String()
	}
	f.events <- fmt.Sprintf("modAck(%v, %s)", ids, ds)
	return nil
}

func (f *fetcherServiceWithModifyAckDeadline) splitAckIDs(ackIDs []string) ([]string, []string) {
	return ackIDs, nil
}

func (f *fetcherServiceWithModifyAckDeadline) newStreamingPuller(ctx context.Context, subName string, ackDeadline int32) *streamingPuller {
	return nil
}

func TestMultipleStopCallsBlockUntilMessageDone(t *testing.T) {
	t.Skip(`This test has subtle timing dependencies, making it flaky.
It is not worth fixing because iterators will be removed shortly.`)
	events := make(chan string, 3)
	s := &fetcherServiceWithModifyAckDeadline{
		fetcherService{
			results: []fetchResult{
				{
					msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
				},
			},
		},
		events,
	}

	ctx := context.Background()
	it := newMessageIterator(ctx, s, "subname", &pullOptions{ackDeadline: time.Second * 10, maxExtension: 0})

	m, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}

	go func() {
		it.Stop()
		events <- "stopped"
	}()
	go func() {
		it.Stop()
		events <- "stopped"
	}()

	select {
	case <-events:
		t.Fatal("Stop is not blocked")
	case <-time.After(100 * time.Millisecond):
	}
	m.Nack()

	got := []string{<-events, <-events, <-events}
	want := []string{"modAck([a], 0s)", "stopped", "stopped"}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("stopping iterator, got: %v ; want: %v", got, want)
	}

	// The iterator is stopped, so should not return another message.
	m, err = it.Next()
	if m != nil {
		t.Errorf("message got: %v ; want: nil", m)
	}
	if err != iterator.Done {
		t.Errorf("err got: %v ; want: %v", err, iterator.Done)
	}
}

func TestFastNack(t *testing.T) {
	if useStreamingPull {
		t.Skip("iterator tests are for polling pull only")
	}
	events := make(chan string, 3)
	s := &fetcherServiceWithModifyAckDeadline{
		fetcherService{
			results: []fetchResult{
				{
					msgs: []*Message{{ackID: "a"}, {ackID: "b"}},
				},
			},
		},
		events,
	}

	ctx := context.Background()
	it := newMessageIterator(ctx, s, "subname", &pullOptions{
		ackDeadline:  time.Second * 6,
		maxExtension: time.Second * 10,
	})
	// Get both messages.
	_, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}
	m2, err := it.Next()
	if err != nil {
		t.Errorf("error calling Next: %v", err)
	}
	// Ignore the first, nack the second.
	m2.Nack()

	got := []string{<-events, <-events}
	// The nack should happen before the deadline extension.
	want := []string{"modAck([b], 0s)", "modAck([a], 6s)"}
	if !reflect.DeepEqual(got, want) {
		t.Errorf("got: %v ; want: %v", got, want)
	}
}
<|endoftext|>


<commit_before>package authorization

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"regexp"

	"github.com/go-chat-bot/bot"
)

var (
	userfile Userfile
)

// Admin can do anything in a channel.
const Admin = "admin"

// Author can add a quote to the quote database.
const Author = "author"

// Deleter can delete a quote from the database.
const Deleter = "deleter"

// Userfile reflects the entire JSON file.
type Userfile struct {
	Users    []User    `json:"users"`
	Channels []Channel `json:"channels"`
}

// User holds a singular user.
type User struct {
	Username string   `json:"username"`
	Masks    []string `json:"masks"`
}

// Channel holds channel name and various user types and their Users.
type Channel struct {
	Channel  string `json:"channel"`
	Admins   []string
	Authors  []string `json:"authors"`
	Deleters []string `json:"deleters"`
}

// Authorize a bot User against a role in a channel.
func Authorize(user *bot.User, channel string, role string) bool {
	username, err := FindUsername(FullHostmask(user))
	if err != nil {
		return false
	}

	var list []string
	for _, Channel := range userfile.Channels {
		if channel != Channel.Channel {
			continue
		}
		switch role {
		case Admin:
			list = Channel.Admins
		case Author:
			list = Channel.Authors
		case Deleter:
			list = Channel.Deleters
		default:
			return false
		}

		for _, uname := range list {
			if uname == "*" || uname == username {
				return true
			}
		}

		return false
	}

	return false
}

// FullHostmask constructs a full user host mask, inpsired by IRC, ready for matching against a regex.
func FullHostmask(user *bot.User) []byte {
	return []byte(fmt.Sprintf("%s!%s@%s", user.Nick, user.RealName, user.ID))
}

// FindUsername finds a username for a given full host.
func FindUsername(host []byte) (username string, err error) {
	for _, user := range userfile.Users {
		for _, mask := range user.Masks {
			match, err := regexp.Match(mask, host)
			if err != nil {
				return "", err
			}
			if match {
				username = user.Username
				return username, nil
			}

		}
	}

	return "*", nil
}

// ReloadUserfile reloads userfile.json into our Userfile struct.
func ReloadUserfile() Userfile {
	file, err := ioutil.ReadFile("userfile.json")
	if err != nil {
		log.Fatalf("Couldn't read file: %#v", err)
	}

	json.Unmarshal(file, &userfile)
	log.Printf("Reloaded userfile: %#v\n", userfile)

	return userfile
}

func init() {
	ReloadUserfile()
}
<commit_msg>Run init<commit_after>package authorization

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"regexp"

	"github.com/go-chat-bot/bot"
)

var (
	userfile Userfile
)

// Admin can do anything in a channel.
const Admin = "admin"

// Author can add a quote to the quote database.
const Author = "author"

// Deleter can delete a quote from the database.
const Deleter = "deleter"

// Userfile reflects the entire JSON file.
type Userfile struct {
	Users    []User    `json:"users"`
	Channels []Channel `json:"channels"`
}

// User holds a singular user.
type User struct {
	Username string   `json:"username"`
	Masks    []string `json:"masks"`
}

// Channel holds channel name and various user types and their Users.
type Channel struct {
	Channel  string `json:"channel"`
	Admins   []string
	Authors  []string `json:"authors"`
	Deleters []string `json:"deleters"`
}

// Authorize a bot User against a role in a channel.
func Authorize(user *bot.User, channel string, role string) bool {
	username, err := FindUsername(FullHostmask(user))
	if err != nil {
		return false
	}

	var list []string
	for _, Channel := range userfile.Channels {
		if channel != Channel.Channel {
			continue
		}
		switch role {
		case Admin:
			list = Channel.Admins
		case Author:
			list = Channel.Authors
		case Deleter:
			list = Channel.Deleters
		default:
			return false
		}

		for _, uname := range list {
			if uname == "*" || uname == username {
				return true
			}
		}

		return false
	}

	return false
}

// FullHostmask constructs a full user host mask, inpsired by IRC, ready for matching against a regex.
func FullHostmask(user *bot.User) []byte {
	return []byte(fmt.Sprintf("%s!%s@%s", user.Nick, user.RealName, user.ID))
}

// FindUsername finds a username for a given full host.
func FindUsername(host []byte) (username string, err error) {
	for _, user := range userfile.Users {
		for _, mask := range user.Masks {
			match, err := regexp.Match(mask, host)
			if err != nil {
				return "", err
			}
			if match {
				username = user.Username
				return username, nil
			}

		}
	}

	return "*", nil
}

// ReloadUserfile reloads userfile.json into our Userfile struct.
func ReloadUserfile() Userfile {
	file, err := ioutil.ReadFile("userfile.json")
	if err != nil {
		log.Fatalf("Couldn't read file: %#v", err)
	}

	json.Unmarshal(file, &userfile)
	log.Printf("Reloaded userfile: %#v\n", userfile)

	return userfile
}

func reloadUsers(command *bot.Cmd) (msg string, err error) {
	if Authorize(command.User, command.Channel, "admin") == false {
		return
	}

	ReloadUserfile()

	return
}

func init() {
	ReloadUserfile()

	bot.RegisterCommand(
		"reloadusers",
		"Reload the user file",
		"",
		reloadUsers)
}
<|endoftext|>