<commit_before>// Package plastic is a game engine...
package plastic

import (
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/audio"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/collision"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/dlog"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/event"
	pmouse "bitbucket.org/oakmoundstudio/plasticpiston/plastic/mouse"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/render"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"math/rand"
	"os"
	"path/filepath"
	"strconv"
	"time"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

var (
	initCh               = make(chan bool)
	sceneCh              = make(chan bool)
	skipSceneCh          = make(chan bool)
	quitCh               = make(chan bool)
	drawChannel          = make(chan bool)
	debugResetCh         = make(chan bool)
	viewportChannel      = make(chan [2]int)
	drawInit             = false
	runEventLoop         = false
	ScreenWidth          int
	ScreenHeight         int
	press                = key.DirPress
	release              = key.DirRelease
	black                = color.RGBA{0x00, 0x00, 0x00, 0xff}
	b                    screen.Buffer
	winBuffer            screen.Buffer
	eb                   *event.EventBus
	esc                  = false
	l_debug              = false
	wd, _                = os.Getwd()
	imageDir             string
	audioDir             string
	debugResetInProgress = false
	globalFirstScene     string
	startupLoadComplete  bool
	imageBlack           = image.Black
	zeroPoint            = image.Point{0, 0}
)

// Init initializes the plastic engine.
// It spawns off an event loop of several goroutines
// and loops through scenes after initalization.
func Init(firstScene string) {
	dlog.CreateLogFile()

	err := loadDefaultConf()

	// Set variables from conf file
	dlog.SetStringDebugLevel(conf.Debug.Level)
	dlog.SetDebugFilter(conf.Debug.Filter)

	if err != nil {
		dlog.Verb(err)
	}

	ScreenWidth = conf.Screen.Width
	ScreenHeight = conf.Screen.Height

	imageDir = filepath.Join(filepath.Dir(wd),
		conf.Assets.AssetPath,
		conf.Assets.ImagePath)
	audioDir = filepath.Join(filepath.Dir(wd),
		conf.Assets.AssetPath,
		conf.Assets.AudioPath)

	render.SetFontDefaults(wd, conf.Assets.AssetPath, conf.Assets.FontPath,
		conf.Font.Hinting, conf.Font.Color, conf.Font.File, conf.Font.Size,
		conf.Font.DPI)
	// END of loading variables from configuration

	// Init various engine pieces
	collision.Init()
	pmouse.Init()
	render.InitDrawHeap()
	audio.InitWinAudio()

	// Seed the rng
	curSeed := time.Now().UTC().UnixNano()
	//curSeed = 1471104995917281000
	rand.Seed(curSeed)
	dlog.Info("The seed is:", curSeed)
	fmt.Println("\n~~~~~~~~~~~~~~~\nTHE SEED IS:", curSeed, "\n~~~~~~~~~~~~~~~\n")

	// Load in assets
	go func() {
		err = render.BatchLoad(imageDir)
		if err != nil {
			dlog.Error(err)
			return
		}

		err = audio.BatchLoad(audioDir)
		if err != nil {
			dlog.Error(err)
		}

		startupLoadComplete = true
	}()

	// Spawn off event loop goroutines
	go driver.Main(eventLoop)

	go DebugConsole(debugResetCh, skipSceneCh)

	prevScene := ""
	sceneMap[firstScene].active = true

	<-initCh
	close(initCh)

	// Loop through scenes
	runEventLoop = true
	globalFirstScene = firstScene
	scene := "loading"
	var data interface{} = nil
	dlog.Info("First Scene Start")
	for {
		ViewX = 0
		ViewY = 0
		useViewBounds = false
		dlog.Info("~~~~~~~~~~~Scene Start~~~~~~~~~")
		sceneMap[scene].start(prevScene, data)
		// Send a signal to resume (or begin) drawing
		drawChannel <- true

		cont := true
		for cont {
			select {
			// The quit channel represents a signal
			// for the engine to stop.
			case <-quitCh:
				return
			case <-sceneCh:
				cont = sceneMap[scene].loop()
			case <-skipSceneCh:
				cont = false
			}
		}
		dlog.Info("~~~~~~~~Scene End~~~~~~~~~~")
		prevScene = scene

		// Send a signal to stop drawing
		drawChannel <- true

		// Reset transient portions of the engine
		event.ResetEntities()
		event.ResetEventBus()
		render.ResetDrawHeap()
		collision.Clear()
		pmouse.Clear()
		render.PreDraw(0, nil)

		scene, data = sceneMap[scene].end()

		eb = event.GetEventBus()
		if !debugResetInProgress {
			debugResetInProgress = true
			go func() {
				debugResetCh <- true
				debugResetInProgress = false

			}()
		}
	}
}

func eventLoop(s screen.Screen) {

	// The event loop requires information about
	// the size of the world and screen that is
	// being dealt with, and so initializes it here.
	//
	// Todo: add world size to config
	b, _ = s.NewBuffer(image.Point{4000, 4000})
	winBuffer, _ = s.NewBuffer(image.Point{ScreenWidth, ScreenHeight})
	w, err := s.NewWindow(&screen.NewWindowOptions{ScreenWidth, ScreenHeight})
	if err != nil {
		dlog.Error(err)
	}
	defer w.Release()

	// This initialization happens here on account of font's initialization
	// requiring a buffer to draw to. Can probably change in the future.
	render.InitFont(b, winBuffer)

	eb = event.GetEventBus()

	// Todo: add frame rate to config
	frameRate := 60
	frameCh := make(chan bool)

	// This goroutine maintains a logical framerate
	go func(frameCh chan bool, frameRate int64) {
		c := time.Tick(time.Second / time.Duration(frameRate))
		for range c {
			frameCh <- true
		}
	}(frameCh, int64(frameRate))

	// Native go event handler
	go func() {
		for {
			e := w.NextEvent()
			switch e := e.(type) {

			// We only currently respond to death lifecycle events.
			case lifecycle.Event:
				if e.To == lifecycle.StageDead {
					quitCh <- true
					return
				}

			// Send key events
			//
			// Key events have two varieties:
			// The "KeyDown" and "KeyUp" events, which trigger for all keys
			// and specific "KeyDown$key", etc events which trigger only for $key.
			// The specific key that is pressed is passed as the data interface for
			// the former events, but not for the latter.
			case key.Event:
				k := GetKeyBind(e.Code.String()[4:])
				if e.Direction == press {
					fmt.Println("--------------------", e.Code.String()[4:], k)
					setDown(k)
					eb.Trigger("KeyDown", k)
					eb.Trigger("KeyDown"+k, nil)
				} else if e.Direction == release {
					setUp(k)
					eb.Trigger("KeyUp", k)
					eb.Trigger("KeyUp"+k, nil)
				}

			// Send mouse events
			//
			// Mouse events are parsed based on their button
			// and direction into an event name and then triggered:
			// 'MousePress', 'MouseRelease', 'MouseScrollDown', 'MouseScrollUp', and 'MouseDrag'
			//
			// The basic event name is meant for entities which
			// want to respond to the mouse event happening -anywhere-.
			//
			// For events which have mouse collision enabled, they'll recieve
			// $eventName+"On" when the event occurs within their collision area.
			//
			// Mouse events all recieve an x, y, and button string.
			case mouse.Event:
				button := pmouse.GetMouseButton(int32(e.Button))
				dlog.Verb("Mouse direction ", e.Direction.String(), " Button ", button)
				mevent := pmouse.MouseEvent{e.X, e.Y, button}
				var eventName string
				if e.Direction == mouse.DirPress {
					setDown(button)
					eventName = "MousePress"
				} else if e.Direction == mouse.DirRelease {
					setUp(button)
					eventName = "MouseRelease"
				} else if e.Button == -2 {
					eventName = "MouseScrollDown"
				} else if e.Button == -1 {
					eventName = "MouseScrollUp"
				} else {
					eventName = "MouseDrag"
				}
				eb.Trigger(eventName, mevent)
				pmouse.Propagate(eventName+"On", mevent)

			// I don't really know what a paint event is to be honest.
			// We hypothetically don't allow the user to manually resize
			// their window, so we don't do anything special for such events.
			case size.Event, paint.Event:
			case error:
				dlog.Error(e)
			}

			// This is a hardcoded quit function bound to the escape key.
			if IsDown("Escape") {
				if esc {
					dlog.Warn("Quiting plastic from holding ESCAPE")
					w.Send(lifecycle.Event{0, 0, nil})
				}
				esc = true
			} else {
				esc = false
			}
		}
	}()

	// This sends a signal to initiate the first scene
	initCh <- true

	// The draw loop
	// Unless told to stop, the draw channel will repeatedly
	// 1. draw black to a temporary buffer
	// 2. run any functions bound to precede drawing.
	// 3. draw all elements onto the temporary buffer.
	// 4. run any functions bound to follow drawing.
	// 5. draw the buffer's data at the viewport's position to the screen.
	// 6. publish the screen to display in window.
	go func() {
		<-drawChannel
		lastTime := time.Now()
		text := render.NewStaticText("Look at me!", 10, 20)
		render.StaticDraw(text, 60000)
		for {
			dlog.Verb("Draw Loop")
		drawSelect:
			select {

			case <-drawChannel:
				dlog.Verb("Got something from draw channel")
				for {
					select {
					case <-drawChannel:
						render.StaticDraw(text, 60000)
						break drawSelect
					case viewPoint := <-viewportChannel:
						dlog.Verb("Got something from viewport channel (waiting on draw)")
						updateScreen(viewPoint[0], viewPoint[1])
					}

				}
			case viewPoint := <-viewportChannel:
				dlog.Verb("Got something from viewport channel")
				updateScreen(viewPoint[0], viewPoint[1])
			default:

				eb = event.GetEventBus()
				// for x := 0; x < render.DirtyZonesX; x++ {
				// 	for y := 0; y < render.DirtyZonesY; y++ {
				// 		if render.IsDirty(x*render.DirtyWidth, y*render.DirtyHeight) {
				// 			draw.Draw(b.RGBA(), render.DirtyBounds, imageBlack, image.Point{64 * x, 64 * y}, screen.Src)
				// 		}
				// 	}
				// }
				draw.Draw(b.RGBA(), b.Bounds(), image.Black, image.Point{0, 0}, screen.Src)

				eb.Trigger("PreDraw", nil)
				render.DrawHeap(b, ViewX, ViewY, ScreenWidth, ScreenHeight)
				draw.Draw(winBuffer.RGBA(), winBuffer.Bounds(), b.RGBA(), image.Point{ViewX, ViewY}, screen.Src)
				render.DrawStaticHeap(winBuffer)
				eb.Trigger("PostDraw", b)

				w.Upload(image.Point{0, 0}, winBuffer, winBuffer.Bounds())
				w.Publish()

				timeSince := 1000000000.0 / float64(time.Since(lastTime).Nanoseconds())
				text.SetText(strconv.Itoa(int(timeSince)))
				lastTime = time.Now()
			}
		}
	}()

	// The logical loop.
	// In order, it waits on receiving a signal to begin a logical frame.
	// It then runs any functions bound to when a frame begins.
	// It then allows a scene to perform it's loop operation.
	// It then runs any functions bound to when a frame ends.
	for {
		for runEventLoop {
			<-frameCh
			eb.Trigger("EnterFrame", nil)
			eb.Trigger("ExitFrame", nil)
			sceneCh <- true
		}
	}
}

func GetScreen() draw.Image {
	return b.RGBA()
}
<commit_msg>Trying to shrink player sight gradient and trying out multiple draw buffers. We could probably figure out the right number of buffers to use through the number of cores available on the machine? Or based on how fast it can wipe a screen buffer to black again.<commit_after>// Package plastic is a game engine...
package plastic

import (
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/audio"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/collision"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/dlog"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/event"
	pmouse "bitbucket.org/oakmoundstudio/plasticpiston/plastic/mouse"
	"bitbucket.org/oakmoundstudio/plasticpiston/plastic/render"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"math/rand"
	"os"
	"path/filepath"
	"strconv"
	"time"

	"golang.org/x/exp/shiny/driver"
	"golang.org/x/exp/shiny/screen"
	"golang.org/x/mobile/event/key"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/mouse"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
)

var (
	initCh               = make(chan bool)
	sceneCh              = make(chan bool)
	skipSceneCh          = make(chan bool)
	quitCh               = make(chan bool)
	drawChannel          = make(chan bool)
	debugResetCh         = make(chan bool)
	b1Cleaned            = make(chan bool)
	b2Cleaned            = make(chan bool)
	viewportChannel      = make(chan [2]int)
	drawInit             = false
	runEventLoop         = false
	ScreenWidth          int
	ScreenHeight         int
	press                = key.DirPress
	release              = key.DirRelease
	black                = color.RGBA{0x00, 0x00, 0x00, 0xff}
	b                    screen.Buffer
	b2                   screen.Buffer
	winBuffer            screen.Buffer
	eb                   *event.EventBus
	esc                  = false
	l_debug              = false
	wd, _                = os.Getwd()
	imageDir             string
	audioDir             string
	debugResetInProgress = false
	globalFirstScene     string
	startupLoadComplete  bool
	imageBlack           = image.Black
	zeroPoint            = image.Point{0, 0}
)

// Init initializes the plastic engine.
// It spawns off an event loop of several goroutines
// and loops through scenes after initalization.
func Init(firstScene string) {
	dlog.CreateLogFile()

	err := loadDefaultConf()

	// Set variables from conf file
	dlog.SetStringDebugLevel(conf.Debug.Level)
	dlog.SetDebugFilter(conf.Debug.Filter)

	if err != nil {
		dlog.Verb(err)
	}

	ScreenWidth = conf.Screen.Width
	ScreenHeight = conf.Screen.Height

	imageDir = filepath.Join(filepath.Dir(wd),
		conf.Assets.AssetPath,
		conf.Assets.ImagePath)
	audioDir = filepath.Join(filepath.Dir(wd),
		conf.Assets.AssetPath,
		conf.Assets.AudioPath)

	render.SetFontDefaults(wd, conf.Assets.AssetPath, conf.Assets.FontPath,
		conf.Font.Hinting, conf.Font.Color, conf.Font.File, conf.Font.Size,
		conf.Font.DPI)
	// END of loading variables from configuration

	// Init various engine pieces
	collision.Init()
	pmouse.Init()
	render.InitDrawHeap()
	audio.InitWinAudio()

	// Seed the rng
	curSeed := time.Now().UTC().UnixNano()
	//curSeed = 1471104995917281000
	rand.Seed(curSeed)
	dlog.Info("The seed is:", curSeed)
	fmt.Println("\n~~~~~~~~~~~~~~~\nTHE SEED IS:", curSeed, "\n~~~~~~~~~~~~~~~\n")

	// Load in assets
	go func() {
		err = render.BatchLoad(imageDir)
		if err != nil {
			dlog.Error(err)
			return
		}

		err = audio.BatchLoad(audioDir)
		if err != nil {
			dlog.Error(err)
		}

		startupLoadComplete = true
	}()

	// Spawn off event loop goroutines
	go driver.Main(eventLoop)

	go DebugConsole(debugResetCh, skipSceneCh)

	prevScene := ""
	sceneMap[firstScene].active = true

	<-initCh
	close(initCh)

	// Loop through scenes
	runEventLoop = true
	globalFirstScene = firstScene
	scene := "loading"
	var data interface{} = nil
	dlog.Info("First Scene Start")
	for {
		ViewX = 0
		ViewY = 0
		useViewBounds = false
		dlog.Info("~~~~~~~~~~~Scene Start~~~~~~~~~")
		sceneMap[scene].start(prevScene, data)
		// Send a signal to resume (or begin) drawing
		drawChannel <- true

		cont := true
		for cont {
			select {
			// The quit channel represents a signal
			// for the engine to stop.
			case <-quitCh:
				return
			case <-sceneCh:
				cont = sceneMap[scene].loop()
			case <-skipSceneCh:
				cont = false
			}
		}
		dlog.Info("~~~~~~~~Scene End~~~~~~~~~~")
		prevScene = scene

		// Send a signal to stop drawing
		drawChannel <- true

		// Reset transient portions of the engine
		event.ResetEntities()
		event.ResetEventBus()
		render.ResetDrawHeap()
		collision.Clear()
		pmouse.Clear()
		render.PreDraw(0, nil)

		scene, data = sceneMap[scene].end()

		eb = event.GetEventBus()
		if !debugResetInProgress {
			debugResetInProgress = true
			go func() {
				debugResetCh <- true
				debugResetInProgress = false

			}()
		}
	}
}

func eventLoop(s screen.Screen) {

	// The event loop requires information about
	// the size of the world and screen that is
	// being dealt with, and so initializes it here.
	//
	// Todo: add world size to config
	b, _ = s.NewBuffer(image.Point{4000, 4000})
	b2, _ = s.NewBuffer(image.Point{4000, 4000})
	winBuffer, _ = s.NewBuffer(image.Point{ScreenWidth, ScreenHeight})
	w, err := s.NewWindow(&screen.NewWindowOptions{ScreenWidth, ScreenHeight})
	if err != nil {
		dlog.Error(err)
	}
	defer w.Release()

	// This initialization happens here on account of font's initialization
	// requiring a buffer to draw to. Can probably change in the future.
	render.InitFont(b, winBuffer)

	eb = event.GetEventBus()

	// Todo: add frame rate to config
	frameRate := 60
	frameCh := make(chan bool)

	// This goroutine maintains a logical framerate
	go func(frameCh chan bool, frameRate int64) {
		c := time.Tick(time.Second / time.Duration(frameRate))
		for range c {
			frameCh <- true
		}
	}(frameCh, int64(frameRate))

	// Native go event handler
	go func() {
		for {
			e := w.NextEvent()
			switch e := e.(type) {

			// We only currently respond to death lifecycle events.
			case lifecycle.Event:
				if e.To == lifecycle.StageDead {
					quitCh <- true
					return
				}

			// Send key events
			//
			// Key events have two varieties:
			// The "KeyDown" and "KeyUp" events, which trigger for all keys
			// and specific "KeyDown$key", etc events which trigger only for $key.
			// The specific key that is pressed is passed as the data interface for
			// the former events, but not for the latter.
			case key.Event:
				k := GetKeyBind(e.Code.String()[4:])
				if e.Direction == press {
					fmt.Println("--------------------", e.Code.String()[4:], k)
					setDown(k)
					eb.Trigger("KeyDown", k)
					eb.Trigger("KeyDown"+k, nil)
				} else if e.Direction == release {
					setUp(k)
					eb.Trigger("KeyUp", k)
					eb.Trigger("KeyUp"+k, nil)
				}

			// Send mouse events
			//
			// Mouse events are parsed based on their button
			// and direction into an event name and then triggered:
			// 'MousePress', 'MouseRelease', 'MouseScrollDown', 'MouseScrollUp', and 'MouseDrag'
			//
			// The basic event name is meant for entities which
			// want to respond to the mouse event happening -anywhere-.
			//
			// For events which have mouse collision enabled, they'll recieve
			// $eventName+"On" when the event occurs within their collision area.
			//
			// Mouse events all recieve an x, y, and button string.
			case mouse.Event:
				button := pmouse.GetMouseButton(int32(e.Button))
				dlog.Verb("Mouse direction ", e.Direction.String(), " Button ", button)
				mevent := pmouse.MouseEvent{e.X, e.Y, button}
				var eventName string
				if e.Direction == mouse.DirPress {
					setDown(button)
					eventName = "MousePress"
				} else if e.Direction == mouse.DirRelease {
					setUp(button)
					eventName = "MouseRelease"
				} else if e.Button == -2 {
					eventName = "MouseScrollDown"
				} else if e.Button == -1 {
					eventName = "MouseScrollUp"
				} else {
					eventName = "MouseDrag"
				}
				eb.Trigger(eventName, mevent)
				pmouse.Propagate(eventName+"On", mevent)

			// I don't really know what a paint event is to be honest.
			// We hypothetically don't allow the user to manually resize
			// their window, so we don't do anything special for such events.
			case size.Event, paint.Event:
			case error:
				dlog.Error(e)
			}

			// This is a hardcoded quit function bound to the escape key.
			if IsDown("Escape") {
				if esc {
					dlog.Warn("Quiting plastic from holding ESCAPE")
					w.Send(lifecycle.Event{0, 0, nil})
				}
				esc = true
			} else {
				esc = false
			}
		}
	}()

	// This sends a signal to initiate the first scene
	initCh <- true
	go func() {
		b1Cleaned <- true
	}()
	// The draw loop
	// Unless told to stop, the draw channel will repeatedly
	// 1. draw black to a temporary buffer
	// 2. run any functions bound to precede drawing.
	// 3. draw all elements onto the temporary buffer.
	// 4. run any functions bound to follow drawing.
	// 5. draw the buffer's data at the viewport's position to the screen.
	// 6. publish the screen to display in window.
	go func() {
		<-drawChannel
		lastTime := time.Now()
		text := render.NewStaticText("Look at me!", 10, 20)
		render.StaticDraw(text, 60000)
		for {
			dlog.Verb("Draw Loop")
		drawSelect:
			select {

			case <-drawChannel:
				dlog.Verb("Got something from draw channel")
				for {
					select {
					case <-drawChannel:
						render.StaticDraw(text, 60000)
						break drawSelect
					case viewPoint := <-viewportChannel:
						dlog.Verb("Got something from viewport channel (waiting on draw)")
						updateScreen(viewPoint[0], viewPoint[1])
					}

				}
			case viewPoint := <-viewportChannel:
				dlog.Verb("Got something from viewport channel")
				updateScreen(viewPoint[0], viewPoint[1])
			default:

				eb = event.GetEventBus()
				// for x := 0; x < render.DirtyZonesX; x++ {
				// 	for y := 0; y < render.DirtyZonesY; y++ {
				// 		if render.IsDirty(x*render.DirtyWidth, y*render.DirtyHeight) {
				// 			draw.Draw(b.RGBA(), render.DirtyBounds, imageBlack, image.Point{64 * x, 64 * y}, screen.Src)
				// 		}
				// 	}
				// }
				go func() {
					draw.Draw(b2.RGBA(), b2.Bounds(), imageBlack, zeroPoint, screen.Src)
					b2Cleaned <- true
				}()

				<-b1Cleaned

				eb.Trigger("PreDraw", nil)
				render.DrawHeap(b, ViewX, ViewY, ScreenWidth, ScreenHeight)
				draw.Draw(winBuffer.RGBA(), winBuffer.Bounds(), b.RGBA(), image.Point{ViewX, ViewY}, screen.Src)
				render.DrawStaticHeap(winBuffer)
				eb.Trigger("PostDraw", b)

				w.Upload(image.Point{0, 0}, winBuffer, winBuffer.Bounds())
				w.Publish()

				timeSince := 1000000000.0 / float64(time.Since(lastTime).Nanoseconds())
				text.SetText(strconv.Itoa(int(timeSince)))
				lastTime = time.Now()

				go func() {
					draw.Draw(b.RGBA(), b.Bounds(), imageBlack, zeroPoint, screen.Src)
					b1Cleaned <- true
				}()

				<-b2Cleaned

				eb.Trigger("PreDraw", nil)
				render.DrawHeap(b2, ViewX, ViewY, ScreenWidth, ScreenHeight)
				draw.Draw(winBuffer.RGBA(), winBuffer.Bounds(), b2.RGBA(), image.Point{ViewX, ViewY}, screen.Src)
				render.DrawStaticHeap(winBuffer)
				eb.Trigger("PostDraw", b2)

				w.Upload(image.Point{0, 0}, winBuffer, winBuffer.Bounds())
				w.Publish()

				timeSince = 1000000000.0 / float64(time.Since(lastTime).Nanoseconds())
				text.SetText(strconv.Itoa(int(timeSince)))
				lastTime = time.Now()
			}
		}
	}()

	// The logical loop.
	// In order, it waits on receiving a signal to begin a logical frame.
	// It then runs any functions bound to when a frame begins.
	// It then allows a scene to perform it's loop operation.
	// It then runs any functions bound to when a frame ends.
	for {
		for runEventLoop {
			<-frameCh
			eb.Trigger("EnterFrame", nil)
			eb.Trigger("ExitFrame", nil)
			sceneCh <- true
		}
	}
}

func GetScreen() draw.Image {
	return b.RGBA()
}
<|endoftext|>


<commit_before>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libgit

import (
	"context"

	billy "gopkg.in/src-d/go-billy.v4"
	gogit "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
	"gopkg.in/src-d/go-git.v4/storage/filesystem"
)

// repoFromStorageAndWorktree returns a repo instance where the dotgit
// storage layer points to the bare repo represented by `repoFS`, but
// the worktree storage layer uses `worktreeFS`.  This is useful for
// checking out the git repo without having to read and copy the
// complete git history.  The index and the HEAD commit of the
// worktree are stored in `worktreeFS/.git`.  The caller should make
// no modifying calls into the repo, other than checkout/reset-related
// calls.
//
// For the convenience of the caller, it also returns `currentHead`,
// which is the current commit at the HEAD of `branch`, according to
// the bare repo represented by `repoFS`.  In addition, it returns
// `repoHead`, which is the current commit that the worktree has or
// `plumbing.ZeroHash` if the worktree is uninitialized.
func repoFromStorageAndWorktree(
	repoFS billy.Filesystem, worktreeFS billy.Filesystem,
	branch plumbing.ReferenceName) (
	repo *gogit.Repository, currentHead, repoHead plumbing.Hash, err error) {
	repoStorer, err := filesystem.NewStorage(repoFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	// Wrap it in an on-demand storer, so we don't try to read all the
	// objects of big repos into memory at once.
	storage, err := NewOnDemandStorer(repoStorer)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	currentHeadRef, err := storer.ResolveReference(storage, branch)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	err = worktreeFS.MkdirAll(".git", 0600)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	wtDotgitFS, err := worktreeFS.Chroot(".git")
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	wtDotgit, err := filesystem.NewStorage(wtDotgitFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	repoHead = plumbing.ZeroHash
	repoHeadRef, err := storer.ResolveReference(wtDotgit, plumbing.HEAD)
	if err == nil {
		repoHead = repoHeadRef.Hash()
	}

	wtStorage := &worktreeStorer{storage, storage, wtDotgit}

	// Override the `IndexStorer` methods with an `IndexStorage` that
	// points to worktreeFS/.git.  And also also reroutes HEAD
	// reference management to worktreeFS/.git as well.
	repo, err = gogit.Open(wtStorage, worktreeFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	return repo, currentHeadRef.Hash(), repoHead, nil
}

// Reset checks out a repo from a billy filesystem, into another billy
// filesystem.  However, it doesn't make a true git clone, for two
// reasons.
//
// 1. This will be used to clone KBFS repos, and go-git doesn't
//    natively understand the keybase:// protocol.  There might be a
//    way around this with the `InstallProtocol` go-git method, but I
//    haven't investigated that yet and it would be some extra work.
//
// 2. These clones are intended for autogit, which will display them
//    as read-only directories.  They do not need to be real git
//    repos, and so there's no need to copy object files or much else
//    in the .git repo.  All we really need is an index, to compute
//    the differences.  Making a full git clone, or even a "shallow"
//    git clone, would be wasteful in that it would read and write way
//    more data than we really need.
//
// The resulting checkout in `worktreeFS` is therefore not a functional
// git repo.  The caller should only interact with `worktreeFS` in a
// read-only way, and should not attempt any git operations on it.
func Reset(
	ctx context.Context, repoFS billy.Filesystem, worktreeFS billy.Filesystem,
	branch plumbing.ReferenceName) error {
	repo, currentHead, repoHead, err := repoFromStorageAndWorktree(
		repoFS, worktreeFS, branch)
	if err != nil {
		return err
	}

	// Quickly check if the repo is already up-to-date.
	if repoHead == currentHead {
		return nil
	}

	wt, err := repo.Worktree()
	if err != nil {
		return err
	}

	return wt.Reset(&gogit.ResetOptions{
		Commit: currentHead,
		Mode:   gogit.HardReset,
	})
}
<commit_msg>libgit: remove HEAD file on reset error, and rename vars<commit_after>// Copyright 2017 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.

package libgit

import (
	"context"
	"fmt"

	billy "gopkg.in/src-d/go-billy.v4"
	gogit "gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/storer"
	"gopkg.in/src-d/go-git.v4/storage/filesystem"
)

// repoFromStorageAndWorktree returns a repo instance where the dotgit
// storage layer points to the bare repo represented by `repoFS`, but
// the worktree storage layer uses `worktreeFS`.  This is useful for
// checking out the git repo without having to read and copy the
// complete git history.  The index and the HEAD commit of the
// worktree are stored in `worktreeFS/.git`.  The caller should make
// no modifying calls into the repo, other than checkout/reset-related
// calls.
//
// For the convenience of the caller, it also returns `repoHead`,
// which is the current commit at the HEAD of `branch`, according to
// the bare repo represented by `repoFS`.  In addition, it returns
// `worktreeHead`, which is the current commit that the worktree has or
// `plumbing.ZeroHash` if the worktree is uninitialized.
func repoFromStorageAndWorktree(
	repoFS billy.Filesystem, worktreeFS billy.Filesystem,
	branch plumbing.ReferenceName) (
	repo *gogit.Repository, repoHead, worktreeHead plumbing.Hash, err error) {
	repoStorer, err := filesystem.NewStorage(repoFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	// Wrap it in an on-demand storer, so we don't try to read all the
	// objects of big repos into memory at once.
	storage, err := NewOnDemandStorer(repoStorer)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	repoHeadRef, err := storer.ResolveReference(storage, branch)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}

	err = worktreeFS.MkdirAll(".git", 0600)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	wtDotgitFS, err := worktreeFS.Chroot(".git")
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	wtDotgit, err := filesystem.NewStorage(wtDotgitFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	worktreeHead = plumbing.ZeroHash
	worktreeHeadRef, err := storer.ResolveReference(wtDotgit, plumbing.HEAD)
	if err == nil {
		worktreeHead = worktreeHeadRef.Hash()
	}

	wtStorage := &worktreeStorer{storage, storage, wtDotgit}

	// Override the `IndexStorer` methods with an `IndexStorage` that
	// points to worktreeFS/.git.  And also also reroutes HEAD
	// reference management to worktreeFS/.git as well.
	repo, err = gogit.Open(wtStorage, worktreeFS)
	if err != nil {
		return nil, plumbing.ZeroHash, plumbing.ZeroHash, err
	}
	return repo, repoHeadRef.Hash(), worktreeHead, nil
}

// Reset checks out a repo from a billy filesystem, into another billy
// filesystem.  However, it doesn't make a true git clone, for two
// reasons.
//
// 1. This will be used to clone KBFS repos, and go-git doesn't
//    natively understand the keybase:// protocol.  There might be a
//    way around this with the `InstallProtocol` go-git method, but I
//    haven't investigated that yet and it would be some extra work.
//
// 2. These clones are intended for autogit, which will display them
//    as read-only directories.  They do not need to be real git
//    repos, and so there's no need to copy object files or much else
//    in the .git repo.  All we really need is an index, to compute
//    the differences.  Making a full git clone, or even a "shallow"
//    git clone, would be wasteful in that it would read and write way
//    more data than we really need.
//
// The resulting checkout in `worktreeFS` is therefore not a functional
// git repo.  The caller should only interact with `worktreeFS` in a
// read-only way, and should not attempt any git operations on it.
func Reset(
	ctx context.Context, repoFS billy.Filesystem, worktreeFS billy.Filesystem,
	branch plumbing.ReferenceName) error {
	repo, repoHead, worktreeHead, err := repoFromStorageAndWorktree(
		repoFS, worktreeFS, branch)
	if err != nil {
		return err
	}

	// Quickly check if the repo is already up-to-date.
	if worktreeHead == repoHead {
		return nil
	}

	wt, err := repo.Worktree()
	if err != nil {
		return err
	}

	defer func() {
		if err != nil {
			// Clean up the worktree HEAD so the next update attempt
			// won't get optimized away.
			_ = worktreeFS.Remove(fmt.Sprintf(".git/%s", plumbing.HEAD))
		}
	}()

	return wt.Reset(&gogit.ResetOptions{
		Commit: repoHead,
		Mode:   gogit.HardReset,
	})
}
<|endoftext|>


<commit_before>package main

import (
    "fmt"
    "golang.org/x/net/html"
    "net/http"
    "os"
//    "strings"
)

// Helper function to pull the href attribute from a Token
func getHref(t html.Token) (ok bool, href string) {
    // Iterate over all of the Token's attributes until we find an "href"
    for _, a := range t.Attr {
        if a.Key == "href" {
            href = a.Val
            ok = true
        }
    }

    // "bare" return will return the variables (ok, href) as defined in
    // the function definition
    return
}

// Extract all http** links from a given webpage
func crawl(url string, ch chan string, chFinished chan bool) {
    resp, err := http.Get(url)

    defer func() {
        // Notify that we're done after this function
        chFinished <- true
    }()

    if err != nil {
        fmt.Println("ERROR: Failed to crawl \"" + url + "\"")
        return
    }
    fmt.Println("\t crawled \"" + url + "\"")

    b := resp.Body
    defer b.Close() // close Body when the function returns

    z := html.NewTokenizer(b)

    for {
        tt := z.Next()

        switch {
        case tt == html.ErrorToken:
            // End of the document, we're done
            return
        case tt == html.StartTagToken:
            t := z.Token()

            // Check if the token is an <a> tag
            isAnchor := t.Data == "a"
            if !isAnchor {
                continue
            }

            // Extract the href value, if there is one
            ok, url := getHref(t)
            if !ok {
                continue
            }

            ch <- url
        }
    }
}

func main() {
    foundUrls := make(map[string]bool)
    seedUrls := os.Args[1:]

    // Channels
    chUrls := make(chan string)
    chFinished := make(chan bool)

    // Kick off the crawl process (concurrently)
    for _, url := range seedUrls {
        go crawl(url, chUrls, chFinished)
    }

    // Subscribe to both channels
    for c := 0; c < len(seedUrls); {
        select {
        case url := <-chUrls:
            foundUrls[url] = true
        case <-chFinished:
            c++
        }
    }

    // We're done! Print the results...

    fmt.Println("\nFound", len(foundUrls), "unique urls:\n")

    for url, _ := range foundUrls {
        fmt.Println(" - " + url)
    }

    close(chUrls)
}
<commit_msg>now we're traversing all the links in the site we start in<commit_after>package main

import (
	"fmt"
	"golang.org/x/net/html"
	"net/http"
	"net/url"
	"os"
	"strings"
)

// Helper function to pull the href attribute from a Token
func getHref(t html.Token) (ok bool, href string) {
	// Iterate over all of the Token's attributes until we find an "href"
	for _, a := range t.Attr {
		if a.Key == "href" {
			href = a.Val
			ok = true
		}
	}

	// "bare" return will return the variables (ok, href) as defined in
	// the function definition
	return
}

type UrlResponse struct {
	url string
	code int
}

type NewUrl struct {
	from string
	url string
}

// Extract all http** links from a given webpage
func crawl(reqUrl string, ch chan NewUrl, chFinished chan UrlResponse) {
	base, err := url.Parse(reqUrl)
	reply := UrlResponse {
		url: reqUrl,
		code: 999,
	}
	if err != nil {
		fmt.Println("ERROR: failed to Parse \"" + reqUrl + "\"")
		chFinished <- reply
		return
	}
	resp, err := http.Get(reqUrl)
	if err != nil {
		fmt.Println("ERROR: Failed to crawl \"" + reqUrl + "\"  " + err.Error())
		chFinished <- reply
		return
	}
	defer func() {
		// Notify that we're done after this function
		reply.code = resp.StatusCode
		chFinished <- reply
	}()

	fmt.Println("\t crawled \"" + reqUrl + "\"")

	b := resp.Body
	defer b.Close() // close Body when the function returns

	z := html.NewTokenizer(b)

	for {
		tt := z.Next()

		switch {
		case tt == html.ErrorToken:
			// End of the document, we're done
			return
		case tt == html.StartTagToken:
			t := z.Token()

			// Check if the token is an <a> tag
			isAnchor := t.Data == "a"
			if !isAnchor {
				continue
			}

			// Extract the href value, if there is one
			ok, newUrl := getHref(t)
			if !ok {
				continue
			}
			u, e := url.Parse(newUrl)
			if e != nil {
				fmt.Println("ERROR: failed to Parse \"" + newUrl + "\"")
				continue
			}
			new := NewUrl{
				from: reqUrl,
				url: base.ResolveReference(u).String(),
				}
			ch <- new
		}
	}
}

func main() {
	foundUrls := make(map[string]int)
	seedUrls := os.Args[1:]

	// Channels
	chUrls := make(chan NewUrl)
	chFinished := make(chan UrlResponse)

	// Kick off the crawl process (concurrently)
	for _, url := range seedUrls {
		foundUrls[url] = 0
		go crawl(url, chUrls, chFinished)
	}

	// Subscribe to both channels
	count := len(seedUrls)
	for c := 0; c < count; {
		select {
		case new := <-chUrls:
			if _, ok := foundUrls[new.url] ; !ok {
				// TODO: need to stick to only the site we're testing plus one
				if strings.HasPrefix(new.from, seedUrls[0]) {
				count++
				// TODO: you're kidding right - lets not make an infinite number of cawlers?
				go crawl(new.url, chUrls, chFinished)
				foundUrls[new.url] = 0
				}
			}
		case ret := <-chFinished:
			foundUrls[ret.url] = ret.code
			c++
		}
	}

	// We're done! Print the results...
	for url, code := range foundUrls {
		fmt.Printf(" - %d: %s\n", code, url)
	}
	fmt.Println("\nFound", len(foundUrls), "unique urls:\n")

	close(chUrls)
}
<|endoftext|>


<commit_before>package readline

const (
	K_BACKSPACE  = "BACKSPACE"
	K_CAPSLOCK   = "CAPSLOCK"
	K_CLEAR      = "CLEAR"
	K_CTRL       = "CTRL"
	K_CTRL_A     = "C_A"
	K_CTRL_B     = "C_B"
	K_CTRL_BREAK = "C_BREAK"
	K_CTRL_C     = "C_C"
	K_CTRL_D     = "C_D"
	K_CTRL_E     = "C_E"
	K_CTRL_F     = "C_F"
	K_CTRL_G     = "C_G"
	K_CTRL_H     = "C_H"
	K_CTRL_I     = "C_I"
	K_CTRL_J     = "C_J"
	K_CTRL_K     = "C_K"
	K_CTRL_L     = "C_L"
	K_CTRL_M     = "C_M"
	K_CTRL_N     = "C_N"
	K_CTRL_O     = "C_O"
	K_CTRL_P     = "C_P"
	K_CTRL_Q     = "C_Q"
	K_CTRL_R     = "C_R"
	K_CTRL_S     = "C_S"
	K_CTRL_T     = "C_T"
	K_CTRL_U     = "C_U"
	K_CTRL_V     = "C_V"
	K_CTRL_W     = "C_W"
	K_CTRL_X     = "C_X"
	K_CTRL_Y     = "C_Y"
	K_CTRL_Z     = "C_Z"
	K_DELETE     = "DEL"
	K_DOWN       = "DOWN"
	K_END        = "END"
	K_ENTER      = "ENTER"
	K_ESCAPE     = "ESCAPE"
	K_F1         = "F1"
	K_F10        = "F10"
	K_F11        = "F11"
	K_F12        = "F12"
	K_F13        = "F13"
	K_F14        = "F14"
	K_F15        = "F15"
	K_F16        = "F16"
	K_F17        = "F17"
	K_F18        = "F18"
	K_F19        = "F19"
	K_F2         = "F2"
	K_F20        = "F20"
	K_F21        = "F21"
	K_F22        = "F22"
	K_F23        = "F23"
	K_F24        = "F24"
	K_F3         = "F3"
	K_F4         = "F4"
	K_F5         = "F5"
	K_F6         = "F6"
	K_F7         = "F7"
	K_F8         = "F8"
	K_F9         = "F9"
	K_HOME       = "HOME"
	K_LEFT       = "LEFT"
	K_PAGEDOWN   = "PAGEDOWN"
	K_PAGEUP     = "PAGEUP"
	K_PAUSE      = "PAUSE"
	K_RIGHT      = "RIGHT"
	K_SHIFT      = "SHIFT"
	K_UP         = "UP"
	K_ALT_A      = "M_A"
	K_ALT_B      = "M_B"
	K_ALT_BREAK  = "M_BREAK"
	K_ALT_C      = "M_C"
	K_ALT_D      = "M_D"
	K_ALT_E      = "M_E"
	K_ALT_F      = "M_F"
	K_ALT_G      = "M_G"
	K_ALT_H      = "M_H"
	K_ALT_I      = "M_I"
	K_ALT_J      = "M_J"
	K_ALT_K      = "M_K"
	K_ALT_L      = "M_L"
	K_ALT_M      = "M_M"
	K_ALT_N      = "M_N"
	K_ALT_O      = "M_O"
	K_ALT_P      = "M_P"
	K_ALT_Q      = "M_Q"
	K_ALT_R      = "M_R"
	K_ALT_S      = "M_S"
	K_ALT_T      = "M_T"
	K_ALT_U      = "M_U"
	K_ALT_V      = "M_V"
	K_ALT_W      = "M_W"
	K_ALT_X      = "M_X"
	K_ALT_Y      = "M_Y"
	K_ALT_Z      = "M_Z"
)

const (
	F_ACCEPT_LINE          = "ACCEPT_LINE"
	F_BACKWARD_CHAR        = "BACKWARD_CHAR"
	F_BACKWARD_DELETE_CHAR = "BACKWARD_DELETE_CHAR"
	F_BEGINNING_OF_LINE    = "BEGINNING_OF_LINE"
	F_CLEAR_SCREEN         = "CLEAR_SCREEN"
	F_DELETE_CHAR          = "DELETE_CHAR"
	F_DELETE_OR_ABORT      = "DELETE_OR_ABORT"
	F_END_OF_LINE          = "END_OF_LINE"
	F_FORARD_CHAR          = "FORWARD_CHAR"
	F_HISTORY_DOWN         = "HISTORY_DOWN"
	F_HISTORY_UP           = "HISTORY_UP"
	F_INTR                 = "INTR"
	F_ISEARCH_BACKWARD     = "ISEARCH_BACKWARD"
	F_KILL_LINE            = "KILL_LINE"
	F_KILL_WHOLE_LINE      = "KILL_WHOLE_LINE"
	F_PASS                 = "PASS"
	F_REPAINT_ON_NEWLINE   = "REPAINT_ON_NEWLINE"
	F_SWAPCHAR             = "SWAPCHAR"
	F_UNIX_LINE_DISCARD    = "UNIX_LINE_DISCARD"
	F_UNIX_WORD_RUBOUT     = "UNIX_WORD_RUBOUT"
	F_YANK                 = "YANK"
	F_YANK_WITH_QUOTE      = "YANK_WITH_QUOTE"
)

var name2char = map[string]rune{
	K_BACKSPACE: '\b',
	K_CTRL_A:    rune('a' & 0x1F),
	K_CTRL_B:    rune('b' & 0x1F),
	K_CTRL_C:    rune('c' & 0x1F),
	K_CTRL_D:    rune('d' & 0x1F),
	K_CTRL_E:    rune('e' & 0x1F),
	K_CTRL_F:    rune('f' & 0x1F),
	K_CTRL_G:    rune('g' & 0x1F),
	K_CTRL_H:    rune('h' & 0x1F),
	K_CTRL_I:    rune('i' & 0x1F),
	K_CTRL_J:    rune('j' & 0x1F),
	K_CTRL_K:    rune('k' & 0x1F),
	K_CTRL_L:    rune('l' & 0x1F),
	K_CTRL_M:    rune('m' & 0x1F),
	K_CTRL_N:    rune('n' & 0x1F),
	K_CTRL_O:    rune('o' & 0x1F),
	K_CTRL_P:    rune('p' & 0x1F),
	K_CTRL_Q:    rune('q' & 0x1F),
	K_CTRL_R:    rune('r' & 0x1F),
	K_CTRL_S:    rune('s' & 0x1F),
	K_CTRL_T:    rune('t' & 0x1F),
	K_CTRL_U:    rune('u' & 0x1F),
	K_CTRL_V:    rune('v' & 0x1F),
	K_CTRL_W:    rune('w' & 0x1F),
	K_CTRL_X:    rune('x' & 0x1F),
	K_CTRL_Y:    rune('y' & 0x1F),
	K_CTRL_Z:    rune('z' & 0x1F),
	K_DELETE:    '\x7F',
	K_ENTER:     '\r',
	K_ESCAPE:    rune('[' & 0x1F),
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx

var name2scan = map[string]uint16{
	K_CAPSLOCK:   0x14,
	K_CLEAR:      0x0C,
	K_CTRL:       0x11,
	K_CTRL_BREAK: 0x03,
	K_DELETE:     0x2E,
	K_DOWN:       0x28,
	K_END:        0x23,
	K_F10:        0x79,
	K_F11:        0x7A,
	K_F12:        0x7B,
	K_F13:        0x7C,
	K_F14:        0x7D,
	K_F15:        0x7E,
	K_F16:        0x7F,
	K_F17:        0x80,
	K_F18:        0x81,
	K_F19:        0x82,
	K_F1:         0x70,
	K_F20:        0x83,
	K_F21:        0x84,
	K_F22:        0x85,
	K_F23:        0x86,
	K_F24:        0x87,
	K_F2:         0x71,
	K_F3:         0x72,
	K_F4:         0x73,
	K_F5:         0x74,
	K_F6:         0x75,
	K_F7:         0x76,
	K_F8:         0x77,
	K_F9:         0x78,
	K_HOME:       0x24,
	K_LEFT:       0x25,
	K_PAGEDOWN:   0x22,
	K_PAGEUP:     0x21,
	K_PAUSE:      0x13,
	K_RIGHT:      0x27,
	K_SHIFT:      0x10,
	K_UP:         0x26,
}

var name2alt = map[string]uint16{
	K_ALT_A: 0x41,
	K_ALT_B: 0x42,
	K_ALT_C: 0x43,
	K_ALT_D: 0x44,
	K_ALT_E: 0x45,
	K_ALT_F: 0x46,
	K_ALT_G: 0x47,
	K_ALT_H: 0x48,
	K_ALT_I: 0x49,
	K_ALT_J: 0x4A,
	K_ALT_K: 0x4B,
	K_ALT_L: 0x4C,
	K_ALT_M: 0x4D,
	K_ALT_N: 0x4E,
	K_ALT_O: 0x4F,
	K_ALT_P: 0x50,
	K_ALT_Q: 0x51,
	K_ALT_R: 0x52,
	K_ALT_S: 0x53,
	K_ALT_T: 0x54,
	K_ALT_U: 0x55,
	K_ALT_V: 0x56,
	K_ALT_W: 0x57,
	K_ALT_X: 0x58,
	K_ALT_Y: 0x59,
	K_ALT_Z: 0x5A,
}

var NAME2FUNC = map[string]func(*Buffer) Result{
	F_ACCEPT_LINE:          KeyFuncEnter,
	F_BACKWARD_CHAR:        KeyFuncBackword,
	F_BACKWARD_DELETE_CHAR: KeyFuncBackSpace,
	F_BEGINNING_OF_LINE:    KeyFuncHead,
	F_CLEAR_SCREEN:         KeyFuncCLS,
	F_DELETE_CHAR:          KeyFuncDelete,
	F_DELETE_OR_ABORT:      KeyFuncDeleteOrAbort,
	F_END_OF_LINE:          KeyFuncTail,
	F_FORARD_CHAR:          KeyFuncForward,
	F_HISTORY_DOWN:         KeyFuncHistoryDown,
	F_HISTORY_UP:           KeyFuncHistoryUp,
	F_INTR:                 KeyFuncIntr,
	F_ISEARCH_BACKWARD:     KeyFuncIncSearch,
	F_KILL_LINE:            KeyFuncClearAfter,
	F_KILL_WHOLE_LINE:      KeyFuncClear,
	F_PASS:                 KeyFuncPass,
	F_UNIX_LINE_DISCARD:    KeyFuncClearBefore,
	F_UNIX_WORD_RUBOUT:     KeyFuncWordRubout,
	F_YANK:                 KeyFuncPaste,
	F_YANK_WITH_QUOTE:      KeyFuncPasteQuote,
	F_SWAPCHAR:             KeyFuncSwapChar,
	F_REPAINT_ON_NEWLINE:   KeyFuncRepaintOnNewline,
}

func name2func(keyName string) KeyFuncT {
	if p, ok := NAME2FUNC[keyName]; ok {
		return &KeyGoFuncT{
			Func: p,
			Name: keyName,
		}
	} else {
		return nil
	}
}
<commit_msg>Add sample code for Alt+Backspace and Alt+"/".<commit_after>package readline

const (
	K_BACKSPACE     = "BACKSPACE"
	K_CAPSLOCK      = "CAPSLOCK"
	K_CLEAR         = "CLEAR"
	K_CTRL          = "CTRL"
	K_CTRL_A        = "C_A"
	K_CTRL_B        = "C_B"
	K_CTRL_BREAK    = "C_BREAK"
	K_CTRL_C        = "C_C"
	K_CTRL_D        = "C_D"
	K_CTRL_E        = "C_E"
	K_CTRL_F        = "C_F"
	K_CTRL_G        = "C_G"
	K_CTRL_H        = "C_H"
	K_CTRL_I        = "C_I"
	K_CTRL_J        = "C_J"
	K_CTRL_K        = "C_K"
	K_CTRL_L        = "C_L"
	K_CTRL_M        = "C_M"
	K_CTRL_N        = "C_N"
	K_CTRL_O        = "C_O"
	K_CTRL_P        = "C_P"
	K_CTRL_Q        = "C_Q"
	K_CTRL_R        = "C_R"
	K_CTRL_S        = "C_S"
	K_CTRL_T        = "C_T"
	K_CTRL_U        = "C_U"
	K_CTRL_V        = "C_V"
	K_CTRL_W        = "C_W"
	K_CTRL_X        = "C_X"
	K_CTRL_Y        = "C_Y"
	K_CTRL_Z        = "C_Z"
	K_DELETE        = "DEL"
	K_DOWN          = "DOWN"
	K_END           = "END"
	K_ENTER         = "ENTER"
	K_ESCAPE        = "ESCAPE"
	K_F1            = "F1"
	K_F10           = "F10"
	K_F11           = "F11"
	K_F12           = "F12"
	K_F13           = "F13"
	K_F14           = "F14"
	K_F15           = "F15"
	K_F16           = "F16"
	K_F17           = "F17"
	K_F18           = "F18"
	K_F19           = "F19"
	K_F2            = "F2"
	K_F20           = "F20"
	K_F21           = "F21"
	K_F22           = "F22"
	K_F23           = "F23"
	K_F24           = "F24"
	K_F3            = "F3"
	K_F4            = "F4"
	K_F5            = "F5"
	K_F6            = "F6"
	K_F7            = "F7"
	K_F8            = "F8"
	K_F9            = "F9"
	K_HOME          = "HOME"
	K_LEFT          = "LEFT"
	K_PAGEDOWN      = "PAGEDOWN"
	K_PAGEUP        = "PAGEUP"
	K_PAUSE         = "PAUSE"
	K_RIGHT         = "RIGHT"
	K_SHIFT         = "SHIFT"
	K_UP            = "UP"
	K_ALT_A         = "M_A"
	K_ALT_B         = "M_B"
	K_ALT_BACKSPACE = "M_BACKSPACE"
	K_ALT_BREAK     = "M_BREAK"
	K_ALT_C         = "M_C"
	K_ALT_D         = "M_D"
	K_ALT_E         = "M_E"
	K_ALT_F         = "M_F"
	K_ALT_G         = "M_G"
	K_ALT_H         = "M_H"
	K_ALT_I         = "M_I"
	K_ALT_J         = "M_J"
	K_ALT_K         = "M_K"
	K_ALT_L         = "M_L"
	K_ALT_M         = "M_M"
	K_ALT_N         = "M_N"
	K_ALT_O         = "M_O"
	K_ALT_P         = "M_P"
	K_ALT_Q         = "M_Q"
	K_ALT_R         = "M_R"
	K_ALT_S         = "M_S"
	K_ALT_T         = "M_T"
	K_ALT_U         = "M_U"
	K_ALT_V         = "M_V"
	K_ALT_W         = "M_W"
	K_ALT_X         = "M_X"
	K_ALT_Y         = "M_Y"
	K_ALT_Z         = "M_Z"
	K_ALT_OEM_2     = "M_OEM_2"
)

const (
	F_ACCEPT_LINE          = "ACCEPT_LINE"
	F_BACKWARD_CHAR        = "BACKWARD_CHAR"
	F_BACKWARD_DELETE_CHAR = "BACKWARD_DELETE_CHAR"
	F_BEGINNING_OF_LINE    = "BEGINNING_OF_LINE"
	F_CLEAR_SCREEN         = "CLEAR_SCREEN"
	F_DELETE_CHAR          = "DELETE_CHAR"
	F_DELETE_OR_ABORT      = "DELETE_OR_ABORT"
	F_END_OF_LINE          = "END_OF_LINE"
	F_FORARD_CHAR          = "FORWARD_CHAR"
	F_HISTORY_DOWN         = "HISTORY_DOWN"
	F_HISTORY_UP           = "HISTORY_UP"
	F_INTR                 = "INTR"
	F_ISEARCH_BACKWARD     = "ISEARCH_BACKWARD"
	F_KILL_LINE            = "KILL_LINE"
	F_KILL_WHOLE_LINE      = "KILL_WHOLE_LINE"
	F_PASS                 = "PASS"
	F_REPAINT_ON_NEWLINE   = "REPAINT_ON_NEWLINE"
	F_SWAPCHAR             = "SWAPCHAR"
	F_UNIX_LINE_DISCARD    = "UNIX_LINE_DISCARD"
	F_UNIX_WORD_RUBOUT     = "UNIX_WORD_RUBOUT"
	F_YANK                 = "YANK"
	F_YANK_WITH_QUOTE      = "YANK_WITH_QUOTE"
)

var name2char = map[string]rune{
	K_BACKSPACE: '\b',
	K_CTRL_A:    rune('a' & 0x1F),
	K_CTRL_B:    rune('b' & 0x1F),
	K_CTRL_C:    rune('c' & 0x1F),
	K_CTRL_D:    rune('d' & 0x1F),
	K_CTRL_E:    rune('e' & 0x1F),
	K_CTRL_F:    rune('f' & 0x1F),
	K_CTRL_G:    rune('g' & 0x1F),
	K_CTRL_H:    rune('h' & 0x1F),
	K_CTRL_I:    rune('i' & 0x1F),
	K_CTRL_J:    rune('j' & 0x1F),
	K_CTRL_K:    rune('k' & 0x1F),
	K_CTRL_L:    rune('l' & 0x1F),
	K_CTRL_M:    rune('m' & 0x1F),
	K_CTRL_N:    rune('n' & 0x1F),
	K_CTRL_O:    rune('o' & 0x1F),
	K_CTRL_P:    rune('p' & 0x1F),
	K_CTRL_Q:    rune('q' & 0x1F),
	K_CTRL_R:    rune('r' & 0x1F),
	K_CTRL_S:    rune('s' & 0x1F),
	K_CTRL_T:    rune('t' & 0x1F),
	K_CTRL_U:    rune('u' & 0x1F),
	K_CTRL_V:    rune('v' & 0x1F),
	K_CTRL_W:    rune('w' & 0x1F),
	K_CTRL_X:    rune('x' & 0x1F),
	K_CTRL_Y:    rune('y' & 0x1F),
	K_CTRL_Z:    rune('z' & 0x1F),
	K_DELETE:    '\x7F',
	K_ENTER:     '\r',
	K_ESCAPE:    rune('[' & 0x1F),
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx

var name2scan = map[string]uint16{
	K_CAPSLOCK:   0x14,
	K_CLEAR:      0x0C,
	K_CTRL:       0x11,
	K_CTRL_BREAK: 0x03,
	K_DELETE:     0x2E,
	K_DOWN:       0x28,
	K_END:        0x23,
	K_F10:        0x79,
	K_F11:        0x7A,
	K_F12:        0x7B,
	K_F13:        0x7C,
	K_F14:        0x7D,
	K_F15:        0x7E,
	K_F16:        0x7F,
	K_F17:        0x80,
	K_F18:        0x81,
	K_F19:        0x82,
	K_F1:         0x70,
	K_F20:        0x83,
	K_F21:        0x84,
	K_F22:        0x85,
	K_F23:        0x86,
	K_F24:        0x87,
	K_F2:         0x71,
	K_F3:         0x72,
	K_F4:         0x73,
	K_F5:         0x74,
	K_F6:         0x75,
	K_F7:         0x76,
	K_F8:         0x77,
	K_F9:         0x78,
	K_HOME:       0x24,
	K_LEFT:       0x25,
	K_PAGEDOWN:   0x22,
	K_PAGEUP:     0x21,
	K_PAUSE:      0x13,
	K_RIGHT:      0x27,
	K_SHIFT:      0x10,
	K_UP:         0x26,
}

var name2alt = map[string]uint16{
	K_ALT_A:         0x41,
	K_ALT_B:         0x42,
	K_ALT_BACKSPACE: 0x08,
	K_ALT_C:         0x43,
	K_ALT_D:         0x44,
	K_ALT_E:         0x45,
	K_ALT_F:         0x46,
	K_ALT_G:         0x47,
	K_ALT_H:         0x48,
	K_ALT_I:         0x49,
	K_ALT_J:         0x4A,
	K_ALT_K:         0x4B,
	K_ALT_L:         0x4C,
	K_ALT_M:         0x4D,
	K_ALT_N:         0x4E,
	K_ALT_O:         0x4F,
	K_ALT_P:         0x50,
	K_ALT_Q:         0x51,
	K_ALT_R:         0x52,
	K_ALT_S:         0x53,
	K_ALT_T:         0x54,
	K_ALT_U:         0x55,
	K_ALT_V:         0x56,
	K_ALT_W:         0x57,
	K_ALT_X:         0x58,
	K_ALT_Y:         0x59,
	K_ALT_Z:         0x5A,
	K_ALT_OEM_2:     0xBF,
}

var NAME2FUNC = map[string]func(*Buffer) Result{
	F_ACCEPT_LINE:          KeyFuncEnter,
	F_BACKWARD_CHAR:        KeyFuncBackword,
	F_BACKWARD_DELETE_CHAR: KeyFuncBackSpace,
	F_BEGINNING_OF_LINE:    KeyFuncHead,
	F_CLEAR_SCREEN:         KeyFuncCLS,
	F_DELETE_CHAR:          KeyFuncDelete,
	F_DELETE_OR_ABORT:      KeyFuncDeleteOrAbort,
	F_END_OF_LINE:          KeyFuncTail,
	F_FORARD_CHAR:          KeyFuncForward,
	F_HISTORY_DOWN:         KeyFuncHistoryDown,
	F_HISTORY_UP:           KeyFuncHistoryUp,
	F_INTR:                 KeyFuncIntr,
	F_ISEARCH_BACKWARD:     KeyFuncIncSearch,
	F_KILL_LINE:            KeyFuncClearAfter,
	F_KILL_WHOLE_LINE:      KeyFuncClear,
	F_PASS:                 KeyFuncPass,
	F_UNIX_LINE_DISCARD:    KeyFuncClearBefore,
	F_UNIX_WORD_RUBOUT:     KeyFuncWordRubout,
	F_YANK:                 KeyFuncPaste,
	F_YANK_WITH_QUOTE:      KeyFuncPasteQuote,
	F_SWAPCHAR:             KeyFuncSwapChar,
	F_REPAINT_ON_NEWLINE:   KeyFuncRepaintOnNewline,
}

func name2func(keyName string) KeyFuncT {
	if p, ok := NAME2FUNC[keyName]; ok {
		return &KeyGoFuncT{
			Func: p,
			Name: keyName,
		}
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The logr 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 logr

import (
	"context"
	"testing"
)

// testLogSink is a Logger just for testing that does nothing.
type testLogSink struct{}

func (l *testLogSink) Init(RuntimeInfo) {
}

func (l *testLogSink) Enabled(int) bool {
	return false
}

func (l *testLogSink) Info(level int, msg string, keysAndValues ...interface{}) {
}

func (l *testLogSink) Error(err error, msg string, keysAndValues ...interface{}) {
}

func (l *testLogSink) WithValues(keysAndValues ...interface{}) LogSink {
	return l
}

func (l *testLogSink) WithName(name string) LogSink {
	return l
}

// Verify that it actually implements the interface
var _ LogSink = &testLogSink{}

func TestContext(t *testing.T) {
	ctx := context.TODO()

	if out, err := FromContext(ctx); err == nil {
		t.Errorf("expected error, got %#v", out)
	} else if _, ok := err.(notFoundError); !ok {
		t.Errorf("expected a notFoundError, got %#v", err)
	}

	out := FromContextOrDiscard(ctx)
	if _, ok := out.sink.(discardLogSink); !ok {
		t.Errorf("expected a discardLogSink, got %#v", out)
	}

	sink := &testLogSink{}
	logger := New(sink)
	lctx := NewContext(ctx, logger)
	if out, err := FromContext(lctx); err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if p := out.sink.(*testLogSink); p != sink {
		t.Errorf("expected output to be the same as input: got in=%p, out=%p", sink, p)
	}
	out = FromContextOrDiscard(lctx)
	if p := out.sink.(*testLogSink); p != sink {
		t.Errorf("expected output to be the same as input: got in=%p, out=%p", sink, p)
	}
}

// testCallDepthLogSink is a Logger just for testing that does nothing.
type testCallDepthLogSink struct {
	*testLogSink
	depth int
}

func (l *testCallDepthLogSink) WithCallDepth(depth int) LogSink {
	return &testCallDepthLogSink{l.testLogSink, l.depth + depth}
}

// Verify that it actually implements the interface
var _ CallDepthLogSink = &testCallDepthLogSink{}

func TestWithCallDepth(t *testing.T) {
	// Test an impl that does not support it.
	t.Run("not supported", func(t *testing.T) {
		in := &testLogSink{}
		l := New(in)
		out := l.WithCallDepth(42)
		if p := out.sink.(*testLogSink); p != in {
			t.Errorf("expected output to be the same as input: got in=%p, out=%p", in, p)
		}
	})

	// Test an impl that does support it.
	t.Run("supported", func(t *testing.T) {
		in := &testCallDepthLogSink{&testLogSink{}, 0}
		l := New(in)
		out := l.WithCallDepth(42)
		if out.sink.(*testCallDepthLogSink) == in {
			t.Errorf("expected output to be different than input: got in=out=%p", in)
		}
		if cdl := out.sink.(*testCallDepthLogSink); cdl.depth != 42 {
			t.Errorf("expected depth=42, got %d", cdl.depth)
		}
	})
}
<commit_msg>Beef up logr tests<commit_after>/*
Copyright 2021 The logr 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 logr

import (
	"context"
	"fmt"
	"reflect"
	"testing"
)

// testLogSink is a Logger just for testing that calls optional hooks on each method.
type testLogSink struct {
	fnInit       func(ri RuntimeInfo)
	fnEnabled    func(lvl int) bool
	fnInfo       func(lvl int, msg string, kv ...interface{})
	fnError      func(err error, msg string, kv ...interface{})
	fnWithValues func(kv ...interface{})
	fnWithName   func(name string)
}

var _ LogSink = &testLogSink{}

func (l *testLogSink) Init(ri RuntimeInfo) {
	if l.fnInit != nil {
		l.fnInit(ri)
	}
}

func (l *testLogSink) Enabled(lvl int) bool {
	if l.fnEnabled != nil {
		return l.fnEnabled(lvl)
	}
	return false
}

func (l *testLogSink) Info(lvl int, msg string, kv ...interface{}) {
	if l.fnInfo != nil {
		l.fnInfo(lvl, msg, kv...)
	}
}

func (l *testLogSink) Error(err error, msg string, kv ...interface{}) {
	if l.fnError != nil {
		l.fnError(err, msg, kv...)
	}
}

func (l *testLogSink) WithValues(kv ...interface{}) LogSink {
	if l.fnWithValues != nil {
		l.fnWithValues(kv...)
	}
	out := *l
	return &out
}

func (l *testLogSink) WithName(name string) LogSink {
	if l.fnWithName != nil {
		l.fnWithName(name)
	}
	out := *l
	return &out
}

type testCallDepthLogSink struct {
	testLogSink
	fnWithCallDepth func(depth int)
}

var _ CallDepthLogSink = &testCallDepthLogSink{}

func (l *testCallDepthLogSink) WithCallDepth(depth int) LogSink {
	if l.fnWithCallDepth != nil {
		l.fnWithCallDepth(depth)
	}
	out := *l
	return &out
}

func TestNew(t *testing.T) {
	calledInit := 0

	sink := &testLogSink{}
	sink.fnInit = func(ri RuntimeInfo) {
		if ri.CallDepth != 1 {
			t.Errorf("expected runtimeInfo.CallDepth = 1, got %d", ri.CallDepth)
		}
		calledInit++
	}
	logger := New(sink)

	if logger.sink == nil {
		t.Errorf("expected sink to be set, got %v", logger.sink)
	}
	if calledInit != 1 {
		t.Errorf("expected sink.Init() to be called once, got %d", calledInit)
	}
	if logger.withCallDepth != nil {
		t.Errorf("expected withCallDepth to be nil, got %v", logger.withCallDepth)
	}
}

func TestNewCachesCallDepthInterface(t *testing.T) {
	sink := &testCallDepthLogSink{}
	logger := New(sink)

	if logger.withCallDepth == nil {
		t.Errorf("expected withCallDepth to be set")
	}
}

func TestEnabled(t *testing.T) {
	calledEnabled := 0

	sink := &testLogSink{}
	sink.fnEnabled = func(lvl int) bool {
		calledEnabled++
		return true
	}
	logger := New(sink)

	if en := logger.Enabled(); en != true {
		t.Errorf("expected true")
	}
	if calledEnabled != 1 {
		t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
	}
}

func TestError(t *testing.T) {
	calledError := 0
	errInput := fmt.Errorf("error")
	msgInput := "msg"
	kvInput := []interface{}{0, 1, 2}

	sink := &testLogSink{}
	sink.fnError = func(err error, msg string, kv ...interface{}) {
		calledError++
		if err != errInput {
			t.Errorf("unexpected err input, got %v", err)
		}
		if msg != msgInput {
			t.Errorf("unexpected msg input, got %q", msg)
		}
		if !reflect.DeepEqual(kv, kvInput) {
			t.Errorf("unexpected kv input, got %v", kv)
		}
	}
	logger := New(sink)

	logger.Error(errInput, msgInput, kvInput...)
	if calledError != 1 {
		t.Errorf("expected sink.Error() to be called once, got %d", calledError)
	}
}

func TestV(t *testing.T) {
	sink := &testLogSink{}
	logger := New(sink)

	if l := logger.V(0); l.level != 0 {
		t.Errorf("expected level 0, got %d", l.level)
	}
	if l := logger.V(93); l.level != 93 {
		t.Errorf("expected level 93, got %d", l.level)
	}
	if l := logger.V(70).V(6); l.level != 76 {
		t.Errorf("expected level 76, got %d", l.level)
	}
	if l := logger.V(-1); l.level != 0 {
		t.Errorf("expected level 0, got %d", l.level)
	}
	if l := logger.V(1).V(-1); l.level != 1 {
		t.Errorf("expected level 1, got %d", l.level)
	}
}

func TestInfo(t *testing.T) {
	calledEnabled := 0
	calledInfo := 0
	lvlInput := 0
	msgInput := "msg"
	kvInput := []interface{}{0, 1, 2}

	sink := &testLogSink{}
	sink.fnEnabled = func(lvl int) bool {
		calledEnabled++
		return lvl < 100
	}
	sink.fnInfo = func(lvl int, msg string, kv ...interface{}) {
		calledInfo++
		if lvl != lvlInput {
			t.Errorf("unexpected lvl input, got %v", lvl)
		}
		if msg != msgInput {
			t.Errorf("unexpected msg input, got %q", msg)
		}
		if !reflect.DeepEqual(kv, kvInput) {
			t.Errorf("unexpected kv input, got %v", kv)
		}
	}
	logger := New(sink)

	calledEnabled = 0
	calledInfo = 0
	lvlInput = 0
	logger.Info(msgInput, kvInput...)
	if calledEnabled != 1 {
		t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
	}
	if calledInfo != 1 {
		t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
	}

	calledEnabled = 0
	calledInfo = 0
	lvlInput = 0
	logger.V(0).Info(msgInput, kvInput...)
	if calledEnabled != 1 {
		t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
	}
	if calledInfo != 1 {
		t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
	}

	calledEnabled = 0
	calledInfo = 0
	lvlInput = 93
	logger.V(93).Info(msgInput, kvInput...)
	if calledEnabled != 1 {
		t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
	}
	if calledInfo != 1 {
		t.Errorf("expected sink.Info() to be called once, got %d", calledInfo)
	}

	calledEnabled = 0
	calledInfo = 0
	lvlInput = 100
	logger.V(100).Info(msgInput, kvInput...)
	if calledEnabled != 1 {
		t.Errorf("expected sink.Enabled() to be called once, got %d", calledEnabled)
	}
	if calledInfo != 0 {
		t.Errorf("expected sink.Info() to not be called, got %d", calledInfo)
	}
}

func TestWithValues(t *testing.T) {
	calledWithValues := 0
	kvInput := []interface{}{"zero", 0, "one", 1, "two", 2}

	sink := &testLogSink{}
	sink.fnWithValues = func(kv ...interface{}) {
		calledWithValues++
		if !reflect.DeepEqual(kv, kvInput) {
			t.Errorf("unexpected kv input, got %v", kv)
		}
	}
	logger := New(sink)

	out := logger.WithValues(kvInput...)
	if calledWithValues != 1 {
		t.Errorf("expected sink.WithValues() to be called once, got %d", calledWithValues)
	}
	if p := out.sink.(*testLogSink); p == sink {
		t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
	}
}

func TestWithName(t *testing.T) {
	calledWithName := 0
	nameInput := "name"

	sink := &testLogSink{}
	sink.fnWithName = func(name string) {
		calledWithName++
		if name != nameInput {
			t.Errorf("unexpected name input, got %q", name)
		}
	}
	logger := New(sink)

	out := logger.WithName(nameInput)
	if calledWithName != 1 {
		t.Errorf("expected sink.WithName() to be called once, got %d", calledWithName)
	}
	if p := out.sink.(*testLogSink); p == sink {
		t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
	}
}

func TestWithCallDepthNotImplemented(t *testing.T) {
	depthInput := 7

	sink := &testLogSink{}
	logger := New(sink)

	out := logger.WithCallDepth(depthInput)
	if p := out.sink.(*testLogSink); p != sink {
		t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
	}
}

func TestWithCallDepthImplemented(t *testing.T) {
	calledWithCallDepth := 0
	depthInput := 7

	sink := &testCallDepthLogSink{}
	sink.fnWithCallDepth = func(depth int) {
		calledWithCallDepth++
		if depth != depthInput {
			t.Errorf("unexpected depth input, got %d", depth)
		}
	}
	logger := New(sink)

	out := logger.WithCallDepth(depthInput)
	if calledWithCallDepth != 1 {
		t.Errorf("expected sink.WithCallDepth() to be called once, got %d", calledWithCallDepth)
	}
	if p := out.sink.(*testCallDepthLogSink); p == sink {
		t.Errorf("expected output to be different from input, got in=%p, out=%p", sink, p)
	}
}

func TestContext(t *testing.T) {
	ctx := context.TODO()

	if out, err := FromContext(ctx); err == nil {
		t.Errorf("expected error, got %#v", out)
	} else if _, ok := err.(notFoundError); !ok {
		t.Errorf("expected a notFoundError, got %#v", err)
	}

	out := FromContextOrDiscard(ctx)
	if _, ok := out.sink.(discardLogSink); !ok {
		t.Errorf("expected a discardLogSink, got %#v", out)
	}

	sink := &testLogSink{}
	logger := New(sink)
	lctx := NewContext(ctx, logger)
	if out, err := FromContext(lctx); err != nil {
		t.Errorf("unexpected error: %v", err)
	} else if p := out.sink.(*testLogSink); p != sink {
		t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
	}
	out = FromContextOrDiscard(lctx)
	if p := out.sink.(*testLogSink); p != sink {
		t.Errorf("expected output to be the same as input, got in=%p, out=%p", sink, p)
	}
}
<|endoftext|>


<commit_before>// +build ignore

/*
Copyright 2014 The FritzPay Authors.
*/

/*
This program builds paymentd
*/
package main

import (
	"os/exec"
)

func main() {
	args := []string{}
	cmd := exec.Command("go", args...)
}
<commit_msg>hack something up for go<commit_after>// +build ignore

/*
Copyright 2014 The FritzPay Authors.
*/

/*
This program builds paymentd
*/
package main

import (
	"log"
	"os"
	"os/exec"
)

func main() {
	log.SetFlags(0)
	args := []string{
		"get",
		"-v",
	}
	pkgs := []string{
		"code.google.com/p/go.tools/cmd/vet",
		"github.com/tools/godep",
	}
	for _, pkg := range pkgs {
		a := append(args, pkg)
		cmd := exec.Command("go", a...)
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		err := cmd.Run()
		if err != nil {
			os.Exit(1)
		}
	}
	args = []string{
		"vet",
		"./...",
	}
	cmd := exec.Command("go", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package redis

import "strings"
import "strconv"
import "errors"
import "math/rand"
import "os"
import "fmt"

const RedisClusterHashSlots = 16384
const RedisClusterRequestTTL = 16
const RedisClusterDefaultTimeout = 1

type RedisCluster struct {
	SeedHosts        map[string]bool
	Handles          map[string]*RedisHandle
	Slots            map[uint16]string
	RefreshTableASAP bool
	SingleRedisMode  bool
	MaxIdle          int
	MaxActive        int
	Debug            bool
}

func NewRedisCluster(addrs []string, max_idle, max_active int, debug bool) RedisCluster {
	cluster := RedisCluster{RefreshTableASAP: false,
		SingleRedisMode: false,
		SeedHosts:       make(map[string]bool),
		Handles:         make(map[string]*RedisHandle),
		Slots:           make(map[uint16]string),
		MaxActive:       max_active,
		Debug:           debug}

	if cluster.Debug {
		fmt.Println("[RedisCluster], PID", os.Getpid(), "StartingNewRedisCluster")
	}

	for _, label := range addrs {
		cluster.SeedHosts[label] = true
		cluster.Handles[label] = NewRedisHandle(label, max_idle, max_active, debug)
	}

	for addr, _ := range cluster.SeedHosts {
		node := cluster.addRedisHandleIfNeeded(addr)
		cluster_enabled := cluster.hasClusterEnabled(node)
		if cluster_enabled == false {
			if len(cluster.SeedHosts) == 1 {
				cluster.SingleRedisMode = true
			} else {
				panic(errors.New("Multiple Seed Hosts Given, But Cluster Support Disabled in Redis"))
			}
		}
	}

	if cluster.SingleRedisMode == false {
		cluster.populateSlotsCache()
	}
	return cluster
}

func (self *RedisCluster) Update(max_idle, max_active int32) {
	for _, rh := range self.Handles {
		rh.Pool.Update(max_idle, max_active)
	}
}

func (self *RedisCluster) SetWaitTime(t int) {
	for _, rh := range self.Handles {
		rh.Pool.SetWaitTime(t)
	}
}

func (self *RedisCluster) SetLifeTime(t int) {
	for _, rh := range self.Handles {
		rh.Pool.SetLifeTime(t)
	}
}

func (self *RedisCluster) hasClusterEnabled(node *RedisHandle) bool {
	_, err := node.Do("CLUSTER", "INFO")
	if err != nil {
		if err.Error() == "ERR This instance has cluster support disabled" ||
			err.Error() == "ERR unknown command 'CLUSTER'" {
			return false
		}
	}
	return true
}

// contact the startup nodes and try to fetch the hash slots -> instances
// map in order to initialize the Slots map.
func (self *RedisCluster) populateSlotsCache() {
	if self.SingleRedisMode == true {
		return
	}
	if self.Debug {
		fmt.Println("[RedisCluster], PID", os.Getpid(), "[PopulateSlots Running]")
	}
	for name, _ := range self.SeedHosts {
		if self.Debug {
			fmt.Println("[RedisCluster] [PopulateSlots] Checking: ", name)
		}
		node := self.addRedisHandleIfNeeded(name)
		cluster_info, err := node.Do("CLUSTER", "NODES")
		if err == nil {
			lines := strings.Split(string(cluster_info.([]uint8)), "\n")
			for _, line := range lines {
				if line != "" {
					fields := strings.Split(line, " ")
					addr := fields[1]
					if addr == ":0" {
						addr = name
					}
					// add to seedlist if not in cluster
					_, seedlist_exists := self.SeedHosts[addr]
					if !seedlist_exists {
						self.SeedHosts[addr] = true
					}
					// add to handles if not in handles
					self.addRedisHandleIfNeeded(name)

					slots := fields[8:len(fields)]
					for _, s_range := range slots {
						slot_range := s_range
						if slot_range != "[" {
							if self.Debug {
								fmt.Println("[RedisCluster] Considering Slot Range", slot_range)
							}
							r_pieces := strings.Split(slot_range, "-")
							min, _ := strconv.Atoi(r_pieces[0])
							max, _ := strconv.Atoi(r_pieces[1])
							for i := min; i <= max; i++ {
								self.Slots[uint16(i)] = addr
							}
						}
					}
				}
			}
			if self.Debug {
				fmt.Println("[RedisCluster] [Initializing] DONE, ",
					"Slots: ", len(self.Slots),
					"Handles So Far:", len(self.Handles),
					"SeedList:", len(self.SeedHosts))
			}
			break
		}
	}
	self.switchToSingleModeIfNeeded()
}

func (self *RedisCluster) switchToSingleModeIfNeeded() {
	// catch case where we really intend to be on
	// single redis mode, but redis was not
	// started on time
	if len(self.SeedHosts) == 1 &&
		len(self.Slots) == 0 &&
		len(self.Handles) == 1 {
		for _, node := range self.Handles {
			cluster_enabled := self.hasClusterEnabled(node)
			if cluster_enabled == false {
				self.SingleRedisMode = true
			}
		}
	}
}

func (self *RedisCluster) addRedisHandleIfNeeded(addr string) *RedisHandle {
	_, handle_exists := self.Handles[addr]
	if !handle_exists {
		self.Handles[addr] = NewRedisHandle(addr, self.MaxIdle, self.MaxActive, self.Debug)
	}
	return self.Handles[addr]
}

func (self *RedisCluster) KeyForRequest(cmd string, args ...interface{}) string {
	cmd = strings.ToLower(cmd)
	if cmd == "info" ||
		cmd == "multi" ||
		cmd == "exec" ||
		cmd == "slaveof" ||
		cmd == "config" ||
		cmd == "shutdown" {
		return ""
	}
	if args[0] == nil {
		return ""
	}
	strs := args[0].([]interface{})
	if strs != nil && strs[0] != nil {
		return strs[0].(string)
	}
	return ""
}

// Return the hash slot from the key.
func (self *RedisCluster) SlotForKey(key string) uint16 {
	checksum := ChecksumCRC16([]byte(key))
	slot := checksum % RedisClusterHashSlots
	return slot
}

func (self *RedisCluster) RandomRedisHandle() *RedisHandle {
	if len(self.Handles) == 0 {
		return nil
	}
	addrs := make([]string, len(self.Handles))
	i := 0
	for addr, _ := range self.Handles {
		addrs[i] = addr
		i++
	}
	rand_addrs := make([]string, i)
	perm := rand.Perm(i)
	for j, v := range perm {
		rand_addrs[v] = addrs[j]
	}
	handle := self.Handles[rand_addrs[0]]
	self.switchToSingleModeIfNeeded()
	return handle
}

// Given a slot return the link (Redis instance) to the mapped node.
// Make sure to create a connection with the node if we don't have
// one.
func (self *RedisCluster) RedisHandleForSlot(slot uint16) *RedisHandle {

	node, exists := self.Slots[slot]
	// If we don't know what the mapping is, return a random node.
	if !exists {
		if self.Debug {
			fmt.Println("[RedisCluster] No One Appears Responsible For Slot: ", slot, "our slotsize is: ", len(self.Slots))
		}
		return self.RandomRedisHandle()
	}

	r, cx_exists := self.Handles[node]
	// add to cluster if not in cluster
	if cx_exists {
		return r
	} else {
		r = NewRedisHandle(node, self.MaxIdle, self.MaxActive, self.Debug)
		self.Handles[node] = r
	}
	// XXX consider returning random if failure
	return r
}

func (self *RedisCluster) disconnectAll() {
	if self.Debug {
		fmt.Println("[RedisCluster] PID:", os.Getpid(), " [Disconnect!] Had Handles:", len(self.Handles))
	}
	// disconnect anyone in handles
	for _, handle := range self.Handles {
		handle.Pool.Close()
	}
	// nuke handles
	for addr, _ := range self.SeedHosts {
		delete(self.Handles, addr)
	}
	// nuke slots
	self.Slots = make(map[uint16]string)
}

func (self *RedisCluster) handleSingleMode(flush bool, cmd string, args ...interface{}) (reply interface{}, err error) {
	for _, handle := range self.Handles {
		if flush {
			return handle.Do(cmd, args...)
		}
		return nil, handle.Send(cmd, args...)
	}
	return nil, errors.New("no redis handle found for single mode")
}

func (self *RedisCluster) SendClusterCommand(flush bool, cmd string, args ...interface{}) (reply interface{}, err error) {

	// forward onto first redis in the handle
	// if we are set to single mode
	if self.SingleRedisMode == true {
		return self.handleSingleMode(flush, cmd, args...)
	}

	if self.RefreshTableASAP == true {
		if self.Debug {
			fmt.Println("[RedisCluster] Refresh Needed")
		}
		self.disconnectAll()
		self.populateSlotsCache()
		self.RefreshTableASAP = false
		// in case we realized we were now in Single Mode
		if self.SingleRedisMode == true {
			return self.handleSingleMode(flush, cmd, args...)
		}
	}

	ttl := RedisClusterRequestTTL
	key := self.KeyForRequest(cmd, args)
	try_random_node := false
	asking := false
	for {
		if ttl <= 0 {
			break
		}
		ttl -= 1
		if key == "" {
			panic(errors.New("no way to dispatch this type of command to redis cluster"))
		}
		slot := self.SlotForKey(key)

		var redis *RedisHandle

		if self.Debug {
			fmt.Println("[RedisCluster] slot: ", slot, "key", key, "ttl", ttl)
		}

		if try_random_node {
			if self.Debug {
				fmt.Println("[RedisCluster] Trying Random Node")
			}
			redis = self.RandomRedisHandle()
			try_random_node = false
		} else {
			if self.Debug {
				fmt.Println("[RedisCluster] Trying Specific Node")
			}
			redis = self.RedisHandleForSlot(slot)
		}

		if redis == nil {
			if self.Debug {
				fmt.Println("[RedisCluster] could not get redis handle, bailing this round")
			}
			break
		}
		if self.Debug {
			fmt.Println("[RedisCluster] Got addr: ", redis.Addr)
		}

		if asking {
			if self.Debug {
				fmt.Println("ASKING")
			}
			redis.Do("ASKING")
			asking = false
		}

		var err error
		var resp interface{}

		if flush {
			resp, err = redis.Do(cmd, args...)
			if err == nil {
				if self.Debug {
					fmt.Println("[RedisCluster] Success")
				}
				return resp, nil
			}
		} else {
			err = redis.Send(cmd, args...)
			if err == nil {
				if self.Debug {
					fmt.Println("[RedisCluster] Success")
				}
				return nil, nil
			}
		}

		// ok we are here so err is not nil
		errv := strings.Split(err.Error(), " ")
		if errv[0] == "MOVED" || errv[0] == "ASK" {
			if errv[0] == "ASK" {
				if self.Debug {
					fmt.Println("[RedisCluster] ASK")
				}
				asking = true
			} else {
				// Serve replied with MOVED. It's better for us to
				// ask for CLUSTER NODES the next time.
				SetRefreshNeeded()
				newslot, _ := strconv.Atoi(errv[1])
				newaddr := errv[2]
				self.Slots[uint16(newslot)] = newaddr
				if self.Debug {
					fmt.Println("[RedisCluster] MOVED newaddr: ", newaddr, "new slot: ", newslot, "my slots len: ", len(self.Slots))
				}
			}
		} else {
			if self.Debug {
				fmt.Println("[RedisCluster] Other Error: ", err.Error())
			}
			try_random_node = true
		}
	}
	if self.Debug {
		fmt.Println("[RedisCluster] Failed Command")
	}
	return nil, errors.New("could not complete command")
}

func (self *RedisCluster) Do(cmd string, args ...interface{}) (reply interface{}, err error) {
	return self.SendClusterCommand(true, cmd, args...)
}

func (self *RedisCluster) Send(cmd string, args ...interface{}) (err error) {
	_, err = self.SendClusterCommand(false, cmd, args...)
	return err
}

func (self *RedisCluster) SetRefreshNeeded() {
	self.RefreshTableASAP = true
}

func (self *RedisCluster) HandleForKey(key string) *RedisHandle {
	// forward onto first redis in the handle
	// if we are set to single mode
	if self.SingleRedisMode == true {
		for _, handle := range self.Handles {
			return handle
		}
	}
	slot := self.SlotForKey(key)
	handle := self.RedisHandleForSlot(slot)
	return handle
}

type RedisClusterAccess interface {
	Do(commandName string, args ...interface{}) (reply interface{}, err error)
	Send(cmd string, args ...interface{}) (err error)
	SetRefreshNeeded()
	HandleForKey(key string) *RedisHandle
}

var Instance RedisCluster

func Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	return Instance.Do(commandName, args...)
}

func Send(cmd string, args ...interface{}) (err error) {
	return Instance.Send(cmd, args...)
}

func SetRefreshNeeded() {
	Instance.SetRefreshNeeded()
}

func HandleForKey(key string) *RedisHandle {
	return Instance.HandleForKey(key)
}
<commit_msg>add slotMutex for cluster<commit_after>package redis

import (
	"strings"
	"sync"
)
import "strconv"
import "errors"
import "math/rand"
import "os"
import "fmt"

const RedisClusterHashSlots = 16384
const RedisClusterRequestTTL = 16
const RedisClusterDefaultTimeout = 1

type RedisCluster struct {
	SeedHosts        map[string]bool
	Handles          map[string]*RedisHandle
	Slots            map[uint16]string
	slotMutex        sync.RWMutex
	RefreshTableASAP bool
	SingleRedisMode  bool
	MaxIdle          int
	MaxActive        int
	Debug            bool
}

func NewRedisCluster(addrs []string, max_idle, max_active int, debug bool) RedisCluster {
	cluster := RedisCluster{RefreshTableASAP: false,
		SingleRedisMode: false,
		SeedHosts:       make(map[string]bool),
		Handles:         make(map[string]*RedisHandle),
		Slots:           make(map[uint16]string),
		MaxActive:       max_active,
		Debug:           debug}

	if cluster.Debug {
		fmt.Println("[RedisCluster], PID", os.Getpid(), "StartingNewRedisCluster")
	}

	for _, label := range addrs {
		cluster.SeedHosts[label] = true
		cluster.Handles[label] = NewRedisHandle(label, max_idle, max_active, debug)
	}

	for addr, _ := range cluster.SeedHosts {
		node := cluster.addRedisHandleIfNeeded(addr)
		cluster_enabled := cluster.hasClusterEnabled(node)
		if cluster_enabled == false {
			if len(cluster.SeedHosts) == 1 {
				cluster.SingleRedisMode = true
			} else {
				panic(errors.New("Multiple Seed Hosts Given, But Cluster Support Disabled in Redis"))
			}
		}
	}

	if cluster.SingleRedisMode == false {
		cluster.populateSlotsCache()
	}
	return cluster
}

func (self *RedisCluster) Update(max_idle, max_active int32) {
	for _, rh := range self.Handles {
		rh.Pool.Update(max_idle, max_active)
	}
}

func (self *RedisCluster) SetWaitTime(t int) {
	for _, rh := range self.Handles {
		rh.Pool.SetWaitTime(t)
	}
}

func (self *RedisCluster) SetLifeTime(t int) {
	for _, rh := range self.Handles {
		rh.Pool.SetLifeTime(t)
	}
}

func (self *RedisCluster) hasClusterEnabled(node *RedisHandle) bool {
	_, err := node.Do("CLUSTER", "INFO")
	if err != nil {
		if err.Error() == "ERR This instance has cluster support disabled" ||
			err.Error() == "ERR unknown command 'CLUSTER'" {
			return false
		}
	}
	return true
}

// contact the startup nodes and try to fetch the hash slots -> instances
// map in order to initialize the Slots map.
func (self *RedisCluster) populateSlotsCache() {
	if self.SingleRedisMode == true {
		return
	}
	if self.Debug {
		fmt.Println("[RedisCluster], PID", os.Getpid(), "[PopulateSlots Running]")
	}
	for name, _ := range self.SeedHosts {
		if self.Debug {
			fmt.Println("[RedisCluster] [PopulateSlots] Checking: ", name)
		}
		node := self.addRedisHandleIfNeeded(name)
		cluster_info, err := node.Do("CLUSTER", "NODES")
		if err == nil {
			lines := strings.Split(string(cluster_info.([]uint8)), "\n")
			for _, line := range lines {
				if line != "" {
					fields := strings.Split(line, " ")
					addr := fields[1]
					if addr == ":0" {
						addr = name
					}
					// add to seedlist if not in cluster
					_, seedlist_exists := self.SeedHosts[addr]
					if !seedlist_exists {
						self.SeedHosts[addr] = true
					}
					// add to handles if not in handles
					self.addRedisHandleIfNeeded(name)

					slots := fields[8:len(fields)]
					for _, s_range := range slots {
						slot_range := s_range
						if slot_range != "[" {
							if self.Debug {
								fmt.Println("[RedisCluster] Considering Slot Range", slot_range)
							}
							r_pieces := strings.Split(slot_range, "-")
							min, _ := strconv.Atoi(r_pieces[0])
							max, _ := strconv.Atoi(r_pieces[1])
							for i := min; i <= max; i++ {
								self.slotMutex.Lock()
								self.Slots[uint16(i)] = addr
								self.slotMutex.Unlock()
							}
						}
					}
				}
			}
			if self.Debug {
				self.slotMutex.RLock()
				fmt.Println("[RedisCluster] [Initializing] DONE, ",
					"Slots: ", len(self.Slots),
					"Handles So Far:", len(self.Handles),
					"SeedList:", len(self.SeedHosts))
				self.slotMutex.RUnlock()
			}
			break
		}
	}
	self.switchToSingleModeIfNeeded()
}

func (self *RedisCluster) switchToSingleModeIfNeeded() {
	// catch case where we really intend to be on
	// single redis mode, but redis was not
	// started on time
	self.slotMutex.RLock()
	slotLen := len(self.Slots)
	self.slotMutex.RUnlock()
	seedHostsLen := len(self.SeedHosts)
	handlesLen := len(self.Handles)
	if seedHostsLen == 1 &&
		slotLen == 0 &&
		handlesLen == 1 {
		for _, node := range self.Handles {
			cluster_enabled := self.hasClusterEnabled(node)
			if cluster_enabled == false {
				self.SingleRedisMode = true
			}
		}
	}
}

func (self *RedisCluster) addRedisHandleIfNeeded(addr string) *RedisHandle {
	_, handle_exists := self.Handles[addr]
	if !handle_exists {
		self.Handles[addr] = NewRedisHandle(addr, self.MaxIdle, self.MaxActive, self.Debug)
	}
	return self.Handles[addr]
}

func (self *RedisCluster) KeyForRequest(cmd string, args ...interface{}) string {
	cmd = strings.ToLower(cmd)
	if cmd == "info" ||
		cmd == "multi" ||
		cmd == "exec" ||
		cmd == "slaveof" ||
		cmd == "config" ||
		cmd == "shutdown" {
		return ""
	}
	if args[0] == nil {
		return ""
	}
	strs := args[0].([]interface{})
	if strs != nil && strs[0] != nil {
		return strs[0].(string)
	}
	return ""
}

// Return the hash slot from the key.
func (self *RedisCluster) SlotForKey(key string) uint16 {
	checksum := ChecksumCRC16([]byte(key))
	slot := checksum % RedisClusterHashSlots
	return slot
}

func (self *RedisCluster) RandomRedisHandle() *RedisHandle {
	if len(self.Handles) == 0 {
		return nil
	}
	addrs := make([]string, len(self.Handles))
	i := 0
	for addr, _ := range self.Handles {
		addrs[i] = addr
		i++
	}
	rand_addrs := make([]string, i)
	perm := rand.Perm(i)
	for j, v := range perm {
		rand_addrs[v] = addrs[j]
	}
	handle := self.Handles[rand_addrs[0]]
	self.switchToSingleModeIfNeeded()
	return handle
}

// Given a slot return the link (Redis instance) to the mapped node.
// Make sure to create a connection with the node if we don't have
// one.
func (self *RedisCluster) RedisHandleForSlot(slot uint16) *RedisHandle {
	self.slotMutex.RLock()
	node, exists := self.Slots[slot]
	self.slotMutex.RUnlock()
	// If we don't know what the mapping is, return a random node.
	if !exists {
		if self.Debug {
			fmt.Println("[RedisCluster] No One Appears Responsible For Slot: ", slot, "our slotsize is: ", len(self.Slots))
		}
		return self.RandomRedisHandle()
	}

	r, cx_exists := self.Handles[node]
	// add to cluster if not in cluster
	if cx_exists {
		return r
	} else {
		r = NewRedisHandle(node, self.MaxIdle, self.MaxActive, self.Debug)
		self.Handles[node] = r
	}
	// XXX consider returning random if failure
	return r
}

func (self *RedisCluster) disconnectAll() {
	if self.Debug {
		fmt.Println("[RedisCluster] PID:", os.Getpid(), " [Disconnect!] Had Handles:", len(self.Handles))
	}
	// disconnect anyone in handles
	for _, handle := range self.Handles {
		handle.Pool.Close()
	}
	// nuke handles
	for addr, _ := range self.SeedHosts {
		delete(self.Handles, addr)
	}
	// nuke slots
	self.Slots = make(map[uint16]string)
}

func (self *RedisCluster) handleSingleMode(flush bool, cmd string, args ...interface{}) (reply interface{}, err error) {
	for _, handle := range self.Handles {
		if flush {
			return handle.Do(cmd, args...)
		}
		return nil, handle.Send(cmd, args...)
	}
	return nil, errors.New("no redis handle found for single mode")
}

func (self *RedisCluster) SendClusterCommand(flush bool, cmd string, args ...interface{}) (reply interface{}, err error) {

	// forward onto first redis in the handle
	// if we are set to single mode
	if self.SingleRedisMode == true {
		return self.handleSingleMode(flush, cmd, args...)
	}

	if self.RefreshTableASAP == true {
		if self.Debug {
			fmt.Println("[RedisCluster] Refresh Needed")
		}
		self.disconnectAll()
		self.populateSlotsCache()
		self.RefreshTableASAP = false
		// in case we realized we were now in Single Mode
		if self.SingleRedisMode == true {
			return self.handleSingleMode(flush, cmd, args...)
		}
	}

	ttl := RedisClusterRequestTTL
	key := self.KeyForRequest(cmd, args)
	try_random_node := false
	asking := false
	for {
		if ttl <= 0 {
			break
		}
		ttl -= 1
		if key == "" {
			panic(errors.New("no way to dispatch this type of command to redis cluster"))
		}
		slot := self.SlotForKey(key)

		var redis *RedisHandle

		if self.Debug {
			fmt.Println("[RedisCluster] slot: ", slot, "key", key, "ttl", ttl)
		}

		if try_random_node {
			if self.Debug {
				fmt.Println("[RedisCluster] Trying Random Node")
			}
			redis = self.RandomRedisHandle()
			try_random_node = false
		} else {
			if self.Debug {
				fmt.Println("[RedisCluster] Trying Specific Node")
			}
			redis = self.RedisHandleForSlot(slot)
		}

		if redis == nil {
			if self.Debug {
				fmt.Println("[RedisCluster] could not get redis handle, bailing this round")
			}
			break
		}
		if self.Debug {
			fmt.Println("[RedisCluster] Got addr: ", redis.Addr)
		}

		if asking {
			if self.Debug {
				fmt.Println("ASKING")
			}
			redis.Do("ASKING")
			asking = false
		}

		var err error
		var resp interface{}

		if flush {
			resp, err = redis.Do(cmd, args...)
			if err == nil {
				if self.Debug {
					fmt.Println("[RedisCluster] Success")
				}
				return resp, nil
			}
		} else {
			err = redis.Send(cmd, args...)
			if err == nil {
				if self.Debug {
					fmt.Println("[RedisCluster] Success")
				}
				return nil, nil
			}
		}

		// ok we are here so err is not nil
		errv := strings.Split(err.Error(), " ")
		if errv[0] == "MOVED" || errv[0] == "ASK" {
			if errv[0] == "ASK" {
				if self.Debug {
					fmt.Println("[RedisCluster] ASK")
				}
				asking = true
			} else {
				// Serve replied with MOVED. It's better for us to
				// ask for CLUSTER NODES the next time.
				SetRefreshNeeded()
				newslot, _ := strconv.Atoi(errv[1])
				newaddr := errv[2]
				self.slotMutex.Lock()
				self.Slots[uint16(newslot)] = newaddr
				self.slotMutex.Unlock()

				if self.Debug {
					self.slotMutex.RLock()
					slotLen := len(self.Slots)
					self.slotMutex.RUnlock()
					fmt.Println("[RedisCluster] MOVED newaddr: ", newaddr, "new slot: ", newslot, "my slots len: ", slotLen)
				}
			}
		} else {
			if self.Debug {
				fmt.Println("[RedisCluster] Other Error: ", err.Error())
			}
			try_random_node = true
		}
	}
	if self.Debug {
		fmt.Println("[RedisCluster] Failed Command")
	}
	return nil, errors.New("could not complete command")
}

func (self *RedisCluster) Do(cmd string, args ...interface{}) (reply interface{}, err error) {
	return self.SendClusterCommand(true, cmd, args...)
}

func (self *RedisCluster) Send(cmd string, args ...interface{}) (err error) {
	_, err = self.SendClusterCommand(false, cmd, args...)
	return err
}

func (self *RedisCluster) SetRefreshNeeded() {
	self.RefreshTableASAP = true
}

func (self *RedisCluster) HandleForKey(key string) *RedisHandle {
	// forward onto first redis in the handle
	// if we are set to single mode
	if self.SingleRedisMode == true {
		for _, handle := range self.Handles {
			return handle
		}
	}
	slot := self.SlotForKey(key)
	handle := self.RedisHandleForSlot(slot)
	return handle
}

type RedisClusterAccess interface {
	Do(commandName string, args ...interface{}) (reply interface{}, err error)
	Send(cmd string, args ...interface{}) (err error)
	SetRefreshNeeded()
	HandleForKey(key string) *RedisHandle
}

var Instance RedisCluster

func Do(commandName string, args ...interface{}) (reply interface{}, err error) {
	return Instance.Do(commandName, args...)
}

func Send(cmd string, args ...interface{}) (err error) {
	return Instance.Send(cmd, args...)
}

func SetRefreshNeeded() {
	Instance.SetRefreshNeeded()
}

func HandleForKey(key string) *RedisHandle {
	return Instance.HandleForKey(key)
}
<|endoftext|>