<commit_before>package test

import "testing"

func TestClientDisconnect(t *testing.T) {
	sh := NewServerHelper(t).SeedDB().ListenAndServe()
	ch := sh.GetClientHelper().AsUser(&sh.SeedData.User1).Connect()

	ch.CloseWait()
	sh.CloseWait()
}

//
// func TestServerDisconnect(t *testing.T) {
// 	s := NewServerHelper(t).SeedDB()
// 	c := NewConnHelper(t, s).AsUser(&s.SeedData.User1).Dial()
//
// 	s.Stop()
// 	c.Close()
// }
//
// func TestClientClose(t *testing.T) {
// 	s := NewServerHelper(t).SeedDB()
// 	defer s.Stop()
// 	c := NewConnHelper(t, s).AsUser(&s.SeedData.User1).Dial()
// 	defer c.Close()
//
// 	// c.WriteNotification("conn.close", nil)
// 	// todo: verify that connection is closed by listner before client does
//
// 	// note: with nanosecond wait, client disconnected doesn't happen because we close client and the server faster than
// 	// listener.handleMsg() goroutine can be spawned as we're never waiting for an answer
// 	// graceful listener closing attempts also fail as reqWG is never incremented in the same method
// 	// time.Sleep(time.Nanosecond)
// }
//
// func TestServerClose(t *testing.T) {
// 	// t.Fatal("Server->client method.close request was not handled properly")
// 	// t.Fatal("ACK for Server->client method.close request was not received")
// }
//
// func TestMultiConn(t *testing.T) {
// 	// t.Fatal("Failed to handle randomly connecting disconnecting users")
// }
//
// func TestStop(t *testing.T) {
// 	// todo: this should be a listener/queue test if we don't use any goroutines in the Server struct methods
// 	// t.Fatal("Failed to stop the server: not all the goroutines were terminated properly")
// 	// t.Fatal("Failed to stop the server: server did not wait for ongoing read/write operations")
// 	// t.Fatal("Server did not release port 3001 after closing.")
// }
//
// func TestConnTimeout(t *testing.T) {
// 	// t.Fatal("Send timout did not occur")
// 	// t.Fatal("Wait timeout did not occur")
// 	// t.Fatal("Read timeout did not occur")
// }
<commit_msg>fix TestServerDisconnect<commit_after>package test

import "testing"

func TestClientDisconnect(t *testing.T) {
	sh := NewServerHelper(t).SeedDB().ListenAndServe()
	ch := sh.GetClientHelper().AsUser(&sh.SeedData.User1).Connect()

	ch.CloseWait()
	sh.CloseWait()

	// todo: validate log output order
}

func TestServerDisconnect(t *testing.T) {
	sh := NewServerHelper(t).SeedDB().ListenAndServe()
	ch := sh.GetClientHelper().AsUser(&sh.SeedData.User1).Connect()

	sh.CloseWait()
	ch.CloseWait()

	// todo: validate log output order
}

//
// func TestClientClose(t *testing.T) {
// 	s := NewServerHelper(t).SeedDB()
// 	defer s.Stop()
// 	c := NewConnHelper(t, s).AsUser(&s.SeedData.User1).Dial()
// 	defer c.Close()
//
// 	// c.WriteNotification("conn.close", nil)
// 	// todo: verify that connection is closed by listner before client does
//
// 	// note: with nanosecond wait, client disconnected doesn't happen because we close client and the server faster than
// 	// listener.handleMsg() goroutine can be spawned as we're never waiting for an answer
// 	// graceful listener closing attempts also fail as reqWG is never incremented in the same method
// 	// time.Sleep(time.Nanosecond)
// }
//
// func TestServerClose(t *testing.T) {
// 	// t.Fatal("Server->client method.close request was not handled properly")
// 	// t.Fatal("ACK for Server->client method.close request was not received")
// }
//
// func TestMultiConn(t *testing.T) {
// 	// t.Fatal("Failed to handle randomly connecting disconnecting users")
// }
//
// func TestStop(t *testing.T) {
// 	// todo: this should be a listener/queue test if we don't use any goroutines in the Server struct methods
// 	// t.Fatal("Failed to stop the server: not all the goroutines were terminated properly")
// 	// t.Fatal("Failed to stop the server: server did not wait for ongoing read/write operations")
// 	// t.Fatal("Server did not release port 3001 after closing.")
// }
//
// func TestConnTimeout(t *testing.T) {
// 	// t.Fatal("Send timout did not occur")
// 	// t.Fatal("Wait timeout did not occur")
// 	// t.Fatal("Read timeout did not occur")
// }
<|endoftext|>


<commit_before>package ui

import (
	"fmt"
	"image"
	"os"
	"time"

	"github.com/bugsnag/bugsnag-go"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/model"
	owm "github.com/ninjasphere/openweathermap"
	"github.com/ninjasphere/sphere-go-led-controller/util"
)

var enableWeatherPane = config.MustBool("led.weather.enabled")
var weatherUpdateInterval = config.MustDuration("led.weather.updateInterval")

type WeatherPane struct {
	siteModel  *ninja.ServiceClient
	site       *model.Site
	getWeather *time.Timer
	weather    *owm.CurrentWeatherData
	image      util.Image
}

func NewWeatherPane(conn *ninja.Connection) *WeatherPane {

	pane := &WeatherPane{
		siteModel: conn.GetServiceClient("$home/services/SiteModel"),
		image:     util.LoadImage(util.ResolveImagePath("weather/loading.gif")),
	}

	if !enableWeatherPane {
		return pane
	}

	var err error
	pane.weather, err = owm.NewCurrent("metric")
	if err != nil {
		log.Warningf("Failed to load weather api:", err)
		enableWeatherPane = false
	} else {
		go pane.GetWeather()
	}

	return pane
}

func (p *WeatherPane) GetWeather() {

	enableWeatherPane = false

	for {
		site := &model.Site{}
		err := p.siteModel.Call("fetch", config.MustString("siteId"), site, time.Second*5)

		if err == nil && (site.Longitude != nil || site.Latitude != nil) {
			p.site = site
			break
		}

		log.Infof("Failed to get site, or site has no location.")

		time.Sleep(time.Second * 5)
	}

	for {

		p.weather.CurrentByCoordinates(
			&owm.Coordinates{
				Longitude: *p.site.Longitude,
				Latitude:  *p.site.Latitude,
			},
		)

		filename := util.ResolveImagePath("weather/" + p.weather.Weather[0].Icon + ".png")

		if _, err := os.Stat(filename); os.IsNotExist(err) {
			enableWeatherPane = false
			fmt.Printf("Couldn't load image for weather: %s", filename)
			bugsnag.Notify(fmt.Errorf("Unknown weather icon: %s", filename), p.weather)
		} else {
			p.image = util.LoadImage(filename)
			enableWeatherPane = true
		}

		time.Sleep(weatherUpdateInterval)

	}

}

func (p *WeatherPane) IsEnabled() bool {
	return enableWeatherPane && p.weather.Unit != ""
}

func (p *WeatherPane) Gesture(gesture *gestic.GestureMessage) {
}

func (p *WeatherPane) Render() (*image.RGBA, error) {
	return p.image.GetNextFrame(), nil
}

func (p *WeatherPane) IsDirty() bool {
	return true
}
<commit_msg>Update for changed OWP api :/<commit_after>package ui

import (
	"fmt"
	"image"
	"os"
	"time"

	"github.com/bugsnag/bugsnag-go"
	"github.com/ninjasphere/gestic-tools/go-gestic-sdk"
	"github.com/ninjasphere/go-ninja/api"
	"github.com/ninjasphere/go-ninja/config"
	"github.com/ninjasphere/go-ninja/model"
	owm "github.com/ninjasphere/openweathermap"
	"github.com/ninjasphere/sphere-go-led-controller/util"
)

var enableWeatherPane = config.MustBool("led.weather.enabled")
var weatherUpdateInterval = config.MustDuration("led.weather.updateInterval")

type WeatherPane struct {
	siteModel  *ninja.ServiceClient
	site       *model.Site
	getWeather *time.Timer
	weather    *owm.CurrentWeatherData
	image      util.Image
}

func NewWeatherPane(conn *ninja.Connection) *WeatherPane {

	pane := &WeatherPane{
		siteModel: conn.GetServiceClient("$home/services/SiteModel"),
		image:     util.LoadImage(util.ResolveImagePath("weather/loading.gif")),
	}

	if !enableWeatherPane {
		return pane
	}

	var err error
	pane.weather, err = owm.NewCurrent("C")
	if err != nil {
		log.Warningf("Failed to load weather api:", err)
		enableWeatherPane = false
	} else {
		go pane.GetWeather()
	}

	return pane
}

func (p *WeatherPane) GetWeather() {

	enableWeatherPane = false

	for {
		site := &model.Site{}
		err := p.siteModel.Call("fetch", config.MustString("siteId"), site, time.Second*5)

		if err == nil && (site.Longitude != nil || site.Latitude != nil) {
			p.site = site
			break
		}

		log.Infof("Failed to get site, or site has no location.")

		time.Sleep(time.Second * 5)
	}

	for {

		p.weather.CurrentByCoordinates(
			&owm.Coordinates{
				Longitude: *p.site.Longitude,
				Latitude:  *p.site.Latitude,
			},
		)

		filename := util.ResolveImagePath("weather/" + p.weather.Weather[0].Icon + ".png")

		if _, err := os.Stat(filename); os.IsNotExist(err) {
			enableWeatherPane = false
			fmt.Printf("Couldn't load image for weather: %s", filename)
			bugsnag.Notify(fmt.Errorf("Unknown weather icon: %s", filename), p.weather)
		} else {
			p.image = util.LoadImage(filename)
			enableWeatherPane = true
		}

		time.Sleep(weatherUpdateInterval)

	}

}

func (p *WeatherPane) IsEnabled() bool {
	return enableWeatherPane && p.weather.Unit != ""
}

func (p *WeatherPane) Gesture(gesture *gestic.GestureMessage) {
}

func (p *WeatherPane) Render() (*image.RGBA, error) {
	return p.image.GetNextFrame(), nil
}

func (p *WeatherPane) IsDirty() bool {
	return true
}
<|endoftext|>


<commit_before>package ui

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/fabiofalci/sconsify/sconsify"
	"github.com/jroimartin/gocui"
)

type KeyMapping struct {
	key  interface{}
	h    gocui.KeybindingHandler
	view string
}

type KeyFunctions struct {
	PauseTrack               string
	ShuffleMode              string
	ShuffleAllMode           string
	NextTrack                string
	ReplayTrack              string
	Search                   string
	Quit                     string
	QueueTrack               string
	RemoveTrackFromPlaylist  string
	RemoveTrackFromQueue     string
	RemoveAllTracksFromQueue string
}

var multipleKeysBuffer bytes.Buffer
var multipleKeysNumber int

func (k *KeyFunctions) defaultValues() {
	if k.PauseTrack == "" {
		k.PauseTrack = "p"
	}
	if k.ShuffleMode == "" {
		k.ShuffleMode = "s"
	}
	if k.ShuffleAllMode == "" {
		k.ShuffleAllMode = "S"
	}
	if k.NextTrack == "" {
		k.NextTrack = ">"
	}
	if k.ReplayTrack == "" {
		k.ReplayTrack = "<"
	}
	if k.Search == "" {
		k.Search = "/"
	}
	if k.Quit == "" {
		k.Quit = "q"
	}
	if k.QueueTrack == "" {
		k.QueueTrack = "u"
	}
	if k.RemoveTrackFromPlaylist == "" {
		k.RemoveTrackFromPlaylist = "d"
	}
	if k.RemoveTrackFromQueue == "" {
		k.RemoveTrackFromQueue = "d"
	}
	if k.RemoveAllTracksFromQueue == "" {
		k.RemoveAllTracksFromQueue = "D"
	}
}

func loadKeyFunctions() *KeyFunctions {
	if fileLocation := sconsify.GetKeyFunctionsFileLocation(); fileLocation != "" {
		if b, err := ioutil.ReadFile(fileLocation); err == nil {
			var keyFunctions KeyFunctions
			if err := json.Unmarshal(b, &keyFunctions); err == nil {
				return &keyFunctions
			}
		}
	}
	return &KeyFunctions{}
}

func getFirstRune(value string) rune {
	runes := []rune(value)
	return runes[0]
}

func keybindings() error {
	keyFunctions := loadKeyFunctions()
	keyFunctions.defaultValues()

	keys := make([]*KeyMapping, 0)

	for _, view := range []string{VIEW_TRACKS, VIEW_PLAYLISTS, VIEW_QUEUE} {
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.PauseTrack), view, pauseTrackCommand))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.ShuffleMode), view, setShuffleMode))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.ShuffleAllMode), view, setShuffleAllMode))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.NextTrack), view, nextTrackCommand))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.ReplayTrack), view, replayTrackCommand))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.Search), view, enableSearchInputCommand))
		addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.Quit), view, quit))
		addKeyBinding(&keys, newKeyMapping('j' , view, cursorDown))
		addKeyBinding(&keys, newKeyMapping('k', view, cursorUp))
	}

	allViews := ""
	addKeyBinding(&keys, newKeyMapping(gocui.KeySpace, VIEW_TRACKS, playCurrentSelectedTrack))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnter, VIEW_TRACKS, playCurrentSelectedTrack))
	addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.QueueTrack), VIEW_TRACKS, queueTrackCommand))
	addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.RemoveTrackFromPlaylist), VIEW_PLAYLISTS, removeTrackFromPlaylistsCommand))
	addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.RemoveTrackFromQueue), VIEW_QUEUE, removeTrackFromQueueCommand))
	addKeyBinding(&keys, newKeyMapping(getFirstRune(keyFunctions.RemoveAllTracksFromQueue), VIEW_QUEUE, removeAllTracksFromQueueCommand))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnter, VIEW_STATUS, searchCommand))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyHome, allViews, cursorHome))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnd, allViews, cursorEnd))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyPgup, allViews, cursorPgup))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyPgdn, allViews, cursorPgdn))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowDown, allViews, cursorDown))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowUp, allViews, cursorUp))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowLeft, VIEW_TRACKS, mainNextViewLeft))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowLeft, VIEW_QUEUE, nextView))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowRight, VIEW_PLAYLISTS, nextView))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowRight, VIEW_TRACKS, mainNextViewRight))
	addKeyBinding(&keys, newKeyMapping('h', VIEW_TRACKS, mainNextViewLeft))
	addKeyBinding(&keys, newKeyMapping('h', VIEW_QUEUE, nextView))
	addKeyBinding(&keys, newKeyMapping('l', VIEW_PLAYLISTS, nextView))
	addKeyBinding(&keys, newKeyMapping('l', VIEW_TRACKS, mainNextViewRight))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyCtrlC, allViews, quit))
	addKeyBinding(&keys, newKeyMapping('G', allViews, uppergCommand))

	for _, key := range keys {
		// it needs to copy the key because closures copy var references and we don't
		// want to execute always the last action
		keyCopy := key
		if err := gui.g.SetKeybinding(key.view, key.key, 0,
			func(g *gocui.Gui, v *gocui.View) error {
				err := keyCopy.h(g, v)
				resetMultipleKeys()
				return err
			}); err != nil {
			return err
		}
	}

	// multiple keys
	keys = make([]*KeyMapping, 0)

	addKeyBinding(&keys, newKeyMapping('g', allViews,
		func(g *gocui.Gui, v *gocui.View) error {
			return multipleKeysPressed(g, v, 'g')
		}))

	// numbers
	for i := 0; i < 10; i++ {
		numberCopy := i
		addKeyBinding(&keys, newKeyMapping(rune(i+48), allViews,
			func(g *gocui.Gui, v *gocui.View) error {
				return multipleKeysNumberPressed(numberCopy)
			}))
	}

	for _, key := range keys {
		keyCopy := key
		if err := gui.g.SetKeybinding(key.view, key.key, 0,
			func(g *gocui.Gui, v *gocui.View) error {
				return keyCopy.h(g, v)
			}); err != nil {
			return err
		}
	}
	return nil
}

func addKeyBinding(keys *[]*KeyMapping, key *KeyMapping) {
	*keys = append(*keys, key)
}

func newKeyMapping(key interface{}, view string, h gocui.KeybindingHandler) *KeyMapping {
	return &KeyMapping{key: key, h: h, view: view}
}

func resetMultipleKeys() {
	multipleKeysBuffer.Reset()
	multipleKeysNumber = 0
}

func multipleKeysNumberPressed(pressedNumber int) error {
	if multipleKeysNumber == 0 {
		multipleKeysNumber = pressedNumber
	} else {
		multipleKeysNumber = multipleKeysNumber*10 + pressedNumber
	}
	return nil
}

func multipleKeysPressed(g *gocui.Gui, v *gocui.View, pressedKey rune) error {
	multipleKeysBuffer.WriteRune(pressedKey)

	switch multipleKeysBuffer.String() {
	case "gg":
		ggCommand(g, v)
		resetMultipleKeys()
	}

	return nil
}

func playCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	player.Play()
	return nil
}

func pauseTrackCommand(g *gocui.Gui, v *gocui.View) error {
	player.Pause()
	return nil
}

func setShuffleMode(g *gocui.Gui, v *gocui.View) error {
	playlists.InvertMode(sconsify.ShuffleMode)
	gui.updateCurrentStatus()
	return nil
}

func setShuffleAllMode(g *gocui.Gui, v *gocui.View) error {
	playlists.InvertMode(sconsify.ShuffleAllMode)
	gui.updateCurrentStatus()
	return nil
}

func nextTrackCommand(g *gocui.Gui, v *gocui.View) error {
	gui.playNext()
	return nil
}

func replayTrackCommand(g *gocui.Gui, v *gocui.View) error {
	gui.replay()
	return nil
}

func queueTrackCommand(g *gocui.Gui, v *gocui.View) error {
	if playlist, trackIndex := gui.getSelectedPlaylistAndTrack(); playlist != nil {
		track := playlist.Track(trackIndex)
		fmt.Fprintf(gui.queueView, "%v\n", track.GetTitle())
		queue.Add(track)
	}
	return nil
}

func removeAllTracksFromQueueCommand(g *gocui.Gui, v *gocui.View) error {
	queue.RemoveAll()
	gui.updateQueueView()
	return gui.enableTracksView()
}

func removeTrackFromQueueCommand(g *gocui.Gui, v *gocui.View) error {
	if index := gui.getQueueSelectedTrackIndex(); index > -1 {
		queue.Remove(index)
		gui.updateQueueView()
	}
	return nil
}

func removeTrackFromPlaylistsCommand(g *gocui.Gui, v *gocui.View) error {
	if playlist := gui.getSelectedPlaylist(); playlist != nil && playlist.IsSearch() {
		playlists.Remove(playlist.Name())
		gui.updatePlaylistsView()
		gui.updateTracksView()
	}
	return nil
}

func enableSearchInputCommand(g *gocui.Gui, v *gocui.View) error {
	gui.clearStatusView()
	gui.statusView.Editable = true
	gui.g.SetCurrentView(VIEW_STATUS)
	return nil
}

func searchCommand(g *gocui.Gui, v *gocui.View) error {
	// after user hit Enter, the typed command is at position -1
	query, _ := gui.statusView.Line(-1)
	query = strings.Trim(query, " ")
	if query != "" {
		events.Search(query)
	}
	gui.enableSideView()
	gui.clearStatusView()
	gui.statusView.Editable = false
	gui.updateCurrentStatus()
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	consoleUserInterface.Shutdown()
	// TODO wait for shutdown
	// <-events.ShutdownUpdates()
	return gocui.Quit
}
<commit_msg>Custom multiple keys command<commit_after>package ui

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"strings"

	"github.com/fabiofalci/sconsify/sconsify"
	"github.com/jroimartin/gocui"
)

type KeyMapping struct {
	key  interface{}
	h    gocui.KeybindingHandler
	view string
}

type KeyFunctions struct {
	PauseTrack               string
	ShuffleMode              string
	ShuffleAllMode           string
	NextTrack                string
	ReplayTrack              string
	Search                   string
	Quit                     string
	QueueTrack               string
	RemoveTrackFromPlaylist  string
	RemoveTrackFromQueue     string
	RemoveAllTracksFromQueue string
}

var multipleKeysBuffer bytes.Buffer
var multipleKeysNumber int
var multipleKeysHandlers map[string]gocui.KeybindingHandler

func (k *KeyFunctions) defaultValues() {
	if k.PauseTrack == "" {
		k.PauseTrack = "p"
	}
	if k.ShuffleMode == "" {
		k.ShuffleMode = "s"
	}
	if k.ShuffleAllMode == "" {
		k.ShuffleAllMode = "S"
	}
	if k.NextTrack == "" {
		k.NextTrack = ">"
	}
	if k.ReplayTrack == "" {
		k.ReplayTrack = "<"
	}
	if k.Search == "" {
		k.Search = "/"
	}
	if k.Quit == "" {
		k.Quit = "q"
	}
	if k.QueueTrack == "" {
		k.QueueTrack = "u"
	}
	if k.RemoveTrackFromPlaylist == "" {
		k.RemoveTrackFromPlaylist = "d"
	}
	if k.RemoveTrackFromQueue == "" {
		k.RemoveTrackFromQueue = "d"
	}
	if k.RemoveAllTracksFromQueue == "" {
		k.RemoveAllTracksFromQueue = "D"
	}
}

func loadKeyFunctions() *KeyFunctions {
	if fileLocation := sconsify.GetKeyFunctionsFileLocation(); fileLocation != "" {
		if b, err := ioutil.ReadFile(fileLocation); err == nil {
			var keyFunctions KeyFunctions
			if err := json.Unmarshal(b, &keyFunctions); err == nil {
				return &keyFunctions
			}
		}
	}
	return &KeyFunctions{}
}

func getFirstRune(value string) rune {
	return getAsRuneArray(value)[0]
}

func getAsRuneArray(value string) []rune {
	return []rune(value)
}

func isMultipleKey(value string) bool {
	return len(getAsRuneArray(value)) > 1
}

func createKeyMapping(handler gocui.KeybindingHandler, command string, view string) (*KeyMapping, bool) {
	if isMultipleKey(command) {
		keyRune := getAsRuneArray(command)
		multipleKeysHandlers[command] = handler
		return newKeyMapping(keyRune[0], view,
			func(g *gocui.Gui, v *gocui.View) error {
				return multipleKeysPressed(g, v, keyRune[0])
			}), true
	}
	return newKeyMapping(getFirstRune(command), view, handler), false
}

func addToKeys(isMultiple bool, keyMapping *KeyMapping, keys *[]*KeyMapping, multipleKeys *[]*KeyMapping) {
	if isMultiple {
		addKeyBinding(multipleKeys, keyMapping)
	} else {
		addKeyBinding(keys, keyMapping)
	}
}

func keybindings() error {
	keyFunctions := loadKeyFunctions()
	keyFunctions.defaultValues()

	keys := make([]*KeyMapping, 0)
	multipleKeys := make([]*KeyMapping, 0)
	multipleKeysHandlers = make(map[string]gocui.KeybindingHandler)

	var keyMapping *KeyMapping
	var isMultiple bool

	for _, view := range []string{VIEW_TRACKS, VIEW_PLAYLISTS, VIEW_QUEUE} {
		keyMapping, isMultiple = createKeyMapping(pauseTrackCommand, keyFunctions.PauseTrack, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(setShuffleMode, keyFunctions.ShuffleMode, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(setShuffleAllMode, keyFunctions.ShuffleAllMode, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(nextTrackCommand, keyFunctions.NextTrack, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(replayTrackCommand, keyFunctions.ReplayTrack, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(enableSearchInputCommand, keyFunctions.Search, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		keyMapping, isMultiple = createKeyMapping(quit, keyFunctions.Quit, view)
		addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

		addKeyBinding(&keys, newKeyMapping('j', view, cursorDown))
		addKeyBinding(&keys, newKeyMapping('k', view, cursorUp))
	}

	allViews := ""
	keyMapping, isMultiple = createKeyMapping(queueTrackCommand, keyFunctions.QueueTrack, allViews)
	addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

	keyMapping, isMultiple = createKeyMapping(removeTrackFromPlaylistsCommand, keyFunctions.RemoveTrackFromPlaylist, allViews)
	addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

	keyMapping, isMultiple = createKeyMapping(removeTrackFromQueueCommand, keyFunctions.RemoveTrackFromQueue, allViews)
	addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

	keyMapping, isMultiple = createKeyMapping(removeAllTracksFromQueueCommand, keyFunctions.RemoveAllTracksFromQueue, allViews)
	addToKeys(isMultiple, keyMapping, &keys, &multipleKeys)

	addKeyBinding(&keys, newKeyMapping(gocui.KeySpace, VIEW_TRACKS, playCurrentSelectedTrack))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnter, VIEW_TRACKS, playCurrentSelectedTrack))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnter, VIEW_STATUS, searchCommand))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyHome, allViews, cursorHome))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyEnd, allViews, cursorEnd))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyPgup, allViews, cursorPgup))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyPgdn, allViews, cursorPgdn))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowDown, allViews, cursorDown))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowUp, allViews, cursorUp))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowLeft, VIEW_TRACKS, mainNextViewLeft))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowLeft, VIEW_QUEUE, nextView))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowRight, VIEW_PLAYLISTS, nextView))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyArrowRight, VIEW_TRACKS, mainNextViewRight))
	addKeyBinding(&keys, newKeyMapping('h', VIEW_TRACKS, mainNextViewLeft))
	addKeyBinding(&keys, newKeyMapping('h', VIEW_QUEUE, nextView))
	addKeyBinding(&keys, newKeyMapping('l', VIEW_PLAYLISTS, nextView))
	addKeyBinding(&keys, newKeyMapping('l', VIEW_TRACKS, mainNextViewRight))
	addKeyBinding(&keys, newKeyMapping(gocui.KeyCtrlC, allViews, quit))
	addKeyBinding(&keys, newKeyMapping('G', allViews, uppergCommand))

	addKeyBinding(&multipleKeys, newKeyMapping('g', allViews,
		func(g *gocui.Gui, v *gocui.View) error {
			return multipleKeysPressed(g, v, 'g')
		}))

	// numbers
	for i := 0; i < 10; i++ {
		numberCopy := i
		addKeyBinding(&multipleKeys, newKeyMapping(rune(i+48), allViews,
			func(g *gocui.Gui, v *gocui.View) error {
				return multipleKeysNumberPressed(numberCopy)
			}))
	}

	for _, key := range keys {
		// it needs to copy the key because closures copy var references and we don't
		// want to execute always the last action
		keyCopy := key
		if err := gui.g.SetKeybinding(key.view, key.key, 0,
			func(g *gocui.Gui, v *gocui.View) error {
				err := keyCopy.h(g, v)
				resetMultipleKeys()
				return err
			}); err != nil {
			return err
		}
	}

	for _, key := range multipleKeys {
		keyCopy := key
		if err := gui.g.SetKeybinding(key.view, key.key, 0,
			func(g *gocui.Gui, v *gocui.View) error {
				return keyCopy.h(g, v)
			}); err != nil {
			return err
		}
	}
	return nil
}

func addKeyBinding(keys *[]*KeyMapping, key *KeyMapping) {
	*keys = append(*keys, key)
}

func newKeyMapping(key interface{}, view string, h gocui.KeybindingHandler) *KeyMapping {
	return &KeyMapping{key: key, h: h, view: view}
}

func resetMultipleKeys() {
	multipleKeysBuffer.Reset()
	multipleKeysNumber = 0
}

func multipleKeysNumberPressed(pressedNumber int) error {
	if multipleKeysNumber == 0 {
		multipleKeysNumber = pressedNumber
	} else {
		multipleKeysNumber = multipleKeysNumber*10 + pressedNumber
	}
	return nil
}

func multipleKeysPressed(g *gocui.Gui, v *gocui.View, pressedKey rune) error {
	multipleKeysBuffer.WriteRune(pressedKey)

	handler := multipleKeysHandlers[multipleKeysBuffer.String()]
	if handler != nil {
		handler(g, v)
		resetMultipleKeys()
	} else {
		switch multipleKeysBuffer.String() {
		case "gg":
			ggCommand(g, v)
			resetMultipleKeys()
		}
	}

	return nil
}

func playCurrentSelectedTrack(g *gocui.Gui, v *gocui.View) error {
	player.Play()
	return nil
}

func pauseTrackCommand(g *gocui.Gui, v *gocui.View) error {
	player.Pause()
	return nil
}

func setShuffleMode(g *gocui.Gui, v *gocui.View) error {
	playlists.InvertMode(sconsify.ShuffleMode)
	gui.updateCurrentStatus()
	return nil
}

func setShuffleAllMode(g *gocui.Gui, v *gocui.View) error {
	playlists.InvertMode(sconsify.ShuffleAllMode)
	gui.updateCurrentStatus()
	return nil
}

func nextTrackCommand(g *gocui.Gui, v *gocui.View) error {
	gui.playNext()
	return nil
}

func replayTrackCommand(g *gocui.Gui, v *gocui.View) error {
	gui.replay()
	return nil
}

func queueTrackCommand(g *gocui.Gui, v *gocui.View) error {
	if playlist, trackIndex := gui.getSelectedPlaylistAndTrack(); playlist != nil {
		track := playlist.Track(trackIndex)
		fmt.Fprintf(gui.queueView, "%v\n", track.GetTitle())
		queue.Add(track)
	}
	return nil
}

func removeAllTracksFromQueueCommand(g *gocui.Gui, v *gocui.View) error {
	queue.RemoveAll()
	gui.updateQueueView()
	return gui.enableTracksView()
}

func removeTrackFromQueueCommand(g *gocui.Gui, v *gocui.View) error {
	if index := gui.getQueueSelectedTrackIndex(); index > -1 {
		queue.Remove(index)
		gui.updateQueueView()
	}
	return nil
}

func removeTrackFromPlaylistsCommand(g *gocui.Gui, v *gocui.View) error {
	if playlist := gui.getSelectedPlaylist(); playlist != nil && playlist.IsSearch() {
		playlists.Remove(playlist.Name())
		gui.updatePlaylistsView()
		gui.updateTracksView()
	}
	return nil
}

func enableSearchInputCommand(g *gocui.Gui, v *gocui.View) error {
	gui.clearStatusView()
	gui.statusView.Editable = true
	gui.g.SetCurrentView(VIEW_STATUS)
	return nil
}

func searchCommand(g *gocui.Gui, v *gocui.View) error {
	// after user hit Enter, the typed command is at position -1
	query, _ := gui.statusView.Line(-1)
	query = strings.Trim(query, " ")
	if query != "" {
		events.Search(query)
	}
	gui.enableSideView()
	gui.clearStatusView()
	gui.statusView.Editable = false
	gui.updateCurrentStatus()
	return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
	consoleUserInterface.Shutdown()
	// TODO wait for shutdown
	// <-events.ShutdownUpdates()
	return gocui.Quit
}
<|endoftext|>


<commit_before>package ollie

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/platforms/ble"
	"gobot.io/x/gobot/platforms/sphero"
)

// Driver is the Gobot driver for the Sphero Ollie robot
type Driver struct {
	name              string
	connection        gobot.Connection
	seq               uint8
	collisionResponse []uint8
	packetChannel     chan *packet
	gobot.Eventer
}

const (
	// bluetooth service IDs
	spheroBLEService    = "22bb746f2bb075542d6f726568705327"
	robotControlService = "22bb746f2ba075542d6f726568705327"

	// BLE characteristic IDs
	wakeCharacteristic     = "22bb746f2bbf75542d6f726568705327"
	txPowerCharacteristic  = "22bb746f2bb275542d6f726568705327"
	antiDosCharacteristic  = "22bb746f2bbd75542d6f726568705327"
	commandsCharacteristic = "22bb746f2ba175542d6f726568705327"
	responseCharacteristic = "22bb746f2ba675542d6f726568705327"

	// SensorData event
	SensorData = "sensordata"

	// Collision event
	Collision = "collision"

	// Error event
	Error = "error"

	// Packet header size
	PacketHeaderSize = 5

	// Response packet max size
	ResponsePacketMaxSize = 20

	// Collision Packet data size: The number of bytes following the DLEN field through the end of the packet
	CollisionDataSize = 17

	// Full size of the collision response
	CollisionResponseSize = PacketHeaderSize + CollisionDataSize
)

type packet struct {
	header   []uint8
	body     []uint8
	checksum uint8
}

// NewDriver creates a Driver for a Sphero Ollie
func NewDriver(a ble.BLEConnector) *Driver {
	n := &Driver{
		name:          gobot.DefaultName("Ollie"),
		connection:    a,
		Eventer:       gobot.NewEventer(),
		packetChannel: make(chan *packet, 1024),
	}

	n.AddEvent(Collision)

	return n
}

// Connection returns the connection to this Ollie
func (b *Driver) Connection() gobot.Connection { return b.connection }

// Name returns the name for the Driver
func (b *Driver) Name() string { return b.name }

// SetName sets the Name for the Driver
func (b *Driver) SetName(n string) { b.name = n }

// adaptor returns BLE adaptor
func (b *Driver) adaptor() ble.BLEConnector {
	return b.Connection().(ble.BLEConnector)
}

// Start tells driver to get ready to do work
func (b *Driver) Start() (err error) {
	b.Init()

	// send commands
	go func() {
		for {
			packet := <-b.packetChannel
			err := b.write(packet)
			if err != nil {
				b.Publish(b.Event(Error), err)
			}
		}
	}()

	go func() {
		for {
			b.adaptor().ReadCharacteristic(responseCharacteristic)
			time.Sleep(100 * time.Millisecond)
		}
	}()

	b.ConfigureCollisionDetection(DefaultCollisionConfig())

	return
}

// Halt stops Ollie driver (void)
func (b *Driver) Halt() (err error) {
	b.Sleep()
	time.Sleep(750 * time.Microsecond)
	return
}

// Init is used to initialize the Ollie
func (b *Driver) Init() (err error) {
	b.AntiDOSOff()
	b.SetTXPower(7)
	b.Wake()

	// subscribe to Sphero response notifications
	b.adaptor().Subscribe(responseCharacteristic, b.HandleResponses)

	return
}

// AntiDOSOff turns off Anti-DOS code so we can control Ollie
func (b *Driver) AntiDOSOff() (err error) {
	str := "011i3"
	buf := &bytes.Buffer{}
	buf.WriteString(str)

	err = b.adaptor().WriteCharacteristic(antiDosCharacteristic, buf.Bytes())
	if err != nil {
		fmt.Println("AntiDOSOff error:", err)
		return err
	}

	return
}

// Wake wakes Ollie up so we can play
func (b *Driver) Wake() (err error) {
	buf := []byte{0x01}

	err = b.adaptor().WriteCharacteristic(wakeCharacteristic, buf)
	if err != nil {
		fmt.Println("Wake error:", err)
		return err
	}

	return
}

// SetTXPower sets transmit level
func (b *Driver) SetTXPower(level int) (err error) {
	buf := []byte{byte(level)}

	err = b.adaptor().WriteCharacteristic(txPowerCharacteristic, buf)
	if err != nil {
		fmt.Println("SetTXLevel error:", err)
		return err
	}

	return
}

// HandleResponses handles responses returned from Ollie
func (b *Driver) HandleResponses(data []byte, e error) {
	//fmt.Println("response data:", data, e)

	b.handleCollisionDetected(data)
}

// SetRGB sets the Ollie to the given r, g, and b values
func (b *Driver) SetRGB(r uint8, g uint8, bl uint8) {
	b.packetChannel <- b.craftPacket([]uint8{r, g, bl, 0x01}, 0x02, 0x20)
}

// Roll tells the Ollie to roll
func (b *Driver) Roll(speed uint8, heading uint16) {
	b.packetChannel <- b.craftPacket([]uint8{speed, uint8(heading >> 8), uint8(heading & 0xFF), 0x01}, 0x02, 0x30)
}

// Stop tells the Ollie to stop
func (b *Driver) Stop() {
	b.Roll(0, 0)
}

// Sleep says Go to sleep
func (b *Driver) Sleep() {
	b.packetChannel <- b.craftPacket([]uint8{0x00, 0x00, 0x00, 0x00, 0x00}, 0x00, 0x22)
}

// EnableStopOnDisconnect auto-sends a Stop command after losing the connection
func (b *Driver) EnableStopOnDisconnect() {
	b.packetChannel <- b.craftPacket([]uint8{0x00, 0x00, 0x00, 0x01}, 0x02, 0x37)
}

// ConfigureCollisionDetection configures the sensitivity of the detection.
func (b *Driver) ConfigureCollisionDetection(cc sphero.CollisionConfig) {
	b.packetChannel <- b.craftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12)
}

func (b *Driver) write(packet *packet) (err error) {
	buf := append(packet.header, packet.body...)
	buf = append(buf, packet.checksum)
	err = b.adaptor().WriteCharacteristic(commandsCharacteristic, buf)
	if err != nil {
		fmt.Println("send command error:", err)
		return err
	}

	b.seq++
	return
}

func (b *Driver) craftPacket(body []uint8, did byte, cid byte) *packet {
	packet := new(packet)
	packet.body = body
	dlen := len(packet.body) + 1
	packet.header = []uint8{0xFF, 0xFF, did, cid, b.seq, uint8(dlen)}
	packet.checksum = b.calculateChecksum(packet)
	return packet
}

func (b *Driver) handleCollisionDetected(data []uint8) {

	if len(data) == ResponsePacketMaxSize {
		// Check if this is the header of collision response. (i.e. first part of data)
		// Collision response is 22 bytes long. (individual packet size is maxed at 20)
		switch data[1] {
		case 0xFE:
			if data[2] == 0x07 {
				// response code 7 is for a detected collision
				if len(b.collisionResponse) == 0 {
					b.collisionResponse = append(b.collisionResponse, data...)
				}
			}
		}
	} else if len(data) == CollisionResponseSize-ResponsePacketMaxSize {
		// if this is the remaining part of the collision response,
		// then make sure the header and first part of data is already received
		if len(b.collisionResponse) == ResponsePacketMaxSize {
			b.collisionResponse = append(b.collisionResponse, data...)
		}
	} else {
		return // not collision event
	}

	// check expected sizes
	if len(b.collisionResponse) != CollisionResponseSize || b.collisionResponse[4] != CollisionDataSize {
		return
	}

	// confirm checksum
	size := len(b.collisionResponse)
	chk := b.collisionResponse[size-1] // last byte is checksum
	if chk != calculateChecksum(b.collisionResponse[2:size-1]) {
		return
	}

	var collision sphero.CollisionPacket
	buffer := bytes.NewBuffer(b.collisionResponse[5:]) // skip header
	binary.Read(buffer, binary.BigEndian, &collision)
	b.collisionResponse = nil // clear the current response

	b.Publish(Collision, collision)
}

func (b *Driver) calculateChecksum(packet *packet) uint8 {
	buf := append(packet.header, packet.body...)
	return calculateChecksum(buf[2:])
}

func calculateChecksum(buf []byte) byte {
	var calculatedChecksum uint16
	for i := range buf {
		calculatedChecksum += uint16(buf[i])
	}
	return uint8(^(calculatedChecksum % 256))
}
<commit_msg>ollie: correct race condition error on seq<commit_after>package ollie

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"sync"
	"time"

	"gobot.io/x/gobot"
	"gobot.io/x/gobot/platforms/ble"
	"gobot.io/x/gobot/platforms/sphero"
)

// Driver is the Gobot driver for the Sphero Ollie robot
type Driver struct {
	name              string
	connection        gobot.Connection
	seq               uint8
	mtx               sync.Mutex
	collisionResponse []uint8
	packetChannel     chan *packet
	gobot.Eventer
}

const (
	// bluetooth service IDs
	spheroBLEService    = "22bb746f2bb075542d6f726568705327"
	robotControlService = "22bb746f2ba075542d6f726568705327"

	// BLE characteristic IDs
	wakeCharacteristic     = "22bb746f2bbf75542d6f726568705327"
	txPowerCharacteristic  = "22bb746f2bb275542d6f726568705327"
	antiDosCharacteristic  = "22bb746f2bbd75542d6f726568705327"
	commandsCharacteristic = "22bb746f2ba175542d6f726568705327"
	responseCharacteristic = "22bb746f2ba675542d6f726568705327"

	// SensorData event
	SensorData = "sensordata"

	// Collision event
	Collision = "collision"

	// Error event
	Error = "error"

	// Packet header size
	PacketHeaderSize = 5

	// Response packet max size
	ResponsePacketMaxSize = 20

	// Collision Packet data size: The number of bytes following the DLEN field through the end of the packet
	CollisionDataSize = 17

	// Full size of the collision response
	CollisionResponseSize = PacketHeaderSize + CollisionDataSize
)

type packet struct {
	header   []uint8
	body     []uint8
	checksum uint8
}

// NewDriver creates a Driver for a Sphero Ollie
func NewDriver(a ble.BLEConnector) *Driver {
	n := &Driver{
		name:          gobot.DefaultName("Ollie"),
		connection:    a,
		Eventer:       gobot.NewEventer(),
		packetChannel: make(chan *packet, 1024),
	}

	n.AddEvent(Collision)

	return n
}

// Connection returns the connection to this Ollie
func (b *Driver) Connection() gobot.Connection { return b.connection }

// Name returns the name for the Driver
func (b *Driver) Name() string { return b.name }

// SetName sets the Name for the Driver
func (b *Driver) SetName(n string) { b.name = n }

// adaptor returns BLE adaptor
func (b *Driver) adaptor() ble.BLEConnector {
	return b.Connection().(ble.BLEConnector)
}

// Start tells driver to get ready to do work
func (b *Driver) Start() (err error) {
	b.Init()

	// send commands
	go func() {
		for {
			packet := <-b.packetChannel
			err := b.write(packet)
			if err != nil {
				b.Publish(b.Event(Error), err)
			}
		}
	}()

	go func() {
		for {
			b.adaptor().ReadCharacteristic(responseCharacteristic)
			time.Sleep(100 * time.Millisecond)
		}
	}()

	b.ConfigureCollisionDetection(DefaultCollisionConfig())

	return
}

// Halt stops Ollie driver (void)
func (b *Driver) Halt() (err error) {
	b.Sleep()
	time.Sleep(750 * time.Microsecond)
	return
}

// Init is used to initialize the Ollie
func (b *Driver) Init() (err error) {
	b.AntiDOSOff()
	b.SetTXPower(7)
	b.Wake()

	// subscribe to Sphero response notifications
	b.adaptor().Subscribe(responseCharacteristic, b.HandleResponses)

	return
}

// AntiDOSOff turns off Anti-DOS code so we can control Ollie
func (b *Driver) AntiDOSOff() (err error) {
	str := "011i3"
	buf := &bytes.Buffer{}
	buf.WriteString(str)

	err = b.adaptor().WriteCharacteristic(antiDosCharacteristic, buf.Bytes())
	if err != nil {
		fmt.Println("AntiDOSOff error:", err)
		return err
	}

	return
}

// Wake wakes Ollie up so we can play
func (b *Driver) Wake() (err error) {
	buf := []byte{0x01}

	err = b.adaptor().WriteCharacteristic(wakeCharacteristic, buf)
	if err != nil {
		fmt.Println("Wake error:", err)
		return err
	}

	return
}

// SetTXPower sets transmit level
func (b *Driver) SetTXPower(level int) (err error) {
	buf := []byte{byte(level)}

	err = b.adaptor().WriteCharacteristic(txPowerCharacteristic, buf)
	if err != nil {
		fmt.Println("SetTXLevel error:", err)
		return err
	}

	return
}

// HandleResponses handles responses returned from Ollie
func (b *Driver) HandleResponses(data []byte, e error) {
	//fmt.Println("response data:", data, e)

	b.handleCollisionDetected(data)
}

// SetRGB sets the Ollie to the given r, g, and b values
func (b *Driver) SetRGB(r uint8, g uint8, bl uint8) {
	b.packetChannel <- b.craftPacket([]uint8{r, g, bl, 0x01}, 0x02, 0x20)
}

// Roll tells the Ollie to roll
func (b *Driver) Roll(speed uint8, heading uint16) {
	b.packetChannel <- b.craftPacket([]uint8{speed, uint8(heading >> 8), uint8(heading & 0xFF), 0x01}, 0x02, 0x30)
}

// Stop tells the Ollie to stop
func (b *Driver) Stop() {
	b.Roll(0, 0)
}

// Sleep says Go to sleep
func (b *Driver) Sleep() {
	b.packetChannel <- b.craftPacket([]uint8{0x00, 0x00, 0x00, 0x00, 0x00}, 0x00, 0x22)
}

// EnableStopOnDisconnect auto-sends a Stop command after losing the connection
func (b *Driver) EnableStopOnDisconnect() {
	b.packetChannel <- b.craftPacket([]uint8{0x00, 0x00, 0x00, 0x01}, 0x02, 0x37)
}

// ConfigureCollisionDetection configures the sensitivity of the detection.
func (b *Driver) ConfigureCollisionDetection(cc sphero.CollisionConfig) {
	b.packetChannel <- b.craftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12)
}

func (b *Driver) write(packet *packet) (err error) {
	buf := append(packet.header, packet.body...)
	buf = append(buf, packet.checksum)
	err = b.adaptor().WriteCharacteristic(commandsCharacteristic, buf)
	if err != nil {
		fmt.Println("send command error:", err)
		return err
	}

	b.mtx.Lock()
	defer b.mtx.Unlock()
	b.seq++
	return
}

func (b *Driver) craftPacket(body []uint8, did byte, cid byte) *packet {
	b.mtx.Lock()
	defer b.mtx.Unlock()

	packet := new(packet)
	packet.body = body
	dlen := len(packet.body) + 1
	packet.header = []uint8{0xFF, 0xFF, did, cid, b.seq, uint8(dlen)}
	packet.checksum = b.calculateChecksum(packet)
	return packet
}

func (b *Driver) handleCollisionDetected(data []uint8) {

	if len(data) == ResponsePacketMaxSize {
		// Check if this is the header of collision response. (i.e. first part of data)
		// Collision response is 22 bytes long. (individual packet size is maxed at 20)
		switch data[1] {
		case 0xFE:
			if data[2] == 0x07 {
				// response code 7 is for a detected collision
				if len(b.collisionResponse) == 0 {
					b.collisionResponse = append(b.collisionResponse, data...)
				}
			}
		}
	} else if len(data) == CollisionResponseSize-ResponsePacketMaxSize {
		// if this is the remaining part of the collision response,
		// then make sure the header and first part of data is already received
		if len(b.collisionResponse) == ResponsePacketMaxSize {
			b.collisionResponse = append(b.collisionResponse, data...)
		}
	} else {
		return // not collision event
	}

	// check expected sizes
	if len(b.collisionResponse) != CollisionResponseSize || b.collisionResponse[4] != CollisionDataSize {
		return
	}

	// confirm checksum
	size := len(b.collisionResponse)
	chk := b.collisionResponse[size-1] // last byte is checksum
	if chk != calculateChecksum(b.collisionResponse[2:size-1]) {
		return
	}

	var collision sphero.CollisionPacket
	buffer := bytes.NewBuffer(b.collisionResponse[5:]) // skip header
	binary.Read(buffer, binary.BigEndian, &collision)
	b.collisionResponse = nil // clear the current response

	b.Publish(Collision, collision)
}

func (b *Driver) calculateChecksum(packet *packet) uint8 {
	buf := append(packet.header, packet.body...)
	return calculateChecksum(buf[2:])
}

func calculateChecksum(buf []byte) byte {
	var calculatedChecksum uint16
	for i := range buf {
		calculatedChecksum += uint16(buf[i])
	}
	return uint8(^(calculatedChecksum % 256))
}
<|endoftext|>


<commit_before>package dwl

import (
	"dwl/progress"
	"dwl/settings"
)

type DWL struct {
	sets      *settings.Settings
	loader    Loader
	size      int64
	isLoading bool
}

type OnChangeFunc func(int, []progress.DownloadProgress)

func NewDWL(sets *settings.Settings) *DWL {
	d := new(DWL)
	d.sets = sets
	return d
}

func (d *DWL) Complete() bool {
	if d.loader == nil {
		return false
	}
	dp := d.loader.GetProgress()
	if dp == nil {
		return false
	}
	for _, p := range dp {
		if !p.Complete() {
			return false
		}
	}
	return true
}

func (d *DWL) GetProgress() []progress.DownloadProgress {
	if d.loader != nil {
		return d.loader.GetProgress()
	}
	return nil
}
<commit_msg>set def threads<commit_after>package dwl

import (
	"dwl/progress"
	"dwl/settings"
	"runtime"
)

type DWL struct {
	sets      *settings.Settings
	loader    Loader
	size      int64
	isLoading bool
}

type OnChangeFunc func(int, []progress.DownloadProgress)

func NewDWL(sets *settings.Settings) *DWL {
	d := new(DWL)
	d.sets = sets
	if d.sets.Threads == -1 {
		d.sets.Threads = runtime.NumCPU() * 4
	}
	return d
}

func (d *DWL) Complete() bool {
	if d.loader == nil {
		return false
	}
	dp := d.loader.GetProgress()
	if dp == nil {
		return false
	}
	for _, p := range dp {
		if !p.Complete() {
			return false
		}
	}
	return true
}

func (d *DWL) GetProgress() []progress.DownloadProgress {
	if d.loader != nil {
		return d.loader.GetProgress()
	}
	return nil
}
<|endoftext|>


<commit_before>//   Copyright 2018 MSolution.IO
//
//   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 plugins_account_unused_ebs

import (
	"bytes"
	"fmt"

	"github.com/aws/aws-sdk-go/service/ec2"

	core "github.com/trackit/trackit-server/plugins/account/core"
	"github.com/trackit/trackit-server/plugins/utils"
)

func init() {
	// Register the plugin
	core.AccountPlugin{
		Name:        "unused_ebs",
		Description: "unused ebs plugin",
		Func:        processUnusedEBS,
	}.Register()
}

// formatResult takes a map of unused EBS and returns 3 strings to fill the
// core.PluginResult struct
func formatResult(unusedByAZ map[string]int) (string, string, string) {
	total := 0
	detail := new(bytes.Buffer)
	for az, totalAz := range unusedByAZ {
		if total == 0 {
			fmt.Fprintf(detail, fmt.Sprintf("%s(%d)", az, totalAz))
		} else {
			fmt.Fprintf(detail, fmt.Sprintf(", %s(%d)", az, totalAz))
		}
		total += totalAz
	}
	if total == 0 {
		return "You don't have any unused EBS", "", ""
	}
	return fmt.Sprintf("You have %d unused EBS", total), detail.String(), ""
}

// getUnusedEBsRecommendation searches for unused ebs in every region available
// It takes a core.PluginParams struct and returns 3 strings (result, details, error)
func getUnusedEBsRecommendation(pluginParams core.PluginParams) (string, string, string) {
	svc := plugins_utils.GetEc2ClientSession(pluginParams.AccountCredentials, nil)
	regionsOutput, err := svc.DescribeRegions(&ec2.DescribeRegionsInput{})
	if err != nil {
		return "", "", fmt.Sprintf("Unable to retrieve the list of regions: %s", err.Error())
	}

	unusedByAZ := make(map[string]int)
	for _, region := range regionsOutput.Regions {
		svc = plugins_utils.GetEc2ClientSession(pluginParams.AccountCredentials, region.RegionName)
		err = svc.DescribeVolumesPages(&ec2.DescribeVolumesInput{},
			func(page *ec2.DescribeVolumesOutput, lastPage bool) bool {
				for _, volume := range page.Volumes {
					if volume != nil && *volume.State == "available" {
						unusedByAZ[*volume.AvailabilityZone] = unusedByAZ[*volume.AvailabilityZone] + 1
					}
				}
				return lastPage
			})
		if err != nil {
			return "", "", fmt.Sprintf("Unable to list volumes: %s", err.Error())
		}
	}
	return formatResult(unusedByAZ)
}

// processUnusedEBS is the handler function for the unused_ebs plugin
// it takes a core.PluginParams struct and returns a core.PluginResult struct
func processUnusedEBS(params core.PluginParams) core.PluginResult {
	result, details, err := getUnusedEBsRecommendation(params)
	return core.PluginResult{
		Result:  result,
		Details: details,
		Error:   err,
	}
}
<commit_msg>fix missing region ebs plugin<commit_after>//   Copyright 2018 MSolution.IO
//
//   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 plugins_account_unused_ebs

import (
	"bytes"
	"fmt"

	"github.com/aws/aws-sdk-go/service/ec2"

	"github.com/trackit/trackit-server/config"
	core "github.com/trackit/trackit-server/plugins/account/core"
	"github.com/trackit/trackit-server/plugins/utils"
)

func init() {
	// Register the plugin
	core.AccountPlugin{
		Name:        "unused_ebs",
		Description: "unused ebs plugin",
		Func:        processUnusedEBS,
	}.Register()
}

// formatResult takes a map of unused EBS and returns 3 strings to fill the
// core.PluginResult struct
func formatResult(unusedByAZ map[string]int) (string, string, string) {
	total := 0
	detail := new(bytes.Buffer)
	for az, totalAz := range unusedByAZ {
		if total == 0 {
			fmt.Fprintf(detail, fmt.Sprintf("%s(%d)", az, totalAz))
		} else {
			fmt.Fprintf(detail, fmt.Sprintf(", %s(%d)", az, totalAz))
		}
		total += totalAz
	}
	if total == 0 {
		return "You don't have any unused EBS", "", ""
	}
	return fmt.Sprintf("You have %d unused EBS", total), detail.String(), ""
}

// getUnusedEBsRecommendation searches for unused ebs in every region available
// It takes a core.PluginParams struct and returns 3 strings (result, details, error)
func getUnusedEBsRecommendation(pluginParams core.PluginParams) (string, string, string) {
	svc := plugins_utils.GetEc2ClientSession(pluginParams.AccountCredentials, &config.AwsRegion)
	regionsOutput, err := svc.DescribeRegions(&ec2.DescribeRegionsInput{})
	if err != nil {
		return "", "", fmt.Sprintf("Unable to retrieve the list of regions: %s", err.Error())
	}

	unusedByAZ := make(map[string]int)
	for _, region := range regionsOutput.Regions {
		svc = plugins_utils.GetEc2ClientSession(pluginParams.AccountCredentials, region.RegionName)
		err = svc.DescribeVolumesPages(&ec2.DescribeVolumesInput{},
			func(page *ec2.DescribeVolumesOutput, lastPage bool) bool {
				for _, volume := range page.Volumes {
					if volume != nil && *volume.State == "available" {
						unusedByAZ[*volume.AvailabilityZone] = unusedByAZ[*volume.AvailabilityZone] + 1
					}
				}
				return lastPage
			})
		if err != nil {
			return "", "", fmt.Sprintf("Unable to list volumes: %s", err.Error())
		}
	}
	return formatResult(unusedByAZ)
}

// processUnusedEBS is the handler function for the unused_ebs plugin
// it takes a core.PluginParams struct and returns a core.PluginResult struct
func processUnusedEBS(params core.PluginParams) core.PluginResult {
	result, details, err := getUnusedEBsRecommendation(params)
	return core.PluginResult{
		Result:  result,
		Details: details,
		Error:   err,
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

// A command that consumes the output of a `comeback verify` run (--fast mode
// is okay), assuming that the roots were all backup jobs of interest. Any
// score that is in the bucket but not represented in the verify output is
// cloned to a garbage/ prefix in the bucket, and deleted from the blobs/
// prefix.

package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"sync/atomic"
	"time"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/wiring"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

var cmdGC = &Command{
	Name: "gc",
}

var fInput = cmdGC.Flags.String(
	"input",
	"",
	"Path to a file containing the output of a verify run.")

func init() {
	cmdGC.Run = runGC // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Parse the verify output, returning a list of all scores encountered.
func parseGCInput(
	ctx context.Context,
	r io.Reader) (scores []blob.Score, err error) {
	b := syncutil.NewBundle(ctx)

	// Parse the input, writing records into a channel.
	verifyRecords := make(chan verifyRecord, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(verifyRecords)
		err = parseVerifyOutput(ctx, r, verifyRecords)
		if err != nil {
			err = fmt.Errorf("parseVerifyOutput: %v", err)
			return
		}

		return
	})

	// Parse and save the scores from the records.
	b.Add(func(ctx context.Context) (err error) {
		for r := range verifyRecords {
			var recordScores []blob.Score
			recordScores, err = r.Scores()
			if err != nil {
				err = fmt.Errorf("Scores: %v", err)
				return
			}

			scores = append(scores, recordScores...)
		}

		return
	})

	err = b.Join()
	return
}

// Filter out scores that are in the list of non-garbage accessible scores,
// passing on only garbage.
func filterToGarbage(
	ctx context.Context,
	accessible []blob.Score,
	allScores <-chan blob.Score,
	garbageScores chan<- blob.Score) (err error) {
	// Create a map indexing the accessible scores.
	accessibleMap := make(map[blob.Score]struct{})
	for _, score := range accessible {
		accessibleMap[score] = struct{}{}
	}

	// Process each score.
	for score := range allScores {
		// Is this score accessible?
		if _, ok := accessibleMap[score]; ok {
			continue
		}

		// Send it down the garbage chute.
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return

		case garbageScores <- score:
		}
	}

	return
}

// Clone garbage blobs into a new location. Pass on the names of the source
// objects that were cloned.
func cloneGarbage(
	ctx context.Context,
	bucket gcs.Bucket,
	garbageScores <-chan blob.Score,
	garbageObjects chan<- string) (err error) {
	b := syncutil.NewBundle(ctx)

	const parallelism = 128
	for i := 0; i < parallelism; i++ {
		b.Add(func(ctx context.Context) (err error) {
			// Process each score.
			for score := range garbageScores {
				srcName := wiring.BlobObjectNamePrefix + score.Hex()

				// Clone the object.
				req := &gcs.CopyObjectRequest{
					SrcName: srcName,
					DstName: fmt.Sprintf("garbage/%s", score.Hex()),
				}

				_, err = bucket.CopyObject(ctx, req)
				if err != nil {
					err = fmt.Errorf("CopyObject: %v", err)
					return
				}

				// Write out the name of the object to be deleted.
				select {
				case <-ctx.Done():
					err = ctx.Err()

				case garbageObjects <- srcName:
				}
			}

			return
		})
	}

	err = b.Join()
	return
}

// Delete all objects whose name come in on the supplied channel.
func deleteObjects(
	ctx context.Context,
	bucket gcs.Bucket,
	names <-chan string) (err error) {
	b := syncutil.NewBundle(ctx)

	const parallelism = 128
	for i := 0; i < parallelism; i++ {
		b.Add(func(ctx context.Context) (err error) {
			for name := range names {
				err = bucket.DeleteObject(ctx, name)
				if err != nil {
					err = fmt.Errorf("DeleteObject(%q): %v", name, err)
					return
				}
			}

			return
		})
	}

	err = b.Join()
	return
}

////////////////////////////////////////////////////////////////////////
// GC
////////////////////////////////////////////////////////////////////////

func runGC(args []string) {
	// Allow parallelism.
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Die on error.
	var err error
	defer func() {
		if err != nil {
			log.Fatalln(err)
		}
	}()

	// Grab dependencies.
	bucket := getBucket()

	// Open the input file.
	if *fInput == "" {
		err = fmt.Errorf("You must set --input.")
		return
	}

	inputFile, err := os.Open(*fInput)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	// Parse it.
	accessibleScores, err := parseGCInput(context.Background(), inputFile)
	inputFile.Close()

	if err != nil {
		err = fmt.Errorf("parseGCInput: %v", err)
		return
	}

	b := syncutil.NewBundle(context.Background())

	// List all extant scores into a channel.
	allScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScores)
		err = blob.ListScores(ctx, bucket, wiring.BlobObjectNamePrefix, allScores)
		if err != nil {
			err = fmt.Errorf("ListScores: %v", err)
			return
		}

		return
	})

	// Count the total number of scores, periodically printing status updates.
	var allScoresCount uint64
	var garbageScoresCount uint64

	allScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScoresAfterCounting)
		ticker := time.Tick(2 * time.Second)

		for score := range allScores {
			allScoresCount++

			// Print a status update?
			select {
			case <-ticker:
				g := atomic.LoadUint64(&garbageScoresCount)
				log.Printf("%d scores seen; %d garbage so far.", allScoresCount, g)

			default:
			}

			// Pass on the score.
			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case allScoresAfterCounting <- score:
			}
		}

		return
	})

	// Filter to garbage scores.
	garbageScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScores)
		err = filterToGarbage(
			ctx,
			accessibleScores,
			allScoresAfterCounting,
			garbageScores)

		if err != nil {
			err = fmt.Errorf("filterToGarbage: %v", err)
			return
		}

		return
	})

	// Count the number of garbage scores.
	garbageScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScoresAfterCounting)
		for score := range garbageScores {
			atomic.AddUint64(&garbageScoresCount, 1)

			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case garbageScoresAfterCounting <- score:
			}
		}

		return
	})

	// Clone garbage blobs into a backup location.
	toDelete := make(chan string, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(toDelete)
		err = cloneGarbage(
			ctx,
			bucket,
			garbageScoresAfterCounting,
			toDelete)
		if err != nil {
			err = fmt.Errorf("cloneGarbage: %v", err)
			return
		}

		return
	})

	// Delete the original objects.
	b.Add(func(ctx context.Context) (err error) {
		err = deleteObjects(ctx, bucket, toDelete)
		if err != nil {
			err = fmt.Errorf("deleteObjects: %v", err)
			return
		}

		return
	})

	err = b.Join()
	if err != nil {
		return
	}

	// Print a summary.
	log.Printf(
		"Deleted %d objects out of %d total.",
		garbageScoresCount,
		allScoresCount)
}
<commit_msg>parseGCInput<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

// A command that consumes the output of a `comeback verify` run (--fast mode
// is okay), assuming that the roots were all backup jobs of interest. Any
// score that is in the bucket but not represented in the verify output is
// cloned to a garbage/ prefix in the bucket, and deleted from the blobs/
// prefix.

package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"sync/atomic"
	"time"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/wiring"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

var cmdGC = &Command{
	Name: "gc",
}

var fInput = cmdGC.Flags.String(
	"input",
	"",
	"Path to a file containing the output of a verify run.")

func init() {
	cmdGC.Run = runGC // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Parse the verify output, returning a list of all scores encountered.
func parseGCInput(
	ctx context.Context,
	r io.Reader) (scores []blob.Score, err error) {
	b := syncutil.NewBundle(ctx)

	// Parse the input, writing records into a channel.
	verifyRecords := make(chan verifyRecord, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(verifyRecords)
		err = parseVerifyOutput(ctx, r, verifyRecords)
		if err != nil {
			err = fmt.Errorf("parseVerifyOutput: %v", err)
			return
		}

		return
	})

	// Save the scores from the records.
	b.Add(func(ctx context.Context) (err error) {
		for r := range verifyRecords {
			scores = append(scores, r.node.Score)
			for _, a := range r.adjacent {
				scores = append(scores, a.Score)
			}
		}

		return
	})

	err = b.Join()
	return
}

// Filter out scores that are in the list of non-garbage accessible scores,
// passing on only garbage.
func filterToGarbage(
	ctx context.Context,
	accessible []blob.Score,
	allScores <-chan blob.Score,
	garbageScores chan<- blob.Score) (err error) {
	// Create a map indexing the accessible scores.
	accessibleMap := make(map[blob.Score]struct{})
	for _, score := range accessible {
		accessibleMap[score] = struct{}{}
	}

	// Process each score.
	for score := range allScores {
		// Is this score accessible?
		if _, ok := accessibleMap[score]; ok {
			continue
		}

		// Send it down the garbage chute.
		select {
		case <-ctx.Done():
			err = ctx.Err()
			return

		case garbageScores <- score:
		}
	}

	return
}

// Clone garbage blobs into a new location. Pass on the names of the source
// objects that were cloned.
func cloneGarbage(
	ctx context.Context,
	bucket gcs.Bucket,
	garbageScores <-chan blob.Score,
	garbageObjects chan<- string) (err error) {
	b := syncutil.NewBundle(ctx)

	const parallelism = 128
	for i := 0; i < parallelism; i++ {
		b.Add(func(ctx context.Context) (err error) {
			// Process each score.
			for score := range garbageScores {
				srcName := wiring.BlobObjectNamePrefix + score.Hex()

				// Clone the object.
				req := &gcs.CopyObjectRequest{
					SrcName: srcName,
					DstName: fmt.Sprintf("garbage/%s", score.Hex()),
				}

				_, err = bucket.CopyObject(ctx, req)
				if err != nil {
					err = fmt.Errorf("CopyObject: %v", err)
					return
				}

				// Write out the name of the object to be deleted.
				select {
				case <-ctx.Done():
					err = ctx.Err()

				case garbageObjects <- srcName:
				}
			}

			return
		})
	}

	err = b.Join()
	return
}

// Delete all objects whose name come in on the supplied channel.
func deleteObjects(
	ctx context.Context,
	bucket gcs.Bucket,
	names <-chan string) (err error) {
	b := syncutil.NewBundle(ctx)

	const parallelism = 128
	for i := 0; i < parallelism; i++ {
		b.Add(func(ctx context.Context) (err error) {
			for name := range names {
				err = bucket.DeleteObject(ctx, name)
				if err != nil {
					err = fmt.Errorf("DeleteObject(%q): %v", name, err)
					return
				}
			}

			return
		})
	}

	err = b.Join()
	return
}

////////////////////////////////////////////////////////////////////////
// GC
////////////////////////////////////////////////////////////////////////

func runGC(args []string) {
	// Allow parallelism.
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Die on error.
	var err error
	defer func() {
		if err != nil {
			log.Fatalln(err)
		}
	}()

	// Grab dependencies.
	bucket := getBucket()

	// Open the input file.
	if *fInput == "" {
		err = fmt.Errorf("You must set --input.")
		return
	}

	inputFile, err := os.Open(*fInput)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	// Parse it.
	accessibleScores, err := parseGCInput(context.Background(), inputFile)
	inputFile.Close()

	if err != nil {
		err = fmt.Errorf("parseGCInput: %v", err)
		return
	}

	b := syncutil.NewBundle(context.Background())

	// List all extant scores into a channel.
	allScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScores)
		err = blob.ListScores(ctx, bucket, wiring.BlobObjectNamePrefix, allScores)
		if err != nil {
			err = fmt.Errorf("ListScores: %v", err)
			return
		}

		return
	})

	// Count the total number of scores, periodically printing status updates.
	var allScoresCount uint64
	var garbageScoresCount uint64

	allScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScoresAfterCounting)
		ticker := time.Tick(2 * time.Second)

		for score := range allScores {
			allScoresCount++

			// Print a status update?
			select {
			case <-ticker:
				g := atomic.LoadUint64(&garbageScoresCount)
				log.Printf("%d scores seen; %d garbage so far.", allScoresCount, g)

			default:
			}

			// Pass on the score.
			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case allScoresAfterCounting <- score:
			}
		}

		return
	})

	// Filter to garbage scores.
	garbageScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScores)
		err = filterToGarbage(
			ctx,
			accessibleScores,
			allScoresAfterCounting,
			garbageScores)

		if err != nil {
			err = fmt.Errorf("filterToGarbage: %v", err)
			return
		}

		return
	})

	// Count the number of garbage scores.
	garbageScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScoresAfterCounting)
		for score := range garbageScores {
			atomic.AddUint64(&garbageScoresCount, 1)

			select {
			case <-ctx.Done():
				err = ctx.Err()
				return

			case garbageScoresAfterCounting <- score:
			}
		}

		return
	})

	// Clone garbage blobs into a backup location.
	toDelete := make(chan string, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(toDelete)
		err = cloneGarbage(
			ctx,
			bucket,
			garbageScoresAfterCounting,
			toDelete)
		if err != nil {
			err = fmt.Errorf("cloneGarbage: %v", err)
			return
		}

		return
	})

	// Delete the original objects.
	b.Add(func(ctx context.Context) (err error) {
		err = deleteObjects(ctx, bucket, toDelete)
		if err != nil {
			err = fmt.Errorf("deleteObjects: %v", err)
			return
		}

		return
	})

	err = b.Join()
	if err != nil {
		return
	}

	// Print a summary.
	log.Printf(
		"Deleted %d objects out of %d total.",
		garbageScoresCount,
		allScoresCount)
}
<|endoftext|>


<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

// A command that consumes the output of a `comeback verify` run (--fast mode
// is okay), assuming that the roots were all backup jobs of interest. Any
// score that is in the bucket but not represented in the verify output is
// cloned to a garbage/ prefix in the bucket, and deleted from the blobs/
// prefix.

package main

import (
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"runtime"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/wiring"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

var cmdGC = &Command{
	Name: "gc",
}

var fInput = cmdGC.Flags.String(
	"input",
	"",
	"Path to a file containing the output of a verify run.")

func init() {
	cmdGC.Run = runGC // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Parse the verify output, returning a list of all scores encountered.
func parseInput(
	r io.Reader) (scores []blob.Score, err error) {
	err = errors.New("TODO: parseInput")
	return
}

// Filter out scores that are in the list of non-garbage accessible scores,
// passing on only garbage.
func filterToGarbage(
	ctx context.Context,
	accessible []blob.Score,
	allScores <-chan blob.Score,
	garbageScores chan<- blob.Score) (err error) {
	err = errors.New("TODO: filterToGarbage")
	return
}

// Count the scores that pass through.
func countScores(
	ctx context.Context,
	in <-chan blob.Score,
	out chan<- blob.Score) (count uint64, err error) {
	err = errors.New("TODO: countScores")
	return
}

// Clone garbage blobs into a new location. Pass on the names of the source
// objects that were cloned.
func cloneGarbage(
	ctx context.Context,
	bucket gcs.Bucket,
	garbageScores <-chan blob.Score,
	garbageObjects chan<- string) (err error) {
	err = errors.New("TODO: cloneGarbage")
	return
}

// Delete all objects whose name come in on the supplied channel.
func deleteObjects(
	ctx context.Context,
	bucket gcs.Bucket,
	names <-chan string) (err error) {
	err = errors.New("TODO: deleteObjects")
	return
}

////////////////////////////////////////////////////////////////////////
// GC
////////////////////////////////////////////////////////////////////////

func runGC(args []string) {
	// Allow parallelism.
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Die on error.
	var err error
	defer func() {
		if err != nil {
			log.Fatalln(err)
		}
	}()

	// Grab dependencies.
	bucket := getBucket()

	// Open the input file.
	if *fInput == "" {
		err = fmt.Errorf("You must set --input.")
		return
	}

	inputFile, err := os.Open(*fInput)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	// Parse it.
	accessibleScores, err := parseInput(inputFile)
	inputFile.Close()

	if err != nil {
		err = fmt.Errorf("parseInput: %v", err)
		return
	}

	b := syncutil.NewBundle(context.Background())

	// List all extant scores into a channel.
	allScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScores)
		err = blob.ListScores(ctx, bucket, wiring.BlobObjectNamePrefix, allScores)
		if err != nil {
			err = fmt.Errorf("ListScores: %v", err)
			return
		}

		return
	})

	// Count the total number of scores.
	var allScoresCount uint64
	allScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScoresAfterCounting)
		allScoresCount, err = countScores(
			ctx,
			allScores,
			allScoresAfterCounting)

		if err != nil {
			err = fmt.Errorf("countScores: %v", err)
			return
		}

		return
	})

	// Filter to garbage scores.
	garbageScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScores)
		err = filterToGarbage(
			ctx,
			accessibleScores,
			allScoresAfterCounting,
			garbageScores)

		if err != nil {
			err = fmt.Errorf("filterToGarbage: %v", err)
			return
		}

		return
	})

	// Count the number of garbage scores.
	var garbageScoresCount uint64
	garbageScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScoresAfterCounting)
		garbageScoresCount, err = countScores(
			ctx,
			garbageScores,
			garbageScoresAfterCounting)

		if err != nil {
			err = fmt.Errorf("countScores: %v", err)
			return
		}

		return
	})

	// Clone garbage blobs into a backup location.
	toDelete := make(chan string, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(toDelete)
		err = cloneGarbage(
			ctx,
			bucket,
			garbageScoresAfterCounting,
			toDelete)
		if err != nil {
			err = fmt.Errorf("cloneGarbage: %v", err)
			return
		}

		return
	})

	// Delete the original objects.
	b.Add(func(ctx context.Context) (err error) {
		err = deleteObjects(ctx, bucket, toDelete)
		if err != nil {
			err = fmt.Errorf("deleteObjects: %v", err)
			return
		}

		return
	})

	err = b.Join()
	if err != nil {
		return
	}

	// Print a summary.
	log.Printf(
		"Deleted %d objects out of %d total.",
		garbageScoresCount,
		allScoresCount)
}
<commit_msg>parseInput<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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.

// A command that consumes the output of a `comeback verify` run (--fast mode
// is okay), assuming that the roots were all backup jobs of interest. Any
// score that is in the bucket but not represented in the verify output is
// cloned to a garbage/ prefix in the bucket, and deleted from the blobs/
// prefix.

package main

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"runtime"

	"github.com/jacobsa/comeback/blob"
	"github.com/jacobsa/comeback/wiring"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/syncutil"
	"golang.org/x/net/context"
)

var cmdGC = &Command{
	Name: "gc",
}

var fInput = cmdGC.Flags.String(
	"input",
	"",
	"Path to a file containing the output of a verify run.")

func init() {
	cmdGC.Run = runGC // Break flag-related dependency loop.
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Parse the supplied input line, returning a list of all scores mentioned.
func parseInputLine(
	line []byte) (scores []blob.Score, err error) {
	err = errors.New("TODO: parseInputLine")
	return
}

// Parse the verify output, returning a list of all scores encountered.
func parseInput(
	r io.Reader) (scores []blob.Score, err error) {
	scanner := bufio.NewScanner(r)
	defer func() {
		scanErr := scanner.Err()
		if err == nil && scanErr != nil {
			err = fmt.Errorf("Scanner: %v", scanErr)
		}
	}()

	// Scan each line.
	for scanner.Scan() {
		var lineScores []blob.Score
		lineScores, err = parseInputLine(scanner.Bytes())
		if err != nil {
			err = fmt.Errorf("parseInputLine(%q): %v", scanner.Text(), err)
			return
		}

		scores = append(scores, lineScores...)
	}

	return
}

// Filter out scores that are in the list of non-garbage accessible scores,
// passing on only garbage.
func filterToGarbage(
	ctx context.Context,
	accessible []blob.Score,
	allScores <-chan blob.Score,
	garbageScores chan<- blob.Score) (err error) {
	err = errors.New("TODO: filterToGarbage")
	return
}

// Count the scores that pass through.
func countScores(
	ctx context.Context,
	in <-chan blob.Score,
	out chan<- blob.Score) (count uint64, err error) {
	err = errors.New("TODO: countScores")
	return
}

// Clone garbage blobs into a new location. Pass on the names of the source
// objects that were cloned.
func cloneGarbage(
	ctx context.Context,
	bucket gcs.Bucket,
	garbageScores <-chan blob.Score,
	garbageObjects chan<- string) (err error) {
	err = errors.New("TODO: cloneGarbage")
	return
}

// Delete all objects whose name come in on the supplied channel.
func deleteObjects(
	ctx context.Context,
	bucket gcs.Bucket,
	names <-chan string) (err error) {
	err = errors.New("TODO: deleteObjects")
	return
}

////////////////////////////////////////////////////////////////////////
// GC
////////////////////////////////////////////////////////////////////////

func runGC(args []string) {
	// Allow parallelism.
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Die on error.
	var err error
	defer func() {
		if err != nil {
			log.Fatalln(err)
		}
	}()

	// Grab dependencies.
	bucket := getBucket()

	// Open the input file.
	if *fInput == "" {
		err = fmt.Errorf("You must set --input.")
		return
	}

	inputFile, err := os.Open(*fInput)
	if err != nil {
		err = fmt.Errorf("Open: %v", err)
		return
	}

	// Parse it.
	accessibleScores, err := parseInput(inputFile)
	inputFile.Close()

	if err != nil {
		err = fmt.Errorf("parseInput: %v", err)
		return
	}

	b := syncutil.NewBundle(context.Background())

	// List all extant scores into a channel.
	allScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScores)
		err = blob.ListScores(ctx, bucket, wiring.BlobObjectNamePrefix, allScores)
		if err != nil {
			err = fmt.Errorf("ListScores: %v", err)
			return
		}

		return
	})

	// Count the total number of scores.
	var allScoresCount uint64
	allScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(allScoresAfterCounting)
		allScoresCount, err = countScores(
			ctx,
			allScores,
			allScoresAfterCounting)

		if err != nil {
			err = fmt.Errorf("countScores: %v", err)
			return
		}

		return
	})

	// Filter to garbage scores.
	garbageScores := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScores)
		err = filterToGarbage(
			ctx,
			accessibleScores,
			allScoresAfterCounting,
			garbageScores)

		if err != nil {
			err = fmt.Errorf("filterToGarbage: %v", err)
			return
		}

		return
	})

	// Count the number of garbage scores.
	var garbageScoresCount uint64
	garbageScoresAfterCounting := make(chan blob.Score, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(garbageScoresAfterCounting)
		garbageScoresCount, err = countScores(
			ctx,
			garbageScores,
			garbageScoresAfterCounting)

		if err != nil {
			err = fmt.Errorf("countScores: %v", err)
			return
		}

		return
	})

	// Clone garbage blobs into a backup location.
	toDelete := make(chan string, 100)
	b.Add(func(ctx context.Context) (err error) {
		defer close(toDelete)
		err = cloneGarbage(
			ctx,
			bucket,
			garbageScoresAfterCounting,
			toDelete)
		if err != nil {
			err = fmt.Errorf("cloneGarbage: %v", err)
			return
		}

		return
	})

	// Delete the original objects.
	b.Add(func(ctx context.Context) (err error) {
		err = deleteObjects(ctx, bucket, toDelete)
		if err != nil {
			err = fmt.Errorf("deleteObjects: %v", err)
			return
		}

		return
	})

	err = b.Join()
	if err != nil {
		return
	}

	// Print a summary.
	log.Printf(
		"Deleted %d objects out of %d total.",
		garbageScoresCount,
		allScoresCount)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	_ "github.com/balzaczyy/golucene/core/codec/lucene42"
	"github.com/balzaczyy/golucene/core/index"
	"github.com/balzaczyy/golucene/core/search"
	"github.com/balzaczyy/golucene/core/store"
	"log"
)

func main() {
	log.Print("Oepning FSDirectory...")
	path := "core/search/testdata/win8/belfrysample"
	d, err := store.OpenFSDirectory(path)
	if err != nil {
		panic(err)
	}
	log.Print("Opening DirectoryReader...")
	r, err := index.OpenDirectoryReader(d)
	if err != nil {
		panic(err)
	}
	if r == nil {
		panic("DirectoryReader cannot be opened.")
	}
	if len(r.Leaves()) < 1 {
		panic("Should have one leaf.")
	}
	for _, ctx := range r.Leaves() {
		if ctx.Parent() != r.Context() {
			fmt.Println("DEBUG", ctx.Parent(), r.Context())
			panic("leaves not point to parent!")
		}
	}
	log.Print("Initializing IndexSearcher...")
	ss := search.NewIndexSearcher(r)
	log.Print("Searching...")
	docs, err := ss.SearchTop(search.NewTermQuery(index.NewTerm("content", "bat")), 10)
	if err != nil {
		panic(err)
	}
	log.Println("Hits:", docs.TotalHits)
	doc, err := r.Document(docs.ScoreDocs[0].Doc)
	if err != nil {
		panic(err)
	}
	log.Println("Hit 1's title: ", doc.Get("title"))
}
<commit_msg>use 4.10 codec in gl.go<commit_after>package main

import (
	"fmt"
	_ "github.com/balzaczyy/golucene/core/codec/lucene410"
	"github.com/balzaczyy/golucene/core/index"
	"github.com/balzaczyy/golucene/core/search"
	"github.com/balzaczyy/golucene/core/store"
	"log"
)

func main() {
	log.Print("Oepning FSDirectory...")
	path := "core/search/testdata/win8/belfrysample"
	d, err := store.OpenFSDirectory(path)
	if err != nil {
		panic(err)
	}
	log.Print("Opening DirectoryReader...")
	r, err := index.OpenDirectoryReader(d)
	if err != nil {
		panic(err)
	}
	if r == nil {
		panic("DirectoryReader cannot be opened.")
	}
	if len(r.Leaves()) < 1 {
		panic("Should have one leaf.")
	}
	for _, ctx := range r.Leaves() {
		if ctx.Parent() != r.Context() {
			fmt.Println("DEBUG", ctx.Parent(), r.Context())
			panic("leaves not point to parent!")
		}
	}
	log.Print("Initializing IndexSearcher...")
	ss := search.NewIndexSearcher(r)
	log.Print("Searching...")
	docs, err := ss.SearchTop(search.NewTermQuery(index.NewTerm("content", "bat")), 10)
	if err != nil {
		panic(err)
	}
	log.Println("Hits:", docs.TotalHits)
	doc, err := r.Document(docs.ScoreDocs[0].Doc)
	if err != nil {
		panic(err)
	}
	log.Println("Hit 1's title: ", doc.Get("title"))
}
<|endoftext|>


<commit_before>package ufop

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/qiniu/api/auth/digest"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

var (
	ufopPrefix  string
	jobHandlers map[string]UfopJobHandler
	unzipper    *UnZipper
)

type UfopJobHandler interface {
	Do(ufopReq UfopRequest) (interface{}, error)
}

type UfopServer struct {
	cfg *UfopConfig
}

func NewServer(cfg *UfopConfig) *UfopServer {
	serv := UfopServer{}
	serv.cfg = cfg
	return &serv
}

func (this *UfopServer) registerJobHandlers() {
	mac := digest.Mac{
		AccessKey: this.cfg.AccessKey,
		SecretKey: []byte(this.cfg.SecretKey),
	}
	jobHandlers = make(map[string]UfopJobHandler, 0)
	ufopPrefix = this.cfg.UfopPrefix
	//unzipper
	unzipper = &UnZipper{
		mac: &mac,
	}

	jobHandlers[ufopPrefix+"unzip"] = unzipper
}

func (this *UfopServer) Listen() {
	//register
	this.registerJobHandlers()

	//define handler
	http.HandleFunc("/uop", serveUfop)

	//bind and listen
	endPoint := fmt.Sprintf("%s:%d", this.cfg.ListenHost, this.cfg.ListenPort)
	ufopServer := &http.Server{
		Addr:           endPoint,
		ReadTimeout:    time.Duration(this.cfg.ReadTimeout) * time.Second,
		WriteTimeout:   time.Duration(this.cfg.WriteTimeout) * time.Second,
		MaxHeaderBytes: this.cfg.MaxHeaderBytes,
	}

	listenErr := ufopServer.ListenAndServe()
	if listenErr != nil {
		log.Println(listenErr)
	}
}

func serveUfop(w http.ResponseWriter, req *http.Request) {
	//check method
	if req.Method != "POST" {
		writeJsonError(w, 405, "method not allowed")
		return
	}

	defer req.Body.Close()
	var err error
	var ufopReq UfopRequest
	var ufopResult interface{}

	ufopReqData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		writeJsonError(w, 500, "read ufop request body error")
		return
	}

	err = json.Unmarshal(ufopReqData, &ufopReq)
	if err != nil {
		writeJsonError(w, 500, "parse ufop request body error")
		return
	}

	ufopResult, err = handleJob(ufopReq)
	if err != nil {
		writeJsonError(w, 500, err.Error())
	} else {
		writeJsonResult(w, 200, ufopResult)
	}
}

func handleJob(ufopReq UfopRequest) (interface{}, error) {
	var ufopResult interface{}
	var err error
	cmd := ufopReq.Cmd

	items := strings.SplitN(cmd, "/", 2)
	fop := items[0]
	if jobHandler, ok := jobHandlers[fop]; ok {
		ufopReq.Cmd = strings.TrimPrefix(ufopReq.Cmd, ufopPrefix)
		ufopResult, err = jobHandler.Do(ufopReq)
	} else {
		err = errors.New("no fop available for the request")
	}
	return ufopResult, err
}

func writeJsonError(w http.ResponseWriter, statusCode int, message string) {
	w.WriteHeader(statusCode)
	w.Header().Add("Content-Type", "application/json")
	io.WriteString(w, fmt.Sprintf(`{"error": "%s"}`, message))
}

func writeJsonResult(w http.ResponseWriter, statusCode int, result interface{}) {
	w.WriteHeader(statusCode)
	w.Header().Add("Content-Type", "application/json")
	data, err := json.Marshal(result)
	if err != nil {
		log.Println("Encode ufop result error,", err)
		writeJsonResult(w, 500, "Encode ufop result error")
	} else {
		io.WriteString(w, string(data))
	}
}
<commit_msg>Add error log.<commit_after>package ufop

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/qiniu/api/auth/digest"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
	"time"
)

var (
	ufopPrefix  string
	jobHandlers map[string]UfopJobHandler
	unzipper    *UnZipper
)

type UfopJobHandler interface {
	Do(ufopReq UfopRequest) (interface{}, error)
}

type UfopServer struct {
	cfg *UfopConfig
}

func NewServer(cfg *UfopConfig) *UfopServer {
	serv := UfopServer{}
	serv.cfg = cfg
	return &serv
}

func (this *UfopServer) registerJobHandlers() {
	mac := digest.Mac{
		AccessKey: this.cfg.AccessKey,
		SecretKey: []byte(this.cfg.SecretKey),
	}
	jobHandlers = make(map[string]UfopJobHandler, 0)
	ufopPrefix = this.cfg.UfopPrefix
	//unzipper
	unzipper = &UnZipper{
		mac: &mac,
	}

	jobHandlers[ufopPrefix+"unzip"] = unzipper
}

func (this *UfopServer) Listen() {
	//register
	this.registerJobHandlers()

	//define handler
	http.HandleFunc("/uop", serveUfop)

	//bind and listen
	endPoint := fmt.Sprintf("%s:%d", this.cfg.ListenHost, this.cfg.ListenPort)
	ufopServer := &http.Server{
		Addr:           endPoint,
		ReadTimeout:    time.Duration(this.cfg.ReadTimeout) * time.Second,
		WriteTimeout:   time.Duration(this.cfg.WriteTimeout) * time.Second,
		MaxHeaderBytes: this.cfg.MaxHeaderBytes,
	}

	listenErr := ufopServer.ListenAndServe()
	if listenErr != nil {
		log.Println(listenErr)
	}
}

func serveUfop(w http.ResponseWriter, req *http.Request) {
	//check method
	if req.Method != "POST" {
		writeJsonError(w, 405, "method not allowed")
		return
	}

	defer req.Body.Close()
	var err error
	var ufopReq UfopRequest
	var ufopResult interface{}

	ufopReqData, err := ioutil.ReadAll(req.Body)
	if err != nil {
		writeJsonError(w, 500, "read ufop request body error")
		return
	}

	err = json.Unmarshal(ufopReqData, &ufopReq)
	if err != nil {
		writeJsonError(w, 500, "parse ufop request body error")
		return
	}

	ufopResult, err = handleJob(ufopReq)
	if err != nil {
		writeJsonError(w, 500, err.Error())
	} else {
		writeJsonResult(w, 200, ufopResult)
	}
}

func handleJob(ufopReq UfopRequest) (interface{}, error) {
	var ufopResult interface{}
	var err error
	cmd := ufopReq.Cmd

	items := strings.SplitN(cmd, "/", 2)
	fop := items[0]
	if jobHandler, ok := jobHandlers[fop]; ok {
		ufopReq.Cmd = strings.TrimPrefix(ufopReq.Cmd, ufopPrefix)
		ufopResult, err = jobHandler.Do(ufopReq)
	} else {
		err = errors.New("no fop available for the request")
	}
	return ufopResult, err
}

func writeJsonError(w http.ResponseWriter, statusCode int, message string) {
	log.Println(message)
	w.WriteHeader(statusCode)
	w.Header().Add("Content-Type", "application/json")
	io.WriteString(w, fmt.Sprintf(`{"error": "%s"}`, message))
}

func writeJsonResult(w http.ResponseWriter, statusCode int, result interface{}) {
	w.WriteHeader(statusCode)
	w.Header().Add("Content-Type", "application/json")
	data, err := json.Marshal(result)
	if err != nil {
		log.Println("Encode ufop result error,", err)
		writeJsonResult(w, 500, "Encode ufop result error")
	} else {
		io.WriteString(w, string(data))
	}
}
<|endoftext|>