<commit_before>// Copyright 2015 Hajime Hoshi
//
// 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 js

import (
	"encoding/hex"
	"syscall/js"
	"unicode"

	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
)

var (
	stringKeydown    = js.ValueOf("keydown")
	stringKeypress   = js.ValueOf("keypress")
	stringKeyup      = js.ValueOf("keyup")
	stringMousedown  = js.ValueOf("mousedown")
	stringMouseup    = js.ValueOf("mouseup")
	stringMousemove  = js.ValueOf("mousemove")
	stringWheel      = js.ValueOf("wheel")
	stringTouchstart = js.ValueOf("touchstart")
	stringTouchend   = js.ValueOf("touchend")
	stringTouchmove  = js.ValueOf("touchmove")
)

var jsKeys []js.Value

func init() {
	for _, k := range driverKeyToJSKey {
		jsKeys = append(jsKeys, k)
	}
}

func jsKeyToID(key js.Value) int {
	// js.Value cannot be used as a map key.
	// As the number of keys is around 100, just a dumb loop should work.
	for i, k := range jsKeys {
		if jsutil.Equal(k, key) {
			return i
		}
	}
	return -1
}

type pos struct {
	X int
	Y int
}

type gamePad struct {
	valid         bool
	name          string
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type Input struct {
	keyPressed         map[int]bool
	keyPressedEdge     map[int]bool
	mouseButtonPressed map[int]bool
	cursorX            int
	cursorY            int
	wheelX             float64
	wheelY             float64
	gamepads           [16]gamePad
	touches            map[driver.TouchID]pos
	runeBuffer         []rune
	ui                 *UserInterface
}

func (i *Input) CursorPosition() (x, y int) {
	xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor())
	return int(xf), int(yf)
}

func (i *Input) GamepadSDLID(id driver.GamepadID) string {
	// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
	// https://hg.libsdl.org/SDL/file/bc90ce38f1e2/src/joystick/emscripten/SDL_sysjoystick.c#l385
	if len(i.gamepads) <= int(id) {
		return ""
	}
	var sdlid [16]byte
	copy(sdlid[:], []byte(i.gamepads[id].name))
	return hex.EncodeToString(sdlid[:])
}

// GamepadName returns a string containing some information about the controller.
// A PS2 controller returned "810-3-USB Gamepad" on Firefox
// A Xbox 360 controller returned "xinput" on Firefox and "Xbox 360 Controller (XInput STANDARD GAMEPAD)" on Chrome
func (i *Input) GamepadName(id driver.GamepadID) string {
	if len(i.gamepads) <= int(id) {
		return ""
	}
	return i.gamepads[id].name
}

func (i *Input) GamepadIDs() []driver.GamepadID {
	if len(i.gamepads) == 0 {
		return nil
	}
	var r []driver.GamepadID
	for id, g := range i.gamepads {
		if g.valid {
			r = append(r, driver.GamepadID(id))
		}
	}
	return r
}

func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
	if len(i.gamepads) <= int(id) {
		return 0
	}
	return i.gamepads[id].axisNum
}

func (i *Input) GamepadAxis(id driver.GamepadID, axis int) float64 {
	if len(i.gamepads) <= int(id) {
		return 0
	}
	return i.gamepads[id].axes[axis]
}

func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
	if len(i.gamepads) <= int(id) {
		return 0
	}
	return i.gamepads[id].buttonNum
}

func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
	if len(i.gamepads) <= int(id) {
		return false
	}
	return i.gamepads[id].buttonPressed[button]
}

func (i *Input) TouchIDs() []driver.TouchID {
	if len(i.touches) == 0 {
		return nil
	}

	var ids []driver.TouchID
	for id := range i.touches {
		ids = append(ids, id)
	}
	return ids
}

func (i *Input) TouchPosition(id driver.TouchID) (x, y int) {
	d := i.ui.DeviceScaleFactor()
	for tid, pos := range i.touches {
		if id == tid {
			x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d)
			return int(x), int(y)
		}
	}
	return 0, 0
}

func (i *Input) RuneBuffer() []rune {
	rs := make([]rune, len(i.runeBuffer))
	copy(rs, i.runeBuffer)
	return rs
}

func (i *Input) resetForFrame() {
	i.runeBuffer = nil
	i.wheelX = 0
	i.wheelY = 0
}

func (i *Input) IsKeyPressed(key driver.Key) bool {
	if i.keyPressed != nil {
		if i.keyPressed[jsKeyToID(driverKeyToJSKey[key])] {
			return true
		}
	}
	if i.keyPressedEdge != nil {
		for c, k := range edgeKeyCodeToDriverKey {
			if k != key {
				continue
			}
			if i.keyPressedEdge[c] {
				return true
			}
		}
	}
	return false
}

var codeToMouseButton = map[int]driver.MouseButton{
	0: driver.MouseButtonLeft,
	1: driver.MouseButtonMiddle,
	2: driver.MouseButtonRight,
}

func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	for c, b := range codeToMouseButton {
		if b != button {
			continue
		}
		if i.mouseButtonPressed[c] {
			return true
		}
	}
	return false
}

func (i *Input) Wheel() (xoff, yoff float64) {
	return i.wheelX, i.wheelY
}

func (i *Input) keyDown(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = true
}

func (i *Input) keyUp(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = false
}

func (i *Input) keyDownEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = true
}

func (i *Input) keyUpEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = false
}

func (i *Input) mouseDown(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = true
}

func (i *Input) mouseUp(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = false
}

func (i *Input) setMouseCursor(x, y int) {
	i.cursorX, i.cursorY = x, y
}

func (i *Input) updateGamepads() {
	nav := js.Global().Get("navigator")
	if !nav.Truthy() {
		return
	}
	if !nav.Get("getGamepads").Truthy() {
		return
	}
	gamepads := nav.Call("getGamepads")
	l := gamepads.Get("length").Int()
	for id := 0; id < l; id++ {
		i.gamepads[id].valid = false
		gamepad := gamepads.Index(id)
		if !gamepad.Truthy() {
			continue
		}
		i.gamepads[id].valid = true
		i.gamepads[id].name = gamepad.Get("id").String()

		axes := gamepad.Get("axes")
		axesNum := axes.Get("length").Int()
		i.gamepads[id].axisNum = axesNum
		for a := 0; a < len(i.gamepads[id].axes); a++ {
			if axesNum <= a {
				i.gamepads[id].axes[a] = 0
				continue
			}
			i.gamepads[id].axes[a] = axes.Index(a).Float()
		}

		buttons := gamepad.Get("buttons")
		buttonsNum := buttons.Get("length").Int()
		i.gamepads[id].buttonNum = buttonsNum
		for b := 0; b < len(i.gamepads[id].buttonPressed); b++ {
			if buttonsNum <= b {
				i.gamepads[id].buttonPressed[b] = false
				continue
			}
			i.gamepads[id].buttonPressed[b] = buttons.Index(b).Get("pressed").Bool()
		}
	}
}

func (i *Input) updateFromEvent(e js.Value) {
	// Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy
	// overhead (#1437).
	switch t := e.Get("type"); {
	case jsutil.Equal(t, stringKeydown):
		c := e.Get("code")
		if c.Type() != js.TypeString {
			code := e.Get("keyCode").Int()
			if edgeKeyCodeToDriverKey[code] == driver.KeyUp ||
				edgeKeyCodeToDriverKey[code] == driver.KeyDown ||
				edgeKeyCodeToDriverKey[code] == driver.KeyLeft ||
				edgeKeyCodeToDriverKey[code] == driver.KeyRight ||
				edgeKeyCodeToDriverKey[code] == driver.KeyBackspace ||
				edgeKeyCodeToDriverKey[code] == driver.KeyTab {
				e.Call("preventDefault")
			}
			i.keyDownEdge(code)
			return
		}
		if jsutil.Equal(c, driverKeyToJSKey[driver.KeyUp]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyDown]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyLeft]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyRight]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyBackspace]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyTab]) {
			e.Call("preventDefault")
		}
		i.keyDown(c)
	case jsutil.Equal(t, stringKeypress):
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			i.runeBuffer = append(i.runeBuffer, r)
		}
	case jsutil.Equal(t, stringKeyup):
		if e.Get("code").Type() != js.TypeString {
			// Assume that UA is Edge.
			code := e.Get("keyCode").Int()
			i.keyUpEdge(code)
			return
		}
		i.keyUp(e.Get("code"))
	case jsutil.Equal(t, stringMousedown):
		button := e.Get("button").Int()
		i.mouseDown(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMouseup):
		button := e.Get("button").Int()
		i.mouseUp(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMousemove):
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringWheel):
		// TODO: What if e.deltaMode is not DOM_DELTA_PIXEL?
		i.wheelX = -e.Get("deltaX").Float()
		i.wheelY = -e.Get("deltaY").Float()
	case jsutil.Equal(t, stringTouchstart) || jsutil.Equal(t, stringTouchend) || jsutil.Equal(t, stringTouchmove):
		i.updateTouchesFromEvent(e)
	}
}

func (i *Input) setMouseCursorFromEvent(e js.Value) {
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	i.setMouseCursor(x, y)
}

func (i *Input) updateTouchesFromEvent(e js.Value) {
	j := e.Get("targetTouches")
	ts := map[driver.TouchID]pos{}
	for i := 0; i < j.Length(); i++ {
		jj := j.Call("item", i)
		id := driver.TouchID(jj.Get("identifier").Int())
		ts[id] = pos{
			X: jj.Get("clientX").Int(),
			Y: jj.Get("clientY").Int(),
		}
	}
	i.touches = ts
}

func (i *Input) updateForGo2Cpp() {
	if !go2cpp.Truthy() {
		return
	}

	i.touches = map[driver.TouchID]pos{}
	maxID := go2cpp.Get("touchCount").Int()
	for idx := 0; idx < maxID; idx++ {
		id := go2cpp.Call("getTouchPositionId", idx)
		x := go2cpp.Call("getTouchPositionX", idx)
		y := go2cpp.Call("getTouchPositionY", idx)
		i.touches[driver.TouchID(id.Int())] = pos{
			X: x.Int(),
			Y: y.Int(),
		}
	}
}
<commit_msg>uidriver/js: Refactoring: Use a map for gamepad states<commit_after>// Copyright 2015 Hajime Hoshi
//
// 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 js

import (
	"encoding/hex"
	"syscall/js"
	"unicode"

	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/jsutil"
)

var (
	stringKeydown    = js.ValueOf("keydown")
	stringKeypress   = js.ValueOf("keypress")
	stringKeyup      = js.ValueOf("keyup")
	stringMousedown  = js.ValueOf("mousedown")
	stringMouseup    = js.ValueOf("mouseup")
	stringMousemove  = js.ValueOf("mousemove")
	stringWheel      = js.ValueOf("wheel")
	stringTouchstart = js.ValueOf("touchstart")
	stringTouchend   = js.ValueOf("touchend")
	stringTouchmove  = js.ValueOf("touchmove")
)

var jsKeys []js.Value

func init() {
	for _, k := range driverKeyToJSKey {
		jsKeys = append(jsKeys, k)
	}
}

func jsKeyToID(key js.Value) int {
	// js.Value cannot be used as a map key.
	// As the number of keys is around 100, just a dumb loop should work.
	for i, k := range jsKeys {
		if jsutil.Equal(k, key) {
			return i
		}
	}
	return -1
}

type pos struct {
	X int
	Y int
}

type gamepad struct {
	name          string
	axisNum       int
	axes          [16]float64
	buttonNum     int
	buttonPressed [256]bool
}

type Input struct {
	keyPressed         map[int]bool
	keyPressedEdge     map[int]bool
	mouseButtonPressed map[int]bool
	cursorX            int
	cursorY            int
	wheelX             float64
	wheelY             float64
	gamepads           map[driver.GamepadID]gamepad
	touches            map[driver.TouchID]pos
	runeBuffer         []rune
	ui                 *UserInterface
}

func (i *Input) CursorPosition() (x, y int) {
	xf, yf := i.ui.context.AdjustPosition(float64(i.cursorX), float64(i.cursorY), i.ui.DeviceScaleFactor())
	return int(xf), int(yf)
}

func (i *Input) GamepadSDLID(id driver.GamepadID) string {
	// This emulates the implementation of EMSCRIPTEN_JoystickGetDeviceGUID.
	// https://hg.libsdl.org/SDL/file/bc90ce38f1e2/src/joystick/emscripten/SDL_sysjoystick.c#l385
	if len(i.gamepads) <= int(id) {
		return ""
	}
	var sdlid [16]byte
	copy(sdlid[:], []byte(i.gamepads[id].name))
	return hex.EncodeToString(sdlid[:])
}

// GamepadName returns a string containing some information about the controller.
// A PS2 controller returned "810-3-USB Gamepad" on Firefox
// A Xbox 360 controller returned "xinput" on Firefox and "Xbox 360 Controller (XInput STANDARD GAMEPAD)" on Chrome
func (i *Input) GamepadName(id driver.GamepadID) string {
	if len(i.gamepads) <= int(id) {
		return ""
	}
	return i.gamepads[id].name
}

func (i *Input) GamepadIDs() []driver.GamepadID {
	if len(i.gamepads) == 0 {
		return nil
	}

	var r []driver.GamepadID
	for id := range i.gamepads {
		r = append(r, id)
	}
	return r
}

func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.axisNum
}

func (i *Input) GamepadAxis(id driver.GamepadID, axis int) float64 {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	if g.axisNum <= axis {
		return 0
	}
	return g.axes[axis]
}

func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
	g, ok := i.gamepads[id]
	if !ok {
		return 0
	}
	return g.buttonNum
}

func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
	g, ok := i.gamepads[id]
	if !ok {
		return false
	}
	if g.buttonNum <= int(button) {
		return false
	}
	return g.buttonPressed[button]
}

func (i *Input) TouchIDs() []driver.TouchID {
	if len(i.touches) == 0 {
		return nil
	}

	var ids []driver.TouchID
	for id := range i.touches {
		ids = append(ids, id)
	}
	return ids
}

func (i *Input) TouchPosition(id driver.TouchID) (x, y int) {
	d := i.ui.DeviceScaleFactor()
	for tid, pos := range i.touches {
		if id == tid {
			x, y := i.ui.context.AdjustPosition(float64(pos.X), float64(pos.Y), d)
			return int(x), int(y)
		}
	}
	return 0, 0
}

func (i *Input) RuneBuffer() []rune {
	rs := make([]rune, len(i.runeBuffer))
	copy(rs, i.runeBuffer)
	return rs
}

func (i *Input) resetForFrame() {
	i.runeBuffer = nil
	i.wheelX = 0
	i.wheelY = 0
}

func (i *Input) IsKeyPressed(key driver.Key) bool {
	if i.keyPressed != nil {
		if i.keyPressed[jsKeyToID(driverKeyToJSKey[key])] {
			return true
		}
	}
	if i.keyPressedEdge != nil {
		for c, k := range edgeKeyCodeToDriverKey {
			if k != key {
				continue
			}
			if i.keyPressedEdge[c] {
				return true
			}
		}
	}
	return false
}

var codeToMouseButton = map[int]driver.MouseButton{
	0: driver.MouseButtonLeft,
	1: driver.MouseButtonMiddle,
	2: driver.MouseButtonRight,
}

func (i *Input) IsMouseButtonPressed(button driver.MouseButton) bool {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	for c, b := range codeToMouseButton {
		if b != button {
			continue
		}
		if i.mouseButtonPressed[c] {
			return true
		}
	}
	return false
}

func (i *Input) Wheel() (xoff, yoff float64) {
	return i.wheelX, i.wheelY
}

func (i *Input) keyDown(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = true
}

func (i *Input) keyUp(code js.Value) {
	if i.keyPressed == nil {
		i.keyPressed = map[int]bool{}
	}
	i.keyPressed[jsKeyToID(code)] = false
}

func (i *Input) keyDownEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = true
}

func (i *Input) keyUpEdge(code int) {
	if i.keyPressedEdge == nil {
		i.keyPressedEdge = map[int]bool{}
	}
	i.keyPressedEdge[code] = false
}

func (i *Input) mouseDown(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = true
}

func (i *Input) mouseUp(code int) {
	if i.mouseButtonPressed == nil {
		i.mouseButtonPressed = map[int]bool{}
	}
	i.mouseButtonPressed[code] = false
}

func (i *Input) setMouseCursor(x, y int) {
	i.cursorX, i.cursorY = x, y
}

func (i *Input) updateGamepads() {
	nav := js.Global().Get("navigator")
	if !nav.Truthy() {
		return
	}

	if !nav.Get("getGamepads").Truthy() {
		return
	}

	i.gamepads = map[driver.GamepadID]gamepad{}

	gamepads := nav.Call("getGamepads")
	l := gamepads.Length()
	for idx := 0; idx < l; idx++ {
		gp := gamepads.Index(idx)
		if !gp.Truthy() {
			continue
		}

		id := driver.GamepadID(gp.Get("index").Int())
		g := gamepad{}
		g.name = gp.Get("id").String()

		axes := gp.Get("axes")
		axesNum := axes.Get("length").Int()
		g.axisNum = axesNum
		for a := 0; a < len(g.axes); a++ {
			if axesNum <= a {
				break
			}
			g.axes[a] = axes.Index(a).Float()
		}

		buttons := gp.Get("buttons")
		buttonsNum := buttons.Get("length").Int()
		g.buttonNum = buttonsNum
		for b := 0; b < len(g.buttonPressed); b++ {
			if buttonsNum <= b {
				break
			}
			g.buttonPressed[b] = buttons.Index(b).Get("pressed").Bool()
		}

		i.gamepads[id] = g
	}
}

func (i *Input) updateFromEvent(e js.Value) {
	// Avoid using js.Value.String() as String creates a Uint8Array via a TextEncoder and causes a heavy
	// overhead (#1437).
	switch t := e.Get("type"); {
	case jsutil.Equal(t, stringKeydown):
		c := e.Get("code")
		if c.Type() != js.TypeString {
			code := e.Get("keyCode").Int()
			if edgeKeyCodeToDriverKey[code] == driver.KeyUp ||
				edgeKeyCodeToDriverKey[code] == driver.KeyDown ||
				edgeKeyCodeToDriverKey[code] == driver.KeyLeft ||
				edgeKeyCodeToDriverKey[code] == driver.KeyRight ||
				edgeKeyCodeToDriverKey[code] == driver.KeyBackspace ||
				edgeKeyCodeToDriverKey[code] == driver.KeyTab {
				e.Call("preventDefault")
			}
			i.keyDownEdge(code)
			return
		}
		if jsutil.Equal(c, driverKeyToJSKey[driver.KeyUp]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyDown]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyLeft]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyRight]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyBackspace]) ||
			jsutil.Equal(c, driverKeyToJSKey[driver.KeyTab]) {
			e.Call("preventDefault")
		}
		i.keyDown(c)
	case jsutil.Equal(t, stringKeypress):
		if r := rune(e.Get("charCode").Int()); unicode.IsPrint(r) {
			i.runeBuffer = append(i.runeBuffer, r)
		}
	case jsutil.Equal(t, stringKeyup):
		if e.Get("code").Type() != js.TypeString {
			// Assume that UA is Edge.
			code := e.Get("keyCode").Int()
			i.keyUpEdge(code)
			return
		}
		i.keyUp(e.Get("code"))
	case jsutil.Equal(t, stringMousedown):
		button := e.Get("button").Int()
		i.mouseDown(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMouseup):
		button := e.Get("button").Int()
		i.mouseUp(button)
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringMousemove):
		i.setMouseCursorFromEvent(e)
	case jsutil.Equal(t, stringWheel):
		// TODO: What if e.deltaMode is not DOM_DELTA_PIXEL?
		i.wheelX = -e.Get("deltaX").Float()
		i.wheelY = -e.Get("deltaY").Float()
	case jsutil.Equal(t, stringTouchstart) || jsutil.Equal(t, stringTouchend) || jsutil.Equal(t, stringTouchmove):
		i.updateTouchesFromEvent(e)
	}
}

func (i *Input) setMouseCursorFromEvent(e js.Value) {
	x, y := e.Get("clientX").Int(), e.Get("clientY").Int()
	i.setMouseCursor(x, y)
}

func (i *Input) updateTouchesFromEvent(e js.Value) {
	j := e.Get("targetTouches")
	ts := map[driver.TouchID]pos{}
	for i := 0; i < j.Length(); i++ {
		jj := j.Call("item", i)
		id := driver.TouchID(jj.Get("identifier").Int())
		ts[id] = pos{
			X: jj.Get("clientX").Int(),
			Y: jj.Get("clientY").Int(),
		}
	}
	i.touches = ts
}

func (i *Input) updateForGo2Cpp() {
	if !go2cpp.Truthy() {
		return
	}

	i.touches = map[driver.TouchID]pos{}
	touchCount := go2cpp.Get("touchCount").Int()
	for idx := 0; idx < touchCount; idx++ {
		id := go2cpp.Call("getTouchPositionId", idx)
		x := go2cpp.Call("getTouchPositionX", idx)
		y := go2cpp.Call("getTouchPositionY", idx)
		i.touches[driver.TouchID(id.Int())] = pos{
			X: x.Int(),
			Y: y.Int(),
		}
	}
}
<|endoftext|>


<commit_before>package git

import (
	"bytes"
	"strings"

	"github.com/polydawn/gosh"
	"github.com/spacemonkeygo/errors"

	"polydawn.net/repeatr/io"
)

/*
	Refer to, interact with, and manage a warehouse.

	Since this is git we're talking about, this is basically references
	to another git repo.
*/
type Warehouse struct {
	url string
}

/*
	Initialize a warehouse controller.

	Note: parsing git URLs is *hard*.  This function is "best effort"
	stuff and may not be able to error in all circumstances where the
	git commands will error.  See `man gitremote-helpers` for one reason
	this is the edge of a tarpit.  (This may improve as we do a better job
	of pinning specific git versions and sandboxing their environment,
	but at the moment, caveat emptor, and this is "PRs welcome" turf.)

	May panic with:
	  - Config Error: if the URI is unparsable or has an unsupported scheme.
*/
func NewWarehouse(coords integrity.SiloURI) *Warehouse {
	wh := &Warehouse{}
	// TODO we currently don't parse the URL at all, actually.
	// `url.Parse` could be made to apply, but there's really nothing we
	//  can explicitly blacklist, and we also don't internally need to
	//   do any mode-switches here (git is already and always CAS).
	wh.url = string(coords)
	return wh
}

/*
	Check if the warehouse exists and can be contacted.

	Returns nil if contactable; if an error, the message will be
	an end-user-meaningful description of why the warehouse is out of reach.
*/
func (wh *Warehouse) Ping() *errors.Error {
	// Shell out to git and ask it if it thinks there's a repo here.
	// TODO this and all future shellouts does NOT SUFFICIENTLY ISOLATE either config or secret keeping yet.
	// TODO there's no "--" in ls-remote, so... we should forbid things starting in "-", i guess?
	//  or use "file://" religiously?  but no, bc ssh doesn't look like "ssh://" all the time... ugh, i do not want to write a git url parser
	//   update: yeah, using "file://" religiously is not an option.  this actually takes a *different* path than `/non/protocol/prefixed`.  not significantly, but it may impact e.g. hardlinking, iiuc
	// TODO jesus christ it's a lion get in the car: `git ls-remote .` and `./` are a special case and return results even if the repo root is *above* your cwd, even though git clone will disagree for obvious reasons.
	// TODO but wait there's more: call now, and you'll discover that `git ls-remote ../sibling` works fine (and so does clone), but if you do it from *inside* the same repo, it just says no-such-repo (but clone still works!)
	// so tl;dr literally everything about url validation in git is completely batshit insane.  ESPECIALLY around local file paths.

	var errBuf bytes.Buffer
	code := git.Bake(
		"ls-remote", wh.url,
		gosh.Opts{
			// never buffer stdout; it may be long and we don't care.
			Err:    &errBuf,
			OkExit: gosh.AnyExit,
		},
	).Run().GetExitCode()
	switch code {
	case 0:
		return nil
	case 128:
		// Code 128 appears to result from any cant-fetch scenario.
		// So far, we've also only seen error messages where the first line
		//  is interesting, so that's what we report.
		msg := strings.TrimPrefix(strings.SplitN(errBuf.String(), "\n", 2)[0], "fatal: ")
		// Known values include:
		//  - "'%s' does not appear to be a git repository"
		//  - "attempt to fetch/clone from a shallow repository"
		return integrity.WarehouseUnavailableError.New("git remote unavailable: %s", msg).(*errors.Error)
	default:
		// We don't recognize this.
		panic(integrity.UnknownError.New("git exit code %d (stderr: %s)", code, errBuf.String()))
	}
}
<commit_msg>Discovered another way git-ls-remote flaps.<commit_after>package git

import (
	"bytes"
	"strings"

	"github.com/polydawn/gosh"
	"github.com/spacemonkeygo/errors"

	"polydawn.net/repeatr/io"
)

/*
	Refer to, interact with, and manage a warehouse.

	Since this is git we're talking about, this is basically references
	to another git repo.
*/
type Warehouse struct {
	url string
}

/*
	Initialize a warehouse controller.

	Note: parsing git URLs is *hard*.  This function is "best effort"
	stuff and may not be able to error in all circumstances where the
	git commands will error.  See `man gitremote-helpers` for one reason
	this is the edge of a tarpit.  (This may improve as we do a better job
	of pinning specific git versions and sandboxing their environment,
	but at the moment, caveat emptor, and this is "PRs welcome" turf.)

	May panic with:
	  - Config Error: if the URI is unparsable or has an unsupported scheme.
*/
func NewWarehouse(coords integrity.SiloURI) *Warehouse {
	wh := &Warehouse{}
	// TODO we currently don't parse the URL at all, actually.
	// `url.Parse` could be made to apply, but there's really nothing we
	//  can explicitly blacklist, and we also don't internally need to
	//   do any mode-switches here (git is already and always CAS).
	wh.url = string(coords)
	return wh
}

/*
	Check if the warehouse exists and can be contacted.

	Returns nil if contactable; if an error, the message will be
	an end-user-meaningful description of why the warehouse is out of reach.
*/
func (wh *Warehouse) Ping() *errors.Error {
	// Shell out to git and ask it if it thinks there's a repo here.
	// TODO this and all future shellouts does NOT SUFFICIENTLY ISOLATE either config or secret keeping yet.
	// TODO there's no "--" in ls-remote, so... we should forbid things starting in "-", i guess?
	//  or use "file://" religiously?  but no, bc ssh doesn't look like "ssh://" all the time... ugh, i do not want to write a git url parser
	//   update: yeah, using "file://" religiously is not an option.  this actually takes a *different* path than `/non/protocol/prefixed`.  not significantly, but it may impact e.g. hardlinking, iiuc
	// TODO jesus christ it's a lion get in the car: `git ls-remote .` and `./` are a special case and return results even if the repo root is *above* your cwd, even though git clone will disagree for obvious reasons.
	// TODO but wait there's more: call now, and you'll discover that `git ls-remote ../sibling` works fine (and so does clone), but if you do it from *inside* the same repo, it just says no-such-repo (but clone still works!)
	// TODO just take notes on the fine quality now: if you make another git repo-b *inside* git-repo-a, then a dir inside of repo-b, then cd there, then `git ls-remote ../` you'll get the remotes from **repo-a**, the grandparent.  yessss.
	// so tl;dr literally everything about url validation in git is completely batshit insane.  ESPECIALLY around local file paths.

	var errBuf bytes.Buffer
	code := git.Bake(
		"ls-remote", wh.url,
		gosh.Opts{
			// never buffer stdout; it may be long and we don't care.
			Err:    &errBuf,
			OkExit: gosh.AnyExit,
		},
	).Run().GetExitCode()
	switch code {
	case 0:
		return nil
	case 128:
		// Code 128 appears to result from any cant-fetch scenario.
		// So far, we've also only seen error messages where the first line
		//  is interesting, so that's what we report.
		msg := strings.TrimPrefix(strings.SplitN(errBuf.String(), "\n", 2)[0], "fatal: ")
		// Known values include:
		//  - "'%s' does not appear to be a git repository"
		//  - "attempt to fetch/clone from a shallow repository"
		return integrity.WarehouseUnavailableError.New("git remote unavailable: %s", msg).(*errors.Error)
	default:
		// We don't recognize this.
		panic(integrity.UnknownError.New("git exit code %d (stderr: %s)", code, errBuf.String()))
	}
}
<|endoftext|>


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

package ledcli

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/maruel/subcommands"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/flag/stringmapflag"

	"go.chromium.org/luci/led/job"
	"go.chromium.org/luci/led/ledcmd"
)

func editRecipeBundleCmd(opts cmdBaseOptions) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: "edit-recipe-bundle [-O project_id=/path/to/local/repo]*",
		ShortDesc: "isolates recipes and adds them to a JobDefinition",
		LongDesc: `Takes recipes from the current repo (based on cwd), along with
any supplied overrides, and pushes them to the isolate service. The CAS digest
for the recipes will be added to the JobDefinition. If the -property-only flag
is passed or the builder has the "led_builder_is_bootstrapped" property set to
true, the "led_cas_recipe_bundle" will be set with the CAS digest so that the
build's bootstrapper executable can launch the bundled recipes.

Isolating recipes takes a bit of time, so you may want to save the result
of this command (stdout) to an intermediate file for quick edits.
`,

		CommandRun: func() subcommands.CommandRun {
			ret := &cmdEditRecipeBundle{}
			ret.initFlags(opts)
			return ret
		},
	}
}

type cmdEditRecipeBundle struct {
	cmdBase

	debugSleep time.Duration

	propertyOnly bool

	overrides stringmapflag.Value
}

func (c *cmdEditRecipeBundle) initFlags(opts cmdBaseOptions) {
	c.Flags.Var(&c.overrides, "O",
		"(repeatable) override a repo dependency. Takes a parameter of `project_id=/path/to/local/repo`.")

	c.Flags.DurationVar(&c.debugSleep, "debug-sleep", 0,
		"Injects an extra 'sleep' time into the recipe shim which will sleep for the "+
			"designated amount of time after the recipe completes to allow SSH "+
			"debugging of failed recipe state. This accepts a duration like `2h`. "+
			"Valid units are 's', 'm', or 'h'.")

	c.Flags.BoolVar(&c.propertyOnly, "property-only", false,
		fmt.Sprintf("Pass the CAS blob information as JSON via the %q property and "+
			"preserve the executable of the input job rather than overwriting it. This "+
			"is useful for when `exe` is actually a bootstrap program that you don't "+
			"want to change. The same behavior can be enabled for a build without this "+
			"flag by setting the \"led_builder_is_bootstrapped\" property to true.",
			ledcmd.CASRecipeBundleProperty))

	c.cmdBase.initFlags(opts)
}

func (c *cmdEditRecipeBundle) jobInput() bool                  { return true }
func (c *cmdEditRecipeBundle) positionalRange() (min, max int) { return 0, 0 }

func (c *cmdEditRecipeBundle) validateFlags(ctx context.Context, _ []string, _ subcommands.Env) (err error) {
	for k, v := range c.overrides {
		if k == "" {
			return errors.New("override has empty project_id")
		}
		if v == "" {
			return errors.Reason("override %q has empty repo path", k).Err()
		}
		v, err = filepath.Abs(v)
		if err != nil {
			return errors.Annotate(err, "override %q", k).Err()
		}
		c.overrides[k] = v

		var fi os.FileInfo
		switch fi, err = os.Stat(v); {
		case err != nil:
			return errors.Annotate(err, "override %q", k).Err()
		case !fi.IsDir():
			return errors.Reason("override %q: not a directory", k).Err()
		}
	}

	switch {
	case c.debugSleep == 0:
		// OK

	case c.debugSleep < 0:
		return errors.Reason(
			"-debug-sleep %q: duration may not be negative", c.debugSleep).Err()

	case c.debugSleep < 10*time.Minute:
		return errors.Reason(
			"-debug-sleep %q: duration is less than 10 minutes... are you sure you want that?",
			c.debugSleep).Err()
	}

	return
}

func (c *cmdEditRecipeBundle) execute(ctx context.Context, authClient *http.Client, authOpts auth.Options, inJob *job.Definition) (out interface{}, err error) {
	return inJob, ledcmd.EditRecipeBundle(ctx, authClient, authOpts, inJob, &ledcmd.EditRecipeBundleOpts{
		Overrides:    c.overrides,
		DebugSleep:   c.debugSleep,
		PropertyOnly: c.propertyOnly,
	})
}

func (c *cmdEditRecipeBundle) Run(a subcommands.Application, args []string, env subcommands.Env) int {
	return c.doContextExecute(a, c, args, env)
}
<commit_msg>Add a missing word to edit-recipe-bundle help text.<commit_after>// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ledcli

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/maruel/subcommands"

	"go.chromium.org/luci/auth"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/flag/stringmapflag"

	"go.chromium.org/luci/led/job"
	"go.chromium.org/luci/led/ledcmd"
)

func editRecipeBundleCmd(opts cmdBaseOptions) *subcommands.Command {
	return &subcommands.Command{
		UsageLine: "edit-recipe-bundle [-O project_id=/path/to/local/repo]*",
		ShortDesc: "isolates recipes and adds them to a JobDefinition",
		LongDesc: `Takes recipes from the current repo (based on cwd), along with
any supplied overrides, and pushes them to the isolate service. The CAS digest
for the recipes will be added to the JobDefinition. If the -property-only flag
is passed or the builder has the "led_builder_is_bootstrapped" property set to
true, the "led_cas_recipe_bundle" property will be set with the CAS digest so
that the build's bootstrapper executable can launch the bundled recipes.

Isolating recipes takes a bit of time, so you may want to save the result
of this command (stdout) to an intermediate file for quick edits.
`,

		CommandRun: func() subcommands.CommandRun {
			ret := &cmdEditRecipeBundle{}
			ret.initFlags(opts)
			return ret
		},
	}
}

type cmdEditRecipeBundle struct {
	cmdBase

	debugSleep time.Duration

	propertyOnly bool

	overrides stringmapflag.Value
}

func (c *cmdEditRecipeBundle) initFlags(opts cmdBaseOptions) {
	c.Flags.Var(&c.overrides, "O",
		"(repeatable) override a repo dependency. Takes a parameter of `project_id=/path/to/local/repo`.")

	c.Flags.DurationVar(&c.debugSleep, "debug-sleep", 0,
		"Injects an extra 'sleep' time into the recipe shim which will sleep for the "+
			"designated amount of time after the recipe completes to allow SSH "+
			"debugging of failed recipe state. This accepts a duration like `2h`. "+
			"Valid units are 's', 'm', or 'h'.")

	c.Flags.BoolVar(&c.propertyOnly, "property-only", false,
		fmt.Sprintf("Pass the CAS blob information as JSON via the %q property and "+
			"preserve the executable of the input job rather than overwriting it. This "+
			"is useful for when `exe` is actually a bootstrap program that you don't "+
			"want to change. The same behavior can be enabled for a build without this "+
			"flag by setting the \"led_builder_is_bootstrapped\" property to true.",
			ledcmd.CASRecipeBundleProperty))

	c.cmdBase.initFlags(opts)
}

func (c *cmdEditRecipeBundle) jobInput() bool                  { return true }
func (c *cmdEditRecipeBundle) positionalRange() (min, max int) { return 0, 0 }

func (c *cmdEditRecipeBundle) validateFlags(ctx context.Context, _ []string, _ subcommands.Env) (err error) {
	for k, v := range c.overrides {
		if k == "" {
			return errors.New("override has empty project_id")
		}
		if v == "" {
			return errors.Reason("override %q has empty repo path", k).Err()
		}
		v, err = filepath.Abs(v)
		if err != nil {
			return errors.Annotate(err, "override %q", k).Err()
		}
		c.overrides[k] = v

		var fi os.FileInfo
		switch fi, err = os.Stat(v); {
		case err != nil:
			return errors.Annotate(err, "override %q", k).Err()
		case !fi.IsDir():
			return errors.Reason("override %q: not a directory", k).Err()
		}
	}

	switch {
	case c.debugSleep == 0:
		// OK

	case c.debugSleep < 0:
		return errors.Reason(
			"-debug-sleep %q: duration may not be negative", c.debugSleep).Err()

	case c.debugSleep < 10*time.Minute:
		return errors.Reason(
			"-debug-sleep %q: duration is less than 10 minutes... are you sure you want that?",
			c.debugSleep).Err()
	}

	return
}

func (c *cmdEditRecipeBundle) execute(ctx context.Context, authClient *http.Client, authOpts auth.Options, inJob *job.Definition) (out interface{}, err error) {
	return inJob, ledcmd.EditRecipeBundle(ctx, authClient, authOpts, inJob, &ledcmd.EditRecipeBundleOpts{
		Overrides:    c.overrides,
		DebugSleep:   c.debugSleep,
		PropertyOnly: c.propertyOnly,
	})
}

func (c *cmdEditRecipeBundle) Run(a subcommands.Application, args []string, env subcommands.Env) int {
	return c.doContextExecute(a, c, args, env)
}
<|endoftext|>


<commit_before>/* https://leetcode.com/problems/valid-perfect-square/#/description
Given a positive integer num, write a function which returns True if num is a perfect square else False.

Note: Do not use any built-in library function such as sqrt.

Example 1:
    Input: 16
    Returns: True

Example 2:
    Input: 14
    Returns: False
*/

package leetcode

func isPerfectSquare(num int) bool {
	return true
}
<commit_msg>add isPerfectSquare<commit_after>/* https://leetcode.com/problems/valid-perfect-square/#/description
Given a positive integer num, write a function which returns True if num is a perfect square else False.

Note: Do not use any built-in library function such as sqrt.

Example 1:
    Input: 16
    Returns: True

Example 2:
    Input: 14
    Returns: False

1 = 1
4 = 1 + 3
9 = 1 + 3 + 5
16 = 1 + 3 + 5 + 7
25 = 1 + 3 + 5 + 7 + 9
36 = 1 + 3 + 5 + 7 + 9 + 11
....
1+3+...+(2n-1) = (2n-1 + 1)n/2 = n*n
*/

package leetcode

func isPerfectSquare(num int) bool {
	i := 1
	for num > 0 {
		num -= i
		i += 2
	}
	return num == 0
}
<|endoftext|>


<commit_before>package annotation

import (
	"fmt"
	"os"

	"math"
	"sort"

	"github.com/codegangsta/cli"
	"github.com/mattn/go-tty"
	"github.com/pkg/browser"
	"github.com/syou6162/go-active-learning/lib/cache"
	"github.com/syou6162/go-active-learning/lib/classifier"
	"github.com/syou6162/go-active-learning/lib/db"
	"github.com/syou6162/go-active-learning/lib/example"
	"github.com/syou6162/go-active-learning/lib/util"
)

func input2ActionType() (ActionType, error) {
	t, err := tty.Open()
	defer t.Close()
	if err != nil {
		return EXIT, err
	}
	var r rune
	for r == 0 {
		r, err = t.ReadRune()
		if err != nil {
			return HELP, err
		}
	}
	return rune2ActionType(r), nil
}

func doAnnotate(c *cli.Context) error {
	openUrl := c.Bool("open-url")
	filterStatusCodeOk := c.Bool("filter-status-code-ok")
	showActiveFeatures := c.Bool("show-active-features")

	cache, err := cache.NewCache()
	if err != nil {
		return err
	}
	defer cache.Close()

	conn, err := db.CreateDBConnection()
	if err != nil {
		return err
	}
	defer conn.Close()

	examples, err := db.ReadExamples(conn)
	if err != nil {
		return err
	}

	stat := example.GetStat(examples)
	fmt.Fprintln(os.Stderr, fmt.Sprintf("Positive:%d, Negative:%d, Unlabeled:%d", stat["positive"], stat["negative"], stat["unlabeled"]))

	cache.AttachMetaData(examples)
	if filterStatusCodeOk {
		examples = util.FilterStatusCodeOkExamples(examples)
	}
	model := classifier.NewBinaryClassifier(examples)

annotationLoop:
	for {
		e := NextExampleToBeAnnotated(model, examples)
		fmt.Println("Label this example (Score: " + fmt.Sprintf("%+0.03f", e.Score) + "): " + e.Url + " (" + e.Title + ")")

		if openUrl {
			browser.OpenURL(e.Url)
		}
		if showActiveFeatures {
			ShowActiveFeatures(model, *e, 5)
		}

		act, err := input2ActionType()
		if err != nil {
			return err
		}
		switch act {
		case LABEL_AS_POSITIVE:
			fmt.Println("Labeled as positive")
			e.Annotate(example.POSITIVE)
			db.InsertOrUpdateExample(conn, e)
		case LABEL_AS_NEGATIVE:
			fmt.Println("Labeled as negative")
			e.Annotate(example.NEGATIVE)
			db.InsertOrUpdateExample(conn, e)
		case SKIP:
			fmt.Println("Skiped this example")
			examples = util.RemoveExample(examples, *e)
			continue
		case HELP:
			fmt.Println(ActionHelpDoc)
		case EXIT:
			fmt.Println("EXIT")
			break annotationLoop
		default:
			break annotationLoop
		}
		model = classifier.NewBinaryClassifier(examples)
	}

	return nil
}

type FeatureWeightPair struct {
	Feature string
	Weight  float64
}

type FeatureWeightPairs []FeatureWeightPair

func SortedActiveFeatures(model classifier.BinaryClassifier, example example.Example, n int) FeatureWeightPairs {
	pairs := FeatureWeightPairs{}
	for _, f := range example.Fv {
		pairs = append(pairs, FeatureWeightPair{f, model.GetWeight(f)})
	}
	sort.Sort(sort.Reverse(pairs))

	result := FeatureWeightPairs{}
	cnt := 0
	for _, pair := range pairs {
		if cnt >= n {
			break
		}
		if (example.Score > 0.0 && pair.Weight > 0.0) || (example.Score < 0.0 && pair.Weight < 0.0) {
			result = append(result, pair)
			cnt++
		}
	}
	return result
}

func ShowActiveFeatures(model classifier.BinaryClassifier, example example.Example, n int) {
	for _, pair := range SortedActiveFeatures(model, example, n) {
		fmt.Println(fmt.Sprintf("%+0.1f %s", pair.Weight, pair.Feature))
	}
}

func (slice FeatureWeightPairs) Len() int {
	return len(slice)
}

func (slice FeatureWeightPairs) Less(i, j int) bool {
	return math.Abs(slice[i].Weight) < math.Abs(slice[j].Weight)
}

func (slice FeatureWeightPairs) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}
<commit_msg>Break when annotation has finished<commit_after>package annotation

import (
	"fmt"
	"os"

	"math"
	"sort"

	"github.com/codegangsta/cli"
	"github.com/mattn/go-tty"
	"github.com/pkg/browser"
	"github.com/syou6162/go-active-learning/lib/cache"
	"github.com/syou6162/go-active-learning/lib/classifier"
	"github.com/syou6162/go-active-learning/lib/db"
	"github.com/syou6162/go-active-learning/lib/example"
	"github.com/syou6162/go-active-learning/lib/util"
)

func input2ActionType() (ActionType, error) {
	t, err := tty.Open()
	defer t.Close()
	if err != nil {
		return EXIT, err
	}
	var r rune
	for r == 0 {
		r, err = t.ReadRune()
		if err != nil {
			return HELP, err
		}
	}
	return rune2ActionType(r), nil
}

func doAnnotate(c *cli.Context) error {
	openUrl := c.Bool("open-url")
	filterStatusCodeOk := c.Bool("filter-status-code-ok")
	showActiveFeatures := c.Bool("show-active-features")

	cache, err := cache.NewCache()
	if err != nil {
		return err
	}
	defer cache.Close()

	conn, err := db.CreateDBConnection()
	if err != nil {
		return err
	}
	defer conn.Close()

	examples, err := db.ReadExamples(conn)
	if err != nil {
		return err
	}

	stat := example.GetStat(examples)
	fmt.Fprintln(os.Stderr, fmt.Sprintf("Positive:%d, Negative:%d, Unlabeled:%d", stat["positive"], stat["negative"], stat["unlabeled"]))

	cache.AttachMetaData(examples)
	if filterStatusCodeOk {
		examples = util.FilterStatusCodeOkExamples(examples)
	}
	model := classifier.NewBinaryClassifier(examples)

annotationLoop:
	for {
		e := NextExampleToBeAnnotated(model, examples)
		if e == nil {
			fmt.Println("No example")
			break annotationLoop
		}
		fmt.Println("Label this example (Score: " + fmt.Sprintf("%+0.03f", e.Score) + "): " + e.Url + " (" + e.Title + ")")

		if openUrl {
			browser.OpenURL(e.Url)
		}
		if showActiveFeatures {
			ShowActiveFeatures(model, *e, 5)
		}

		act, err := input2ActionType()
		if err != nil {
			return err
		}
		switch act {
		case LABEL_AS_POSITIVE:
			fmt.Println("Labeled as positive")
			e.Annotate(example.POSITIVE)
			db.InsertOrUpdateExample(conn, e)
		case LABEL_AS_NEGATIVE:
			fmt.Println("Labeled as negative")
			e.Annotate(example.NEGATIVE)
			db.InsertOrUpdateExample(conn, e)
		case SKIP:
			fmt.Println("Skiped this example")
			examples = util.RemoveExample(examples, *e)
			continue
		case HELP:
			fmt.Println(ActionHelpDoc)
		case EXIT:
			fmt.Println("EXIT")
			break annotationLoop
		default:
			break annotationLoop
		}
		model = classifier.NewBinaryClassifier(examples)
	}

	return nil
}

type FeatureWeightPair struct {
	Feature string
	Weight  float64
}

type FeatureWeightPairs []FeatureWeightPair

func SortedActiveFeatures(model classifier.BinaryClassifier, example example.Example, n int) FeatureWeightPairs {
	pairs := FeatureWeightPairs{}
	for _, f := range example.Fv {
		pairs = append(pairs, FeatureWeightPair{f, model.GetWeight(f)})
	}
	sort.Sort(sort.Reverse(pairs))

	result := FeatureWeightPairs{}
	cnt := 0
	for _, pair := range pairs {
		if cnt >= n {
			break
		}
		if (example.Score > 0.0 && pair.Weight > 0.0) || (example.Score < 0.0 && pair.Weight < 0.0) {
			result = append(result, pair)
			cnt++
		}
	}
	return result
}

func ShowActiveFeatures(model classifier.BinaryClassifier, example example.Example, n int) {
	for _, pair := range SortedActiveFeatures(model, example, n) {
		fmt.Println(fmt.Sprintf("%+0.1f %s", pair.Weight, pair.Feature))
	}
}

func (slice FeatureWeightPairs) Len() int {
	return len(slice)
}

func (slice FeatureWeightPairs) Less(i, j int) bool {
	return math.Abs(slice[i].Weight) < math.Abs(slice[j].Weight)
}

func (slice FeatureWeightPairs) Swap(i, j int) {
	slice[i], slice[j] = slice[j], slice[i]
}
<|endoftext|>


<commit_before>package meow-ml-kmeans

import (
	"math"
	"ashumeow/ml-kmeans/meow-ml-kmeans/meow-data-structures"
	"ashumeow/ml-kmeans/meow-ml-kmeans/meow-ml"
)<commit_msg>add type<commit_after>package meow-ml-kmeans

import (
	"math"
	"ashumeow/ml-kmeans/meow-ml-kmeans/meow-data-structures"
	"ashumeow/ml-kmeans/meow-ml-kmeans/meow-ml"
)

type meowKmeans struct {
	dots *meow-data-structures.meowArrayList
	num_of_clusters int
	meowta float64
}<|endoftext|>


<commit_before>// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux,!appengine darwin freebsd openbsd

package term

import (
	"unsafe"

	"golang.org/x/sys/unix"
)

// IsTty returns true if the given file descriptor is a terminal.
func IsTty(fd uintptr) bool {
	var termios Termios
	_, _, err := unix.Syscall6(unix.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0)
	return err == 0
}
<commit_msg>shared/log15: Fix due to recent unix change<commit_after>// Based on ssh/terminal:
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build linux,!appengine darwin freebsd openbsd

package term

import (
	"golang.org/x/sys/unix"
)

// IsTty returns true if the given file descriptor is a terminal.
func IsTty(fd uintptr) bool {
	_, err := unix.IoctlGetTermios(int(fd), ioctlReadTermios)
	return err == nil
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"strings"
)

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func helloworld(args []string) {
	print("Hello world from Go\n")
}

func echo(args []string) {
	if len(args) > 0 {
		for _, arg := range args {
			println(arg)
		}
	}
}

func reverse(args []string) {
	bytes, err := ioutil.ReadAll(os.Stdin)
	assert(err)
	runes := []rune(strings.Trim(string(bytes), "\n"))
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	println(string(runes))
}

func main() {
	bash := basher.NewContext()
	bash.ExportFunc("helloworld", helloworld)
	bash.ExportFunc("go-echo", echo)
	bash.ExportFunc("reverse", reverse)
	bash.HandleFuncs()

	bash.Source("./example.bash")
	status, err := bash.Run("main", os.Args)
	assert(err)
	os.Exit(status)
}
<commit_msg>use lib<commit_after>package main

import (
	"log"
	"os"
	"strings"

	"github.com/progrium/go-basher"
)

func assert(err error) {
	if err != nil {
		log.Fatal(err)
	}
}

func helloworld(args []string) {
	print("Hello world from Go\n")
}

func echo(args []string) {
	if len(args) > 0 {
		for _, arg := range args {
			println(arg)
		}
	}
}

func reverse(args []string) {
	bytes, err := ioutil.ReadAll(os.Stdin)
	assert(err)
	runes := []rune(strings.Trim(string(bytes), "\n"))
	for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
		runes[i], runes[j] = runes[j], runes[i]
	}
	println(string(runes))
}

func main() {
	bash := basher.NewContext()
	bash.ExportFunc("helloworld", helloworld)
	bash.ExportFunc("go-echo", echo)
	bash.ExportFunc("reverse", reverse)
	bash.HandleFuncs()

	bash.Source("./example.bash")
	status, err := bash.Run("main", os.Args)
	assert(err)
	os.Exit(status)
}
<|endoftext|>


<commit_before>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package server

// This file deals with loading Access files and checking access permissions.

import (
	"time"

	"upspin.io/access"
	"upspin.io/bind"
	"upspin.io/client/clientutil"
	"upspin.io/errors"
	"upspin.io/log"
	"upspin.io/path"
	"upspin.io/upspin"
)

// accessEntry holds parsed Access files and a sequence number for their entries.
// It is the unit stored in the access cache.
type accessEntry struct {
	sequence int64          // sequence number of the DirEntry parsed.
	acc      *access.Access // parsed contents of the Access file.
}

// remoteGroupDuration is how long a remote Group can be cached.
// Modified by tests.
var remoteGroupDuration = 2 * time.Minute

// whichAccess implements DirServer.WhichAccess.
func (s *server) whichAccess(p path.Parsed, opts ...options) (*upspin.DirEntry, error) {
	o, ss := subspan("whichAccess", opts)
	defer ss.End()

	tree, err := s.loadTreeFor(p.User(), o)
	if err != nil {
		return nil, errors.E(err)
	}
	// Do tree lookups until we find an Access file. Lookups start at the
	// root and go forward till the named path. If no Access file is there,
	// pop up one level. This is so we can find the closest Acesss file,
	// while being aware of links in the way.
	for {
		accPath, err := path.Parse(path.Join(p.Path(), "Access"))
		if err != nil {
			return nil, err
		}
		entry, _, err := tree.Lookup(accPath)
		if err == upspin.ErrFollowLink {
			// WhichAccess(link) always returns the link
			// and ErrFollowLink.
			return entry, err
		}
		if errors.Match(errNotExist, err) {
			if p.IsRoot() {
				// Already at the root, nothing found.
				return nil, nil
			}
			p = p.Drop(1)
			continue
		}
		if err != nil {
			return nil, err
		}
		// Found the Access file.
		return entry, nil
	}
}

// loadAccess loads and processes an Access file from its DirEntry.
func (s *server) loadAccess(entry *upspin.DirEntry, opts ...options) (*access.Access, error) {
	defer span(opts).StartSpan("loadAccess").End()
	buf, err := clientutil.ReadAll(s.serverContext, entry)
	if err != nil {
		return nil, err
	}
	return access.Parse(entry.Name, buf)
}

// loadPath loads a name from the Store, if its entry can be resolved by this
// DirServer. Intended for use with access.Can only.
func (s *server) loadPath(name upspin.PathName) ([]byte, error) {
	p, err := path.Parse(name)
	if err != nil {
		return nil, err
	}

	var entry *upspin.DirEntry
	if s.userName == p.User() {
		entry, err = s.lookup("loadPath", p, entryMustBeClean)
	} else {
		entry, err = s.remoteLookup(p)
		if err == nil {
			// Remember this Group file so we can then forget it
			// when it gets stale. This is guaranteed to be a Group
			// file because Access files are local only. If this
			// ever changes, we must first check whether
			// access.IsGroupFile(p.path()).
			s.remoteGroups.Add(p.Path(), lastLoad(s.now()))
		}
	}
	if err != nil {
		return nil, err
	}
	// entry contains a valid value now. Read it.
	return clientutil.ReadAll(s.serverContext, entry)
}

// remoteLookup performs a lookup on the canonical DirServer for the path,
// which might be remote.
func (s *server) remoteLookup(p path.Parsed) (*upspin.DirEntry, error) {
	key, err := bind.KeyServer(s.serverContext, s.serverContext.KeyEndpoint())
	if err != nil {
		return nil, err
	}
	u, err := key.Lookup(p.User())
	if err != nil {
		return nil, err
	}
	var firstErr error
	check := func(err error) error {
		if firstErr == nil {
			firstErr = err
		}
		return err
	}
	for _, e := range u.Dirs {
		if e == s.serverContext.DirEndpoint() {
			// It's okay to load the tree for this user, because they
			// live in this dir server, according to the KeyServer.
			return s.lookup("remoteLookup", p, entryMustBeClean)
		}
		dir, err := bind.DirServer(s.serverContext, e)
		if check(err) != nil {
			// Skip bad bind.
			continue
		}
		return dir.Lookup(p.Path())
	}
	if firstErr != nil {
		return nil, firstErr
	}
	return nil, errors.E(errors.NotExist, p.Path(), errors.Str("no remote entry for path"))
}

// hasRight reports whether the current user has the given right on the path. If
// ErrFollowLink is returned, the DirEntry will be that of the link.
func (s *server) hasRight(right access.Right, p path.Parsed, opts ...options) (bool, *upspin.DirEntry, error) {
	o, ss := subspan("hasRight", opts)
	defer ss.End()

	entry, err := s.whichAccess(p, o)
	if err == upspin.ErrFollowLink {
		// If we need to follow a link to get to p
		// return that link.
		if entry.Name != p.Path() {
			return false, entry, err
		}
		// If p is the link then find the Access file for p.
		entry, err = s.whichAccess(p.Drop(1), o)
	}
	if err != nil {
		return false, nil, err
	}
	var acc *access.Access
	if entry != nil {
		// We have the Access file entry. Get the contents.
		acc, err = s.getAccess(entry, o)
	} else {
		// No Access file exists anywhere. Use an implicit one.
		// Get the implicit one from the defaultAccess cache.
		acc, err = s.getDefaultAccess(p.User())
	}
	if err != nil {
		return false, nil, err
	}
	// Finally, check whether the user has the requested right.
	can, err := acc.Can(s.userName, right, p.Path(), s.loadPath)
	if err != nil {
		return false, nil, err
	}
	return can, nil, nil
}

// getAccess returns the parsed contents of the Access file described by entry.
func (s *server) getAccess(entry *upspin.DirEntry, opts ...options) (*access.Access, error) {
	o, ss := subspan("getAccess", opts)
	defer ss.End()

	// Sanity check: is this really an Access file?
	if !access.IsAccessFile(entry.Name) {
		return nil, errors.E(errors.Internal, entry.Name, errors.Str("not an Access file"))
	}

	// Is it in the cache?
	var accEntry *accessEntry
	a, found := s.access.Get(entry.Name)
	if found {
		var ok bool
		accEntry, ok = a.(*accessEntry)
		if !ok {
			return nil, errors.E(errors.Internal, errors.Str("invalid accessEntry"))
		}
		if entry.Sequence == accEntry.sequence {
			return accEntry.acc, nil
		}
		// A race happened and we have a cached version that is
		// different than the requested one. Fall through and load the
		// requested one.
	}
	// Not in cache; load data from the Store.

	acc, err := s.loadAccess(entry, o)
	if err != nil {
		return nil, err
	}
	// Add or update cache.
	s.access.Add(entry.Name, &accessEntry{
		sequence: entry.Sequence,
		acc:      acc,
	})
	return acc, nil
}

// getDefaultAccess returns the implicit Access file for a user.
func (s *server) getDefaultAccess(userName upspin.UserName) (acc *access.Access, err error) {
	cacheEntry, found := s.defaultAccess.Get(userName)
	if !found {
		// Create one now and add to the cache.
		acc, err = access.New(upspin.PathName(userName + "/"))
		if err != nil {
			return
		}
		s.defaultAccess.Add(userName, acc)
	} else {
		var ok bool
		acc, ok = cacheEntry.(*access.Access)
		if !ok {
			return nil, errors.E(errors.Internal, errors.Str("not an Access file"))
		}
	}
	return
}

// loadGroup loads a group file from its entry and parses it, but does not
// pass it to access.AddGroup
func (s *server) loadGroup(p path.Parsed, entry *upspin.DirEntry) error {
	data, err := clientutil.ReadAll(s.serverContext, entry)
	if err != nil {
		return err
	}
	_, err = access.ParseGroup(p, data)
	return err
}

// groupRefreshLoop periodically removes potentially stale Group files from the
// access group cache. It runs continuously and must be in a goroutine.
func (s *server) groupRefreshLoop() {
	for {
		time.Sleep(remoteGroupDuration)
		for {
			k, v := s.remoteGroups.PeekOldest()
			if k == nil || v == nil {
				// Nothing to do.
				break
			}
			lastLoaded, ok := v.(lastLoad)
			if !ok {
				log.Error.Printf("dir/server.groupRefreshLoop: value is not of type lastLoad")
				return
			}
			expiration := upspin.Time(lastLoaded) + upspin.Time(remoteGroupDuration.Seconds())
			if expiration < s.now() {
				// Remote the oldest (LRU) and calls OnEviction.
				s.remoteGroups.RemoveOldest()
				continue // look for the next one to expire.
			}
			break // Oldest entry is not old enough.
		}
	}
}

// lastLoad represents the time a remote Group file was loaded by the DirServer.
// It is the value stored in the remoteGroup cache.
type lastLoad upspin.Time

// OnEviction implements cache.EvictionNotifier. It is called when the remote
// group cache is full or an item is forcefully evicted (by calling RemoveOldest
// on the cache). In effect, this "forgets" the Group file if it was loaded.
func (l lastLoad) OnEviction(key interface{}) {
	name, ok := key.(upspin.PathName)
	if !ok {
		log.Error.Printf("dir/server: key in remote group cache is not a pathname: %v", key)
		return
	}
	access.RemoveGroup(name) // ignore return, it may not have been loaded.
}
<commit_msg>dir/server: if an Access file has problems, revert to owner-only privileges<commit_after>// Copyright 2016 The Upspin Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package server

// This file deals with loading Access files and checking access permissions.

import (
	"time"

	"upspin.io/access"
	"upspin.io/bind"
	"upspin.io/client/clientutil"
	"upspin.io/errors"
	"upspin.io/log"
	"upspin.io/path"
	"upspin.io/upspin"
)

// accessEntry holds parsed Access files and a sequence number for their entries.
// It is the unit stored in the access cache.
type accessEntry struct {
	sequence int64          // sequence number of the DirEntry parsed.
	acc      *access.Access // parsed contents of the Access file.
}

// remoteGroupDuration is how long a remote Group can be cached.
// Modified by tests.
var remoteGroupDuration = 2 * time.Minute

// whichAccess implements DirServer.WhichAccess.
func (s *server) whichAccess(p path.Parsed, opts ...options) (*upspin.DirEntry, error) {
	o, ss := subspan("whichAccess", opts)
	defer ss.End()

	tree, err := s.loadTreeFor(p.User(), o)
	if err != nil {
		return nil, errors.E(err)
	}
	// Do tree lookups until we find an Access file. Lookups start at the
	// root and go forward till the named path. If no Access file is there,
	// pop up one level. This is so we can find the closest Acesss file,
	// while being aware of links in the way.
	for {
		accPath, err := path.Parse(path.Join(p.Path(), "Access"))
		if err != nil {
			return nil, err
		}
		entry, _, err := tree.Lookup(accPath)
		if err == upspin.ErrFollowLink {
			// WhichAccess(link) always returns the link
			// and ErrFollowLink.
			return entry, err
		}
		if errors.Match(errNotExist, err) {
			if p.IsRoot() {
				// Already at the root, nothing found.
				return nil, nil
			}
			p = p.Drop(1)
			continue
		}
		if err != nil {
			return nil, err
		}
		// Found the Access file.
		return entry, nil
	}
}

// loadAccess loads and processes an Access file from its DirEntry.
func (s *server) loadAccess(entry *upspin.DirEntry, opts ...options) (*access.Access, error) {
	defer span(opts).StartSpan("loadAccess").End()
	buf, err := clientutil.ReadAll(s.serverContext, entry)
	if err != nil {
		return nil, err
	}
	return access.Parse(entry.Name, buf)
}

// loadPath loads a name from the Store, if its entry can be resolved by this
// DirServer. Intended for use with access.Can only.
func (s *server) loadPath(name upspin.PathName) ([]byte, error) {
	p, err := path.Parse(name)
	if err != nil {
		return nil, err
	}

	var entry *upspin.DirEntry
	if s.userName == p.User() {
		entry, err = s.lookup("loadPath", p, entryMustBeClean)
	} else {
		entry, err = s.remoteLookup(p)
		if err == nil {
			// Remember this Group file so we can then forget it
			// when it gets stale. This is guaranteed to be a Group
			// file because Access files are local only. If this
			// ever changes, we must first check whether
			// access.IsGroupFile(p.path()).
			s.remoteGroups.Add(p.Path(), lastLoad(s.now()))
		}
	}
	if err != nil {
		return nil, err
	}
	// entry contains a valid value now. Read it.
	return clientutil.ReadAll(s.serverContext, entry)
}

// remoteLookup performs a lookup on the canonical DirServer for the path,
// which might be remote.
func (s *server) remoteLookup(p path.Parsed) (*upspin.DirEntry, error) {
	key, err := bind.KeyServer(s.serverContext, s.serverContext.KeyEndpoint())
	if err != nil {
		return nil, err
	}
	u, err := key.Lookup(p.User())
	if err != nil {
		return nil, err
	}
	var firstErr error
	check := func(err error) error {
		if firstErr == nil {
			firstErr = err
		}
		return err
	}
	for _, e := range u.Dirs {
		if e == s.serverContext.DirEndpoint() {
			// It's okay to load the tree for this user, because they
			// live in this dir server, according to the KeyServer.
			return s.lookup("remoteLookup", p, entryMustBeClean)
		}
		dir, err := bind.DirServer(s.serverContext, e)
		if check(err) != nil {
			// Skip bad bind.
			continue
		}
		return dir.Lookup(p.Path())
	}
	if firstErr != nil {
		return nil, firstErr
	}
	return nil, errors.E(errors.NotExist, p.Path(), errors.Str("no remote entry for path"))
}

// hasRight reports whether the current user has the given right on the path. If
// ErrFollowLink is returned, the DirEntry will be that of the link.
func (s *server) hasRight(right access.Right, p path.Parsed, opts ...options) (bool, *upspin.DirEntry, error) {
	o, ss := subspan("hasRight", opts)
	defer ss.End()

	entry, err := s.whichAccess(p, o)
	if err == upspin.ErrFollowLink {
		// If we need to follow a link to get to p
		// return that link.
		if entry.Name != p.Path() {
			return false, entry, err
		}
		// If p is the link then find the Access file for p.
		entry, err = s.whichAccess(p.Drop(1), o)
	}
	if err != nil {
		return false, nil, err
	}
	var acc *access.Access
	if entry != nil {
		// We have the Access file entry. Get the contents.
		acc, err = s.getAccess(entry, o)
		if err != nil {
			// There was an error acquiring or parsing this Access file.
			// That means an Access file is recorded here but is invalid,
			// at least temporarily. Instead of refusing all rights by
			// returning an error, we log the error and restore default
			// (owner-only) rights.
			log.Error.Printf("dir/server: bad Access file %q: %v; using default rights", entry.Name, err)
			acc, err = s.getDefaultAccess(p.User())
		}
	} else {
		// No Access file exists anywhere. Use an implicit one.
		// Get the implicit one from the defaultAccess cache.
		acc, err = s.getDefaultAccess(p.User())
	}
	if err != nil {
		return false, nil, err
	}
	// Finally, check whether the user has the requested right.
	can, err := acc.Can(s.userName, right, p.Path(), s.loadPath)
	if err != nil {
		return false, nil, err
	}
	return can, nil, nil
}

// getAccess returns the parsed contents of the Access file described by entry.
func (s *server) getAccess(entry *upspin.DirEntry, opts ...options) (*access.Access, error) {
	o, ss := subspan("getAccess", opts)
	defer ss.End()

	// Sanity check: is this really an Access file?
	if !access.IsAccessFile(entry.Name) {
		return nil, errors.E(errors.Internal, entry.Name, errors.Str("not an Access file"))
	}

	// Is it in the cache?
	var accEntry *accessEntry
	a, found := s.access.Get(entry.Name)
	if found {
		var ok bool
		accEntry, ok = a.(*accessEntry)
		if !ok {
			return nil, errors.E(errors.Internal, errors.Str("invalid accessEntry"))
		}
		if entry.Sequence == accEntry.sequence {
			return accEntry.acc, nil
		}
		// A race happened and we have a cached version that is
		// different than the requested one. Fall through and load the
		// requested one.
	}
	// Not in cache; load data from the Store.

	acc, err := s.loadAccess(entry, o)
	if err != nil {
		return nil, err
	}
	// Add or update cache.
	s.access.Add(entry.Name, &accessEntry{
		sequence: entry.Sequence,
		acc:      acc,
	})
	return acc, nil
}

// getDefaultAccess returns the implicit Access file for a user.
func (s *server) getDefaultAccess(userName upspin.UserName) (acc *access.Access, err error) {
	cacheEntry, found := s.defaultAccess.Get(userName)
	if !found {
		// Create one now and add to the cache.
		acc, err = access.New(upspin.PathName(userName + "/"))
		if err != nil {
			return
		}
		s.defaultAccess.Add(userName, acc)
	} else {
		var ok bool
		acc, ok = cacheEntry.(*access.Access)
		if !ok {
			return nil, errors.E(errors.Internal, errors.Str("not an Access file"))
		}
	}
	return
}

// loadGroup loads a group file from its entry and parses it, but does not
// pass it to access.AddGroup
func (s *server) loadGroup(p path.Parsed, entry *upspin.DirEntry) error {
	data, err := clientutil.ReadAll(s.serverContext, entry)
	if err != nil {
		return err
	}
	_, err = access.ParseGroup(p, data)
	return err
}

// groupRefreshLoop periodically removes potentially stale Group files from the
// access group cache. It runs continuously and must be in a goroutine.
func (s *server) groupRefreshLoop() {
	for {
		time.Sleep(remoteGroupDuration)
		for {
			k, v := s.remoteGroups.PeekOldest()
			if k == nil || v == nil {
				// Nothing to do.
				break
			}
			lastLoaded, ok := v.(lastLoad)
			if !ok {
				log.Error.Printf("dir/server.groupRefreshLoop: value is not of type lastLoad")
				return
			}
			expiration := upspin.Time(lastLoaded) + upspin.Time(remoteGroupDuration.Seconds())
			if expiration < s.now() {
				// Remote the oldest (LRU) and calls OnEviction.
				s.remoteGroups.RemoveOldest()
				continue // look for the next one to expire.
			}
			break // Oldest entry is not old enough.
		}
	}
}

// lastLoad represents the time a remote Group file was loaded by the DirServer.
// It is the value stored in the remoteGroup cache.
type lastLoad upspin.Time

// OnEviction implements cache.EvictionNotifier. It is called when the remote
// group cache is full or an item is forcefully evicted (by calling RemoveOldest
// on the cache). In effect, this "forgets" the Group file if it was loaded.
func (l lastLoad) OnEviction(key interface{}) {
	name, ok := key.(upspin.PathName)
	if !ok {
		log.Error.Printf("dir/server: key in remote group cache is not a pathname: %v", key)
		return
	}
	access.RemoveGroup(name) // ignore return, it may not have been loaded.
}
<|endoftext|>


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

package discovery

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"

	sd_config "github.com/prometheus/prometheus/discovery/config"
	"github.com/prometheus/prometheus/discovery/targetgroup"

	"github.com/prometheus/prometheus/discovery/azure"
	"github.com/prometheus/prometheus/discovery/consul"
	"github.com/prometheus/prometheus/discovery/dns"
	"github.com/prometheus/prometheus/discovery/ec2"
	"github.com/prometheus/prometheus/discovery/file"
	"github.com/prometheus/prometheus/discovery/gce"
	"github.com/prometheus/prometheus/discovery/kubernetes"
	"github.com/prometheus/prometheus/discovery/marathon"
	"github.com/prometheus/prometheus/discovery/openstack"
	"github.com/prometheus/prometheus/discovery/triton"
	"github.com/prometheus/prometheus/discovery/zookeeper"
)

// Discoverer provides information about target groups. It maintains a set
// of sources from which TargetGroups can originate. Whenever a discovery provider
// detects a potential change, it sends the TargetGroup through its channel.
//
// Discoverer does not know if an actual change happened.
// It does guarantee that it sends the new TargetGroup whenever a change happens.
//
// Discoverers should initially send a full set of all discoverable TargetGroups.
type Discoverer interface {
	// Run hands a channel to the discovery provider(consul,dns etc) through which it can send
	// updated target groups.
	// Must returns if the context gets canceled. It should not close the update
	// channel on returning.
	Run(ctx context.Context, up chan<- []*targetgroup.Group)
}

type poolKey struct {
	setName  string
	provider string
}

// NewManager is the Discovery Manager constructor
func NewManager(ctx context.Context, logger log.Logger) *Manager {
	return &Manager{
		logger:         logger,
		syncCh:         make(chan map[string][]*targetgroup.Group),
		targets:        make(map[poolKey]map[string]*targetgroup.Group),
		discoverCancel: []context.CancelFunc{},
		ctx:            ctx,
	}
}

// Manager maintains a set of discovery providers and sends each update to a map channel.
// Targets are grouped by the target set name.
type Manager struct {
	logger         log.Logger
	mtx            sync.RWMutex
	ctx            context.Context
	discoverCancel []context.CancelFunc
	// Some Discoverers(eg. k8s) send only the updates for a given target group
	// so we use map[tg.Source]*targetgroup.Group to know which group to update.
	targets map[poolKey]map[string]*targetgroup.Group
	// The sync channels sends the updates in map[targetSetName] where targetSetName is the job value from the scrape config.
	syncCh chan map[string][]*targetgroup.Group
	// True if updates were received in the last 5 seconds.
	recentlyUpdated bool
	// Protects recentlyUpdated.
	recentlyUpdatedMtx sync.Mutex
}

// Run starts the background processing
func (m *Manager) Run() error {
	for range m.ctx.Done() {
		m.cancelDiscoverers()
		return m.ctx.Err()
	}
	return nil
}

// SyncCh returns a read only channel used by all Discoverers to send target updates.
func (m *Manager) SyncCh() <-chan map[string][]*targetgroup.Group {
	return m.syncCh
}

// ApplyConfig removes all running discovery providers and starts new ones using the provided config.
func (m *Manager) ApplyConfig(cfg map[string]sd_config.ServiceDiscoveryConfig) error {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	m.cancelDiscoverers()
	for name, scfg := range cfg {
		for provName, prov := range m.providersFromConfig(scfg) {
			m.startProvider(m.ctx, poolKey{setName: name, provider: provName}, prov)
		}
	}

	return nil
}

// StartCustomProvider is used for sdtool. Only use this if you know what you're doing.
func (m *Manager) StartCustomProvider(ctx context.Context, name string, worker Discoverer) {
	// Pool key for non-standard SD implementations are unknown.
	poolKey := poolKey{setName: name, provider: name}
	m.startProvider(ctx, poolKey, worker)
}

func (m *Manager) startProvider(ctx context.Context, poolKey poolKey, worker Discoverer) {
	ctx, cancel := context.WithCancel(ctx)
	updates := make(chan []*targetgroup.Group)

	m.discoverCancel = append(m.discoverCancel, cancel)

	go worker.Run(ctx, updates)
	go m.runProvider(ctx, poolKey, updates)
	go m.runUpdater(ctx)
}

func (m *Manager) runProvider(ctx context.Context, poolKey poolKey, updates chan []*targetgroup.Group) {
	for {
		select {
		case <-ctx.Done():
			return
		case tgs, ok := <-updates:
			// Handle the case that a target provider exits and closes the channel
			// before the context is done.
			if !ok {
				return
			}
			m.updateGroup(poolKey, tgs)
			m.recentlyUpdatedMtx.Lock()
			m.recentlyUpdated = true
			m.recentlyUpdatedMtx.Unlock()
		}
	}
}

func (m *Manager) runUpdater(ctx context.Context) {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			m.recentlyUpdatedMtx.Lock()
			if m.recentlyUpdated {
				m.syncCh <- m.allGroups()
				m.recentlyUpdated = false
			}
			m.recentlyUpdatedMtx.Unlock()
		}
	}
}

func (m *Manager) cancelDiscoverers() {
	for _, c := range m.discoverCancel {
		c()
	}
	m.targets = make(map[poolKey]map[string]*targetgroup.Group)
	m.discoverCancel = nil
}

func (m *Manager) updateGroup(poolKey poolKey, tgs []*targetgroup.Group) {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	for _, tg := range tgs {
		if tg != nil { // Some Discoverers send nil target group so need to check for it to avoid panics.
			if _, ok := m.targets[poolKey]; !ok {
				m.targets[poolKey] = make(map[string]*targetgroup.Group)
			}
			m.targets[poolKey][tg.Source] = tg
		}
	}
}

func (m *Manager) allGroups() map[string][]*targetgroup.Group {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	tSets := map[string][]*targetgroup.Group{}
	for pkey, tsets := range m.targets {
		for _, tg := range tsets {
			// Even if the target group 'tg' is empty we still need to send it to the 'Scrape manager'
			// to signal that it needs to stop all scrape loops for this target set.
			tSets[pkey.setName] = append(tSets[pkey.setName], tg)
		}
	}
	return tSets
}

func (m *Manager) providersFromConfig(cfg sd_config.ServiceDiscoveryConfig) map[string]Discoverer {
	providers := map[string]Discoverer{}

	app := func(mech string, i int, tp Discoverer) {
		providers[fmt.Sprintf("%s/%d", mech, i)] = tp
	}

	for i, c := range cfg.DNSSDConfigs {
		app("dns", i, dns.NewDiscovery(*c, log.With(m.logger, "discovery", "dns")))
	}
	for i, c := range cfg.FileSDConfigs {
		app("file", i, file.NewDiscovery(c, log.With(m.logger, "discovery", "file")))
	}
	for i, c := range cfg.ConsulSDConfigs {
		k, err := consul.NewDiscovery(c, log.With(m.logger, "discovery", "consul"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Consul discovery", "err", err)
			continue
		}
		app("consul", i, k)
	}
	for i, c := range cfg.MarathonSDConfigs {
		t, err := marathon.NewDiscovery(*c, log.With(m.logger, "discovery", "marathon"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Marathon discovery", "err", err)
			continue
		}
		app("marathon", i, t)
	}
	for i, c := range cfg.KubernetesSDConfigs {
		k, err := kubernetes.New(log.With(m.logger, "discovery", "k8s"), c)
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Kubernetes discovery", "err", err)
			continue
		}
		app("kubernetes", i, k)
	}
	for i, c := range cfg.ServersetSDConfigs {
		app("serverset", i, zookeeper.NewServersetDiscovery(c, log.With(m.logger, "discovery", "zookeeper")))
	}
	for i, c := range cfg.NerveSDConfigs {
		app("nerve", i, zookeeper.NewNerveDiscovery(c, log.With(m.logger, "discovery", "nerve")))
	}
	for i, c := range cfg.EC2SDConfigs {
		app("ec2", i, ec2.NewDiscovery(c, log.With(m.logger, "discovery", "ec2")))
	}
	for i, c := range cfg.OpenstackSDConfigs {
		openstackd, err := openstack.NewDiscovery(c, log.With(m.logger, "discovery", "openstack"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot initialize OpenStack discovery", "err", err)
			continue
		}
		app("openstack", i, openstackd)
	}

	for i, c := range cfg.GCESDConfigs {
		gced, err := gce.NewDiscovery(*c, log.With(m.logger, "discovery", "gce"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot initialize GCE discovery", "err", err)
			continue
		}
		app("gce", i, gced)
	}
	for i, c := range cfg.AzureSDConfigs {
		app("azure", i, azure.NewDiscovery(c, log.With(m.logger, "discovery", "azure")))
	}
	for i, c := range cfg.TritonSDConfigs {
		t, err := triton.New(log.With(m.logger, "discovery", "trition"), c)
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Triton discovery", "err", err)
			continue
		}
		app("triton", i, t)
	}
	if len(cfg.StaticConfigs) > 0 {
		app("static", 0, &StaticProvider{cfg.StaticConfigs})
	}

	return providers
}

// StaticProvider holds a list of target groups that never change.
type StaticProvider struct {
	TargetGroups []*targetgroup.Group
}

// Run implements the Worker interface.
func (sd *StaticProvider) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
	// We still have to consider that the consumer exits right away in which case
	// the context will be canceled.
	select {
	case ch <- sd.TargetGroups:
	case <-ctx.Done():
	}
	close(ch)
}
<commit_msg>Simplify SD update throttling (#4523)<commit_after>// Copyright 2016 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package discovery

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"

	sd_config "github.com/prometheus/prometheus/discovery/config"
	"github.com/prometheus/prometheus/discovery/targetgroup"

	"github.com/prometheus/prometheus/discovery/azure"
	"github.com/prometheus/prometheus/discovery/consul"
	"github.com/prometheus/prometheus/discovery/dns"
	"github.com/prometheus/prometheus/discovery/ec2"
	"github.com/prometheus/prometheus/discovery/file"
	"github.com/prometheus/prometheus/discovery/gce"
	"github.com/prometheus/prometheus/discovery/kubernetes"
	"github.com/prometheus/prometheus/discovery/marathon"
	"github.com/prometheus/prometheus/discovery/openstack"
	"github.com/prometheus/prometheus/discovery/triton"
	"github.com/prometheus/prometheus/discovery/zookeeper"
)

// Discoverer provides information about target groups. It maintains a set
// of sources from which TargetGroups can originate. Whenever a discovery provider
// detects a potential change, it sends the TargetGroup through its channel.
//
// Discoverer does not know if an actual change happened.
// It does guarantee that it sends the new TargetGroup whenever a change happens.
//
// Discoverers should initially send a full set of all discoverable TargetGroups.
type Discoverer interface {
	// Run hands a channel to the discovery provider(consul,dns etc) through which it can send
	// updated target groups.
	// Must returns if the context gets canceled. It should not close the update
	// channel on returning.
	Run(ctx context.Context, up chan<- []*targetgroup.Group)
}

type poolKey struct {
	setName  string
	provider string
}

// NewManager is the Discovery Manager constructor
func NewManager(ctx context.Context, logger log.Logger) *Manager {
	return &Manager{
		logger:         logger,
		syncCh:         make(chan map[string][]*targetgroup.Group),
		targets:        make(map[poolKey]map[string]*targetgroup.Group),
		discoverCancel: []context.CancelFunc{},
		ctx:            ctx,
	}
}

// Manager maintains a set of discovery providers and sends each update to a map channel.
// Targets are grouped by the target set name.
type Manager struct {
	logger         log.Logger
	mtx            sync.RWMutex
	ctx            context.Context
	discoverCancel []context.CancelFunc
	// Some Discoverers(eg. k8s) send only the updates for a given target group
	// so we use map[tg.Source]*targetgroup.Group to know which group to update.
	targets map[poolKey]map[string]*targetgroup.Group
	// The sync channels sends the updates in map[targetSetName] where targetSetName is the job value from the scrape config.
	syncCh chan map[string][]*targetgroup.Group
}

// Run starts the background processing
func (m *Manager) Run() error {
	for range m.ctx.Done() {
		m.cancelDiscoverers()
		return m.ctx.Err()
	}
	return nil
}

// SyncCh returns a read only channel used by all Discoverers to send target updates.
func (m *Manager) SyncCh() <-chan map[string][]*targetgroup.Group {
	return m.syncCh
}

// ApplyConfig removes all running discovery providers and starts new ones using the provided config.
func (m *Manager) ApplyConfig(cfg map[string]sd_config.ServiceDiscoveryConfig) error {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	m.cancelDiscoverers()
	for name, scfg := range cfg {
		for provName, prov := range m.providersFromConfig(scfg) {
			m.startProvider(m.ctx, poolKey{setName: name, provider: provName}, prov)
		}
	}

	return nil
}

// StartCustomProvider is used for sdtool. Only use this if you know what you're doing.
func (m *Manager) StartCustomProvider(ctx context.Context, name string, worker Discoverer) {
	// Pool key for non-standard SD implementations are unknown.
	poolKey := poolKey{setName: name, provider: name}
	m.startProvider(ctx, poolKey, worker)
}

func (m *Manager) startProvider(ctx context.Context, poolKey poolKey, worker Discoverer) {
	ctx, cancel := context.WithCancel(ctx)
	updates := make(chan []*targetgroup.Group)

	m.discoverCancel = append(m.discoverCancel, cancel)

	go worker.Run(ctx, updates)
	go m.runProvider(ctx, poolKey, updates)
}

func (m *Manager) runProvider(ctx context.Context, poolKey poolKey, updates chan []*targetgroup.Group) {
	ticker := time.NewTicker(5 * time.Second)
	defer ticker.Stop()

	updateReceived := make(chan struct{}, 1)

	for {
		select {
		case <-ctx.Done():
			return
		case tgs, ok := <-updates:
			// Handle the case that a target provider(E.g. StaticProvider) exits and
			// closes the channel before the context is done.
			// This will prevent sending the updates to the receiver so we send them before exiting.
			if !ok {
				select {
				case m.syncCh <- m.allGroups():
				default:
					level.Debug(m.logger).Log("msg", "discovery receiver's channel was full")
				}
				return
			}
			m.updateGroup(poolKey, tgs)

			// Signal that there was an update.
			select {
			case updateReceived <- struct{}{}:
			default:
			}
		case <-ticker.C: // Some discoverers send updates too often so we send these to the receiver once every 5 seconds.
			select {
			case <-updateReceived: // Send only when there is a new update.
				select {
				case m.syncCh <- m.allGroups():
				default:
					level.Debug(m.logger).Log("msg", "discovery receiver's channel was full")
				}
			default:
			}
		}
	}
}

func (m *Manager) cancelDiscoverers() {
	for _, c := range m.discoverCancel {
		c()
	}
	m.targets = make(map[poolKey]map[string]*targetgroup.Group)
	m.discoverCancel = nil
}

func (m *Manager) updateGroup(poolKey poolKey, tgs []*targetgroup.Group) {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	for _, tg := range tgs {
		if tg != nil { // Some Discoverers send nil target group so need to check for it to avoid panics.
			if _, ok := m.targets[poolKey]; !ok {
				m.targets[poolKey] = make(map[string]*targetgroup.Group)
			}
			m.targets[poolKey][tg.Source] = tg
		}
	}
}

func (m *Manager) allGroups() map[string][]*targetgroup.Group {
	m.mtx.Lock()
	defer m.mtx.Unlock()

	tSets := map[string][]*targetgroup.Group{}
	for pkey, tsets := range m.targets {
		for _, tg := range tsets {
			// Even if the target group 'tg' is empty we still need to send it to the 'Scrape manager'
			// to signal that it needs to stop all scrape loops for this target set.
			tSets[pkey.setName] = append(tSets[pkey.setName], tg)
		}
	}
	return tSets
}

func (m *Manager) providersFromConfig(cfg sd_config.ServiceDiscoveryConfig) map[string]Discoverer {
	providers := map[string]Discoverer{}

	app := func(mech string, i int, tp Discoverer) {
		providers[fmt.Sprintf("%s/%d", mech, i)] = tp
	}

	for i, c := range cfg.DNSSDConfigs {
		app("dns", i, dns.NewDiscovery(*c, log.With(m.logger, "discovery", "dns")))
	}
	for i, c := range cfg.FileSDConfigs {
		app("file", i, file.NewDiscovery(c, log.With(m.logger, "discovery", "file")))
	}
	for i, c := range cfg.ConsulSDConfigs {
		k, err := consul.NewDiscovery(c, log.With(m.logger, "discovery", "consul"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Consul discovery", "err", err)
			continue
		}
		app("consul", i, k)
	}
	for i, c := range cfg.MarathonSDConfigs {
		t, err := marathon.NewDiscovery(*c, log.With(m.logger, "discovery", "marathon"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Marathon discovery", "err", err)
			continue
		}
		app("marathon", i, t)
	}
	for i, c := range cfg.KubernetesSDConfigs {
		k, err := kubernetes.New(log.With(m.logger, "discovery", "k8s"), c)
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Kubernetes discovery", "err", err)
			continue
		}
		app("kubernetes", i, k)
	}
	for i, c := range cfg.ServersetSDConfigs {
		app("serverset", i, zookeeper.NewServersetDiscovery(c, log.With(m.logger, "discovery", "zookeeper")))
	}
	for i, c := range cfg.NerveSDConfigs {
		app("nerve", i, zookeeper.NewNerveDiscovery(c, log.With(m.logger, "discovery", "nerve")))
	}
	for i, c := range cfg.EC2SDConfigs {
		app("ec2", i, ec2.NewDiscovery(c, log.With(m.logger, "discovery", "ec2")))
	}
	for i, c := range cfg.OpenstackSDConfigs {
		openstackd, err := openstack.NewDiscovery(c, log.With(m.logger, "discovery", "openstack"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot initialize OpenStack discovery", "err", err)
			continue
		}
		app("openstack", i, openstackd)
	}

	for i, c := range cfg.GCESDConfigs {
		gced, err := gce.NewDiscovery(*c, log.With(m.logger, "discovery", "gce"))
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot initialize GCE discovery", "err", err)
			continue
		}
		app("gce", i, gced)
	}
	for i, c := range cfg.AzureSDConfigs {
		app("azure", i, azure.NewDiscovery(c, log.With(m.logger, "discovery", "azure")))
	}
	for i, c := range cfg.TritonSDConfigs {
		t, err := triton.New(log.With(m.logger, "discovery", "trition"), c)
		if err != nil {
			level.Error(m.logger).Log("msg", "Cannot create Triton discovery", "err", err)
			continue
		}
		app("triton", i, t)
	}
	if len(cfg.StaticConfigs) > 0 {
		app("static", 0, &StaticProvider{cfg.StaticConfigs})
	}

	return providers
}

// StaticProvider holds a list of target groups that never change.
type StaticProvider struct {
	TargetGroups []*targetgroup.Group
}

// Run implements the Worker interface.
func (sd *StaticProvider) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
	// We still have to consider that the consumer exits right away in which case
	// the context will be canceled.
	select {
	case ch <- sd.TargetGroups:
	case <-ctx.Done():
	}
	close(ch)
}
<|endoftext|>


<commit_before>package config

import (
	"bytes"
	. "launchpad.net/gocheck"
)

var conf = `defaults:
    port: 5555
    nick: nick
    username: username
    userhost: userhost.com
    realname: realname
servers:
    myserver:
        host: irc.gamesurge.net
        nick: nickoverride
    irc.gamesurge.net:
        port: 3333
`

func (s *s) TestConfig_FromFile(c *C) {
	buf := bytes.NewBufferString(conf)
	conf := CreateConfigFromReader(buf)
	c.Assert(len(conf.Errors), Equals, 0)

	srv1 := conf.Servers["myserver"]
	c.Assert(srv1.GetNick(), Equals, "nickoverride")
	c.Assert(srv1.GetPort(), Equals, uint16(5555))
	c.Assert(srv1.GetUsername(), Equals, "username")
	c.Assert(srv1.GetUserhost(), Equals, "userhost.com")
	c.Assert(srv1.GetRealname(), Equals, "realname")

	c.Assert(srv1.GetName(), Equals, "myserver")
	c.Assert(srv1.GetHost(), Equals, "irc.gamesurge.net")

	srv2 := conf.Servers["irc.gamesurge.net"]
	c.Assert(srv2.GetNick(), Equals, "nick")
	c.Assert(srv2.GetHost(), Equals, "irc.gamesurge.net")
	c.Assert(srv2.GetName(), Equals, srv2.GetHost())

	c.Assert(conf.IsValid(), Equals, true)
}

func (s *s) TestConfig_FromFileError(c *C) {
	buf := bytes.NewBufferString("defaults:\n\tport: 5555")
	conf := CreateConfigFromReader(buf)
	c.Assert(len(conf.Errors), Equals, 1)
	c.Assert(conf.Errors[0].Error(), Matches,
		errMsgInvalidConfigFile[:len(errMsgInvalidConfigFile)-4]+`.*`)
}
<commit_msg>Cover configfile_test better.<commit_after>package config

import (
	"bytes"
	"io"
	. "launchpad.net/gocheck"
)

var conf = `defaults:
    port: 5555
    nick: nick
    username: username
    userhost: userhost.com
    realname: realname
servers:
    myserver:
        host: irc.gamesurge.net
        nick: nickoverride
    irc.gamesurge.net:
        port: 3333
`

type dyingReader struct {
}

func (d *dyingReader) Read(b []byte) (n int, err error) {
	err = io.ErrUnexpectedEOF
	return
}

func (s *s) TestConfig_FromFile(c *C) {
	buf := bytes.NewBufferString(conf)
	conf := CreateConfigFromReader(buf)
	c.Assert(len(conf.Errors), Equals, 0)

	srv1 := conf.Servers["myserver"]
	c.Assert(srv1.GetNick(), Equals, "nickoverride")
	c.Assert(srv1.GetPort(), Equals, uint16(5555))
	c.Assert(srv1.GetUsername(), Equals, "username")
	c.Assert(srv1.GetUserhost(), Equals, "userhost.com")
	c.Assert(srv1.GetRealname(), Equals, "realname")

	c.Assert(srv1.GetName(), Equals, "myserver")
	c.Assert(srv1.GetHost(), Equals, "irc.gamesurge.net")

	srv2 := conf.Servers["irc.gamesurge.net"]
	c.Assert(srv2.GetNick(), Equals, "nick")
	c.Assert(srv2.GetHost(), Equals, "irc.gamesurge.net")
	c.Assert(srv2.GetName(), Equals, srv2.GetHost())

	c.Assert(conf.IsValid(), Equals, true)
}

func (s *s) TestConfig_FromFileError(c *C) {
	conf := CreateConfigFromReader(&dyingReader{})
	c.Assert(len(conf.Errors), Equals, 1)
	c.Assert(conf.Errors[0].Error(), Matches,
		errMsgInvalidConfigFile[:len(errMsgInvalidConfigFile)-4]+`.*`)

	buf := bytes.NewBufferString("defaults:\n\tport: 5555")
	conf = CreateConfigFromReader(buf)
	c.Assert(len(conf.Errors), Equals, 1)
	c.Assert(conf.Errors[0].Error(), Matches,
		errMsgInvalidConfigFile[:len(errMsgInvalidConfigFile)-4]+`.*`)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"net/http"
	"net/url"
	"os"
	"version"

	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

var tilePath string
var infoCache *lru.Cache

const DefaultAddress = ":8888"
const DefaultInfoCacheLen = 10000

func main() {
	// Defaults
	viper.SetDefault("Address", DefaultAddress)
	viper.SetDefault("InfoCacheLen", DefaultInfoCacheLen)

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com:8888/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", DefaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", DefaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		log.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	// Handle IIIF data only if we have a IIIF URL
	if viper.IsSet("IIIFURL") {
		iiifURL := viper.GetString("IIIFURL")
		iiifBase, err := url.Parse(iiifURL)
		if err != nil || iiifBase.Scheme == "" || iiifBase.Host == "" || iiifBase.Path == "" {
			log.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
		}

		icl := viper.GetInt("InfoCacheLen")
		if icl > 0 {
			infoCache, err = lru.New(icl)
			if err != nil {
				log.Fatalf("Unable to start info cache: %s", err)
			}
		}

		log.Printf("IIIF enabled at %s\n", iiifBase.String())
		ih := NewIIIFHandler(iiifBase, tilePath)
		http.HandleFunc(ih.Base.Path+"/", ih.Route)
	}

	http.HandleFunc("/images/tiles/", TileHandler)
	http.HandleFunc("/images/resize/", ResizeHandler)
	http.HandleFunc("/version", VersionHandler)
	if err := http.ListenAndServe(address, nil); err != nil {
		log.Fatalf("Error starting listener: %s", err)
	}
}

func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<commit_msg>Make default address ":12415"<commit_after>package main

import (
	"log"
	"net/http"
	"net/url"
	"os"
	"version"

	"github.com/hashicorp/golang-lru"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
)

var tilePath string
var infoCache *lru.Cache

const DefaultAddress = ":12415"
const DefaultInfoCacheLen = 10000

func main() {
	// Defaults
	viper.SetDefault("Address", DefaultAddress)
	viper.SetDefault("InfoCacheLen", DefaultInfoCacheLen)

	// Config file options
	viper.SetConfigName("rais")
	viper.AddConfigPath("/etc")
	viper.AddConfigPath(".")
	viper.ReadInConfig()

	// CLI flags
	pflag.String("iiif-url", "", `Base URL for serving IIIF requests, e.g., "http://example.com:8888/images/iiif"`)
	viper.BindPFlag("IIIFURL", pflag.CommandLine.Lookup("iiif-url"))
	pflag.String("address", DefaultAddress, "http service address")
	viper.BindPFlag("Address", pflag.CommandLine.Lookup("address"))
	pflag.String("tile-path", "", "Base path for images")
	viper.BindPFlag("TilePath", pflag.CommandLine.Lookup("tile-path"))
	pflag.Int("iiif-info-cache-size", DefaultInfoCacheLen, "Maximum cached image info entries (IIIF only)")
	viper.BindPFlag("InfoCacheLen", pflag.CommandLine.Lookup("iiif-info-cache-size"))
	pflag.Parse()

	// Make sure required values exist
	if !viper.IsSet("TilePath") {
		log.Println("ERROR: --tile-path is required")
		pflag.Usage()
		os.Exit(1)
	}

	// Pull all values we need for all cases
	tilePath = viper.GetString("TilePath")
	address := viper.GetString("Address")

	// Handle IIIF data only if we have a IIIF URL
	if viper.IsSet("IIIFURL") {
		iiifURL := viper.GetString("IIIFURL")
		iiifBase, err := url.Parse(iiifURL)
		if err != nil || iiifBase.Scheme == "" || iiifBase.Host == "" || iiifBase.Path == "" {
			log.Fatalf("Invalid IIIF URL (%s) specified: %s", iiifURL, err)
		}

		icl := viper.GetInt("InfoCacheLen")
		if icl > 0 {
			infoCache, err = lru.New(icl)
			if err != nil {
				log.Fatalf("Unable to start info cache: %s", err)
			}
		}

		log.Printf("IIIF enabled at %s\n", iiifBase.String())
		ih := NewIIIFHandler(iiifBase, tilePath)
		http.HandleFunc(ih.Base.Path+"/", ih.Route)
	}

	http.HandleFunc("/images/tiles/", TileHandler)
	http.HandleFunc("/images/resize/", ResizeHandler)
	http.HandleFunc("/version", VersionHandler)
	if err := http.ListenAndServe(address, nil); err != nil {
		log.Fatalf("Error starting listener: %s", err)
	}
}

func VersionHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte(version.Version))
}
<|endoftext|>