<commit_before>package router

import (
	"fmt"
	"runtime"
	"strings"
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/distribution/health"

	"encoding/json"

	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/server/kvstore"
	"github.com/smancke/guble/server/store"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

// Router interface provides a mechanism for PubSub messaging
type Router interface {
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)
	KVStore() (kvstore.KVStore, error)
	Cluster() *cluster.Cluster
	Fetch(*store.FetchRequest) error

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
	GetSubscribersForTopic(topic string) ([]byte, error)
	Done() <-chan bool
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route *Route
	doneC chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       kvstore.KVStore
	cluster       *cluster.Cluster

	sync.RWMutex
}

// New returns a pointer to Router
func New(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore kvstore.KVStore, cluster *cluster.Cluster) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
		cluster:       cluster,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	logger.Info("Starting router")
	resetRouterMetrics()

	router.wg.Add(1)
	router.setStopping(false)

	go func() {
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneC <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneC <- true
				case <-router.Done():
					router.setStopping(true)
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Info("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithError(ErrServiceNotProvided).Error("Some mandatory services are not provided")
		return ErrServiceNotProvided
	}
	if checkable, ok := router.messageStore.(health.Checker); ok {
		err := checkable.Check()
		if err != nil {
			logger.WithField("error", err.Error()).Error("MessageStore check failed")
			return err
		}
	}
	if checkable, ok := router.kvStore.(health.Checker); ok {
		err := checkable.Check()
		if err != nil {
			logger.WithField("error", err.Error()).Error("KVStore check failed")
			return err
		}
	}
	return nil
}

// HandleMessage stores the message in the MessageStore(and gets a new ID for it if the message was created locally)
// and then passes it to the internal channel, and asynchronously to the cluster (if available).
func (router *router) HandleMessage(message *protocol.Message) error {
	logger.WithFields(log.Fields{
		"userID": message.UserID,
		"path":   message.Path}).Debug("HandleMessage")

	mTotalMessagesIncoming.Add(1)
	if err := router.isStopping(); err != nil {
		logger.WithField("error", err.Error()).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{UserID: message.UserID, AccessType: auth.WRITE, Path: message.Path}
	}

	var nodeID uint8
	if router.cluster != nil {
		nodeID = router.cluster.Config.ID
	}

	mTotalMessagesIncomingBytes.Add(int64(len(message.Bytes())))
	size, err := router.messageStore.StoreMessage(message, nodeID)
	if err != nil {
		logger.WithField("error", err.Error()).Error("Error storing message")
		mTotalMessageStoreErrors.Add(1)
		return err
	}
	mTotalMessagesStoredBytes.Add(int64(size))

	router.handleOverloadedChannel()

	router.handleC <- message

	if router.cluster != nil && message.NodeID == router.cluster.Config.ID {
		go router.cluster.BroadcastMessage(message)
	}

	return nil
}

func (router *router) GetSubscribersForTopic(topicPath string) ([]byte, error) {
	subscribers := make([]RouteParams, 0)
	routes, present := router.routes[protocol.Path(topicPath)]
	if present {
		for index, currRoute := range routes {
			logger.WithFields(log.Fields{
				"index":       index,
				"routeParams": currRoute.RouteParams,
			}).Debug("Added route to slice")
			subscribers = append(subscribers, currRoute.RouteParams)
		}
	}
	return json.Marshal(subscribers)
}

// Subscribe adds a route to the subscribers. If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"route":         r,
	}).Debug("Subscribe")

	if err := router.isStopping(); err != nil {
		return nil, err
	}

	userID := r.Get("user_id")
	routePath := r.Path

	accessAllowed := router.accessManager.IsAllowed(auth.READ, userID, routePath)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: userID, AccessType: auth.READ, Path: routePath}
	}
	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}

	router.subscribeC <- req
	<-req.doneC
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"route":         r,
	}).Debug("Unsubscribe")

	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneC
}

func (router *router) subscribe(r *Route) {
	logger.WithField("route", r).Debug("Internal subscribe")
	mTotalSubscriptionAttempts.Add(1)

	routePath := r.Path
	slice, present := router.routes[routePath]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[routePath] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[routePath] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {
	logger.WithField("route", r).Debug("Internal unsubscribe")
	mTotalUnsubscriptionAttempts.Add(1)

	routePath := r.Path
	slice, present := router.routes[routePath]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[routePath], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[routePath]) == 0 {
		delete(router.routes, routePath)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) setStopping(v bool) {
	router.Lock()
	defer router.Unlock()

	router.stopping = v
}

func (router *router) Done() <-chan bool {
	return router.stopC
}

func (router *router) isStopping() error {
	router.RLock()
	defer router.RUnlock()

	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}

	return nil
}

func (router *router) routeMessage(message *protocol.Message) {
	logger.WithField("msgMetadata", message.Metadata()).Debug("Called routeMessage for data")
	mTotalMessagesRouted.Add(1)

	matched := false
	for path, pathRoutes := range router.routes {
		if matchesTopic(message.Path, path) {
			matched = true
			for _, route := range pathRoutes {
				if err := route.Deliver(message); err == ErrInvalidRoute {
					// Unsubscribe invalid routes
					router.unsubscribe(route)
				}
			}
		}
	}

	if !matched {
		logger.WithField("topic", message.Path).Debug("No route matched.")
		mTotalMessagesNotMatchingTopic.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Debug("Called closeRoutes")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			log.WithFields(log.Fields{"module": "router", "route": route.String()}).Debug("Closing route")
			route.Close()
		}
	}
}

func (router *router) handleOverloadedChannel() {
	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("handleC channel is almost full")
		mTotalOverloadedHandleChannel.Add(1)
	}
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// removeIfMatching removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.Equal(route) {
			position = p
			break
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (kvstore.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}

func (router *router) Fetch(req *store.FetchRequest) error {
	if err := router.isStopping(); err != nil {
		return err
	}
	router.messageStore.Fetch(req)
	return nil
}

// Cluster returns the `cluster` provided for the router, or nil if no cluster was set-up
func (router *router) Cluster() *cluster.Cluster {
	return router.cluster
}
<commit_msg>More log fields when handling message<commit_after>package router

import (
	"fmt"
	"runtime"
	"strings"
	"sync"

	log "github.com/Sirupsen/logrus"
	"github.com/docker/distribution/health"

	"encoding/json"

	"github.com/smancke/guble/protocol"
	"github.com/smancke/guble/server/auth"
	"github.com/smancke/guble/server/cluster"
	"github.com/smancke/guble/server/kvstore"
	"github.com/smancke/guble/server/store"
)

const (
	overloadedHandleChannelRatio = 0.9
	handleChannelCapacity        = 500
	subscribeChannelCapacity     = 10
	unsubscribeChannelCapacity   = 10
)

// Router interface provides a mechanism for PubSub messaging
type Router interface {
	AccessManager() (auth.AccessManager, error)
	MessageStore() (store.MessageStore, error)
	KVStore() (kvstore.KVStore, error)
	Cluster() *cluster.Cluster
	Fetch(*store.FetchRequest) error

	Subscribe(r *Route) (*Route, error)
	Unsubscribe(r *Route)
	HandleMessage(message *protocol.Message) error
	GetSubscribersForTopic(topic string) ([]byte, error)
	Done() <-chan bool
}

// Helper struct to pass `Route` to subscription channel and provide a notification channel.
type subRequest struct {
	route *Route
	doneC chan bool
}

type router struct {
	routes       map[protocol.Path][]*Route // mapping the path to the route slice
	handleC      chan *protocol.Message
	subscribeC   chan subRequest
	unsubscribeC chan subRequest
	stopC        chan bool      // Channel that signals stop of the router
	stopping     bool           // Flag: the router is in stopping process and no incoming messages are accepted
	wg           sync.WaitGroup // Add any operation that we need to wait upon here

	accessManager auth.AccessManager
	messageStore  store.MessageStore
	kvStore       kvstore.KVStore
	cluster       *cluster.Cluster

	sync.RWMutex
}

// New returns a pointer to Router
func New(accessManager auth.AccessManager, messageStore store.MessageStore, kvStore kvstore.KVStore, cluster *cluster.Cluster) Router {
	return &router{
		routes: make(map[protocol.Path][]*Route),

		handleC:      make(chan *protocol.Message, handleChannelCapacity),
		subscribeC:   make(chan subRequest, subscribeChannelCapacity),
		unsubscribeC: make(chan subRequest, unsubscribeChannelCapacity),
		stopC:        make(chan bool, 1),

		accessManager: accessManager,
		messageStore:  messageStore,
		kvStore:       kvStore,
		cluster:       cluster,
	}
}

func (router *router) Start() error {
	router.panicIfInternalDependenciesAreNil()
	logger.Info("Starting router")
	resetRouterMetrics()

	router.wg.Add(1)
	router.setStopping(false)

	go func() {
		for {
			if router.stopping && router.channelsAreEmpty() {
				router.closeRoutes()
				router.wg.Done()
				return
			}

			func() {
				defer protocol.PanicLogger()

				select {
				case message := <-router.handleC:
					router.routeMessage(message)
					runtime.Gosched()
				case subscriber := <-router.subscribeC:
					router.subscribe(subscriber.route)
					subscriber.doneC <- true
				case unsubscriber := <-router.unsubscribeC:
					router.unsubscribe(unsubscriber.route)
					unsubscriber.doneC <- true
				case <-router.Done():
					router.setStopping(true)
				}
			}()
		}
	}()

	return nil
}

// Stop stops the router by closing the stop channel, and waiting on the WaitGroup
func (router *router) Stop() error {
	logger.Info("Stopping router")

	router.stopC <- true
	router.wg.Wait()
	return nil
}

func (router *router) Check() error {
	if router.accessManager == nil || router.messageStore == nil || router.kvStore == nil {
		logger.WithError(ErrServiceNotProvided).Error("Some mandatory services are not provided")
		return ErrServiceNotProvided
	}
	if checkable, ok := router.messageStore.(health.Checker); ok {
		err := checkable.Check()
		if err != nil {
			logger.WithField("error", err.Error()).Error("MessageStore check failed")
			return err
		}
	}
	if checkable, ok := router.kvStore.(health.Checker); ok {
		err := checkable.Check()
		if err != nil {
			logger.WithField("error", err.Error()).Error("KVStore check failed")
			return err
		}
	}
	return nil
}

// HandleMessage stores the message in the MessageStore(and gets a new ID for it if the message was created locally)
// and then passes it to the internal channel, and asynchronously to the cluster (if available).
func (router *router) HandleMessage(message *protocol.Message) error {
	logger.WithFields(log.Fields{
		"userID": message.UserID,
		"path":   message.Path}).Debug("HandleMessage")

	mTotalMessagesIncoming.Add(1)
	if err := router.isStopping(); err != nil {
		logger.WithField("error", err.Error()).Error("Router is stopping")
		return err
	}

	if !router.accessManager.IsAllowed(auth.WRITE, message.UserID, message.Path) {
		return &PermissionDeniedError{UserID: message.UserID, AccessType: auth.WRITE, Path: message.Path}
	}

	var nodeID uint8
	if router.cluster != nil {
		nodeID = router.cluster.Config.ID
	}

	mTotalMessagesIncomingBytes.Add(int64(len(message.Bytes())))
	size, err := router.messageStore.StoreMessage(message, nodeID)
	if err != nil {
		logger.WithField("error", err.Error()).Error("Error storing message")
		mTotalMessageStoreErrors.Add(1)
		return err
	}
	mTotalMessagesStoredBytes.Add(int64(size))

	router.handleOverloadedChannel()

	router.handleC <- message

	if router.cluster != nil && message.NodeID == router.cluster.Config.ID {
		go router.cluster.BroadcastMessage(message)
	}

	return nil
}

func (router *router) GetSubscribersForTopic(topicPath string) ([]byte, error) {
	subscribers := make([]RouteParams, 0)
	routes, present := router.routes[protocol.Path(topicPath)]
	if present {
		for index, currRoute := range routes {
			logger.WithFields(log.Fields{
				"index":       index,
				"routeParams": currRoute.RouteParams,
			}).Debug("Added route to slice")
			subscribers = append(subscribers, currRoute.RouteParams)
		}
	}
	return json.Marshal(subscribers)
}

// Subscribe adds a route to the subscribers. If there is already a route with same Application Id and Path, it will be replaced.
func (router *router) Subscribe(r *Route) (*Route, error) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"route":         r,
	}).Debug("Subscribe")

	if err := router.isStopping(); err != nil {
		return nil, err
	}

	userID := r.Get("user_id")
	routePath := r.Path

	accessAllowed := router.accessManager.IsAllowed(auth.READ, userID, routePath)
	if !accessAllowed {
		return r, &PermissionDeniedError{UserID: userID, AccessType: auth.READ, Path: routePath}
	}
	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}

	router.subscribeC <- req
	<-req.doneC
	return r, nil
}

func (router *router) Unsubscribe(r *Route) {
	logger.WithFields(log.Fields{
		"accessManager": router.accessManager,
		"route":         r,
	}).Debug("Unsubscribe")

	req := subRequest{
		route: r,
		doneC: make(chan bool),
	}
	router.unsubscribeC <- req
	<-req.doneC
}

func (router *router) subscribe(r *Route) {
	logger.WithField("route", r).Debug("Internal subscribe")
	mTotalSubscriptionAttempts.Add(1)

	routePath := r.Path
	slice, present := router.routes[routePath]
	var removed bool
	if present {
		// Try to remove, to avoid double subscriptions of the same app
		slice, removed = removeIfMatching(slice, r)
	} else {
		// Path not present yet. Initialize the slice
		slice = make([]*Route, 0, 1)
		router.routes[routePath] = slice
		mCurrentRoutes.Add(1)
	}
	router.routes[routePath] = append(slice, r)
	if removed {
		mTotalDuplicateSubscriptionsAttempts.Add(1)
	} else {
		mTotalSubscriptions.Add(1)
		mCurrentSubscriptions.Add(1)
	}
}

func (router *router) unsubscribe(r *Route) {
	logger.WithField("route", r).Debug("Internal unsubscribe")
	mTotalUnsubscriptionAttempts.Add(1)

	routePath := r.Path
	slice, present := router.routes[routePath]
	if !present {
		mTotalInvalidTopicOnUnsubscriptionAttempts.Add(1)
		return
	}
	var removed bool
	router.routes[routePath], removed = removeIfMatching(slice, r)
	if removed {
		mTotalUnsubscriptions.Add(1)
		mCurrentSubscriptions.Add(-1)
	} else {
		mTotalInvalidUnsubscriptionAttempts.Add(1)
	}
	if len(router.routes[routePath]) == 0 {
		delete(router.routes, routePath)
		mCurrentRoutes.Add(-1)
	}
}

func (router *router) panicIfInternalDependenciesAreNil() {
	if router.accessManager == nil || router.kvStore == nil || router.messageStore == nil {
		panic(fmt.Sprintf("router: the internal dependencies marked with `true` are not set: AccessManager=%v, KVStore=%v, MessageStore=%v",
			router.accessManager == nil, router.kvStore == nil, router.messageStore == nil))
	}
}

func (router *router) channelsAreEmpty() bool {
	return len(router.handleC) == 0 && len(router.subscribeC) == 0 && len(router.unsubscribeC) == 0
}

func (router *router) setStopping(v bool) {
	router.Lock()
	defer router.Unlock()

	router.stopping = v
}

func (router *router) Done() <-chan bool {
	return router.stopC
}

func (router *router) isStopping() error {
	router.RLock()
	defer router.RUnlock()

	if router.stopping {
		return &ModuleStoppingError{"Router"}
	}

	return nil
}

func (router *router) routeMessage(message *protocol.Message) {
	flog := logger.WithFields(log.Fields{
		"topic":    message.Path,
		"metadata": message.Metadata(),
		"filters":  message.Filters,
	})
	flog.Debug("Called routeMessage for data")
	mTotalMessagesRouted.Add(1)

	matched := false
	for path, pathRoutes := range router.routes {
		if matchesTopic(message.Path, path) {
			matched = true
			for _, route := range pathRoutes {
				if err := route.Deliver(message); err == ErrInvalidRoute {
					// Unsubscribe invalid routes
					router.unsubscribe(route)
				}
			}
		}
	}

	if !matched {
		flog.Debug("No route matched.")
		mTotalMessagesNotMatchingTopic.Add(1)
	}
}

func (router *router) closeRoutes() {
	logger.Debug("Called closeRoutes")

	for _, currentRouteList := range router.routes {
		for _, route := range currentRouteList {
			router.unsubscribe(route)
			log.WithFields(log.Fields{"module": "router", "route": route.String()}).Debug("Closing route")
			route.Close()
		}
	}
}

func (router *router) handleOverloadedChannel() {
	if float32(len(router.handleC))/float32(cap(router.handleC)) > overloadedHandleChannelRatio {
		logger.WithFields(log.Fields{
			"currentLength": len(router.handleC),
			"maxCapacity":   cap(router.handleC),
		}).Warn("handleC channel is almost full")
		mTotalOverloadedHandleChannel.Add(1)
	}
}

// matchesTopic checks whether the supplied routePath matches the message topic
func matchesTopic(messagePath, routePath protocol.Path) bool {
	messagePathLen := len(string(messagePath))
	routePathLen := len(string(routePath))
	return strings.HasPrefix(string(messagePath), string(routePath)) &&
		(messagePathLen == routePathLen ||
			(messagePathLen > routePathLen && string(messagePath)[routePathLen] == '/'))
}

// removeIfMatching removes a route from the supplied list, based on same ApplicationID id and same path (if existing)
// returns: the (possibly updated) slide, and a boolean value (true if route was removed, false otherwise)
func removeIfMatching(slice []*Route, route *Route) ([]*Route, bool) {
	position := -1
	for p, r := range slice {
		if r.Equal(route) {
			position = p
			break
		}
	}
	if position == -1 {
		return slice, false
	}
	return append(slice[:position], slice[position+1:]...), true
}

// AccessManager returns the `accessManager` provided for the router
func (router *router) AccessManager() (auth.AccessManager, error) {
	if router.accessManager == nil {
		return nil, ErrServiceNotProvided
	}
	return router.accessManager, nil
}

// MessageStore returns the `messageStore` provided for the router
func (router *router) MessageStore() (store.MessageStore, error) {
	if router.messageStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.messageStore, nil
}

// KVStore returns the `kvStore` provided for the router
func (router *router) KVStore() (kvstore.KVStore, error) {
	if router.kvStore == nil {
		return nil, ErrServiceNotProvided
	}
	return router.kvStore, nil
}

func (router *router) Fetch(req *store.FetchRequest) error {
	if err := router.isStopping(); err != nil {
		return err
	}
	router.messageStore.Fetch(req)
	return nil
}

// Cluster returns the `cluster` provided for the router, or nil if no cluster was set-up
func (router *router) Cluster() *cluster.Cluster {
	return router.cluster
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/aqatl/mal/dialog"
	"github.com/aqatl/mal/nyaa_scraper"
	"github.com/fatih/color"
	"github.com/jroimartin/gocui"
	"github.com/urfave/cli"
	"math"
	"os/exec"
	"strings"
)

func malNyaaCui(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	entry := list.GetByID(cfg.SelectedID)
	if entry == nil {
		return fmt.Errorf("no entry found")
	}
	return startNyaaCui(cfg, entry.Title)
}

func alNyaaCui(ctx *cli.Context) error {
	al, err := loadAniList()
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	entry := al.GetMediaListById(cfg.ALSelectedID)
	if entry == nil {
		return fmt.Errorf("no entry found")
	}

	searchTerm := entry.Title.UserPreferred
	if ctx.Bool("alt") {
		alts := make([]string, 0, 3+len(entry.Synonyms))
		if t := entry.Title.English; t != "" {
			alts = append(alts, t)
		}
		if t := entry.Title.Native; t != "" {
			alts = append(alts, t)
		}
		if t := entry.Title.Romaji; t != "" {
			alts = append(alts, t)
		}
		alts = append(alts, entry.Synonyms...)
		fmt.Printf("Select desired title\n\n")
		if searchTerm = chooseStrFromSlice(alts); searchTerm == "" {
			return fmt.Errorf("no alternative titles")
		}
	} else if ctx.NArg() > 0 {
		searchTerm = strings.Join(ctx.Args(), " ")
	}

	return startNyaaCui(cfg, searchTerm)
}

func startNyaaCui(cfg *Config, searchTerm string) error {
	gui, err := gocui.NewGui(gocui.Output256)
	defer gui.Close()
	if err != nil {
		return fmt.Errorf("gocui error: %v", err)
	}
	nc := &nyaaCui{
		Gui: gui,
		Cfg: cfg,

		SearchTerm: searchTerm,
		Category:   nyaa_scraper.AnimeEnglishTranslated,
		Filter:     nyaa_scraper.NoFilter,
	}
	gui.SetManager(nc)
	nc.setGuiKeyBindings(gui)

	gui.Cursor = false
	gui.Mouse = false
	gui.Highlight = true
	gui.SelFgColor = gocui.ColorGreen

	gui.Update(func(gui *gocui.Gui) error {
		nc.Reload(gui)
		return nil
	})

	if err = gui.MainLoop(); err != nil && err != gocui.ErrQuit {
		return err
	}
	return nil
}

const (
	ncInfoView      = "ncInfoView"
	ncResultsView   = "ncResultsView "
	ncShortcutsView = "ncShortcutsView"
)

type nyaaCui struct {
	Gui *gocui.Gui
	Cfg *Config

	SearchTerm string
	Category   nyaa_scraper.NyaaCategory
	Filter     nyaa_scraper.NyaaFilter

	Results     []nyaa_scraper.NyaaEntry
	MaxResults  int
	MaxPages    int
	LoadedPages int

	ResultsView *gocui.View
}

var red = color.New(color.FgRed).SprintFunc()
var cyan = color.New(color.FgCyan).SprintFunc()
var blue = color.New(color.FgBlue).SprintFunc()
var green = color.New(color.FgGreen).SprintFunc()

func (nc *nyaaCui) Layout(gui *gocui.Gui) error {
	w, h := gui.Size()
	if v, err := gui.SetView(ncInfoView, 0, 0, w-1, 2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Info"
		v.Editable = false

		fmt.Fprintf(v, "[%s]: displaying %d out of %d results",
			nc.SearchTerm, len(nc.Results), nc.MaxResults)
	}

	if v, err := gui.SetView(ncResultsView, 0, 3, w-1, h-4); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Search results"
		v.SelBgColor = gocui.ColorGreen
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		v.Highlight = true
		v.Editable = true
		v.Editor = gocui.EditorFunc(nc.GetEditor())

		gui.SetCurrentView(ncResultsView)
		nc.ResultsView = v

		//TODO Better/clearer results printing
		for _, result := range nc.Results {
			fmt.Fprintln(v,
				result.Title,
				red(result.Size),
				cyan(result.DateAdded.Format("15:04:05 02-01-2006 MST")),
				green(result.Seeders),
				red(result.Leechers),
				blue(result.CompletedDownloads),
			)
		}
	}

	if v, err := gui.SetView(ncShortcutsView, 0, h-3, w-1, h-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Shortcuts"
		v.Editable = false

		c := color.New(color.FgCyan).SprintFunc()
		fmt.Fprintln(v, c("d"), "download", c("l"), "load next page",
			c("c"), "category", c("f"), "filters")
	}

	return nil
}

func (nc *nyaaCui) GetEditor() func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		switch {
		case key == gocui.KeyArrowDown || ch == 'j':
			_, oy := v.Origin()
			_, y := v.Cursor()
			y += oy
			if y < len(nc.Results)-1 {
				v.MoveCursor(0, 1, false)
			}
		case key == gocui.KeyArrowUp || ch == 'k':
			v.MoveCursor(0, -1, false)
		case ch == 'd':
			_, y := v.Cursor()
			_, oy := v.Origin()
			y += oy
			nc.Download(y)
		case ch == 'l':
			nc.LoadNextPage()
		case ch == 'c':
			nc.ChangeCategory()
		case ch == 'f':
			nc.ChangeFilter()
		}
	}
}

func (nc *nyaaCui) Reload(gui *gocui.Gui) {
	var resultPage nyaa_scraper.NyaaResultPage
	var searchErr error
	f := func() {
		resultPage, searchErr = nyaa_scraper.Search(nc.SearchTerm, nc.Category, nc.Filter)
	}
	jobDone, err := dialog.StuffLoader(dialog.FitMessage(gui, "Loading "+nc.SearchTerm), f)
	if err != nil {
		gocuiReturnError(gui, err)
	}
	go func() {
		ok := <-jobDone
		if searchErr != nil {
			dialog.JustShowOkDialog(gui, "Error", searchErr.Error())
			return
		}
		if ok {
			nc.Results = resultPage.Results
			nc.MaxResults = resultPage.DisplayedOutOf
			nc.MaxPages = int(math.Ceil(float64(resultPage.DisplayedOutOf) /
				float64(resultPage.DisplayedTo-resultPage.DisplayedFrom+1)))
			nc.LoadedPages = 1
		}

		gui.Update(func(gui *gocui.Gui) error {
			gui.DeleteView(ncResultsView)
			gui.DeleteView(ncInfoView)
			return nil
		})
	}()
}

func (nc *nyaaCui) Download(yIdx int) {
	if yIdx >= len(nc.Results) {
		return
	}

	link := ""
	if entry := nc.Results[yIdx]; entry.MagnetLink != "" {
		link = entry.MagnetLink
	} else if entry.TorrentLink != "" {
		link = entry.TorrentLink
	} else {
		dialog.JustShowOkDialog(nc.Gui, "Error", "No link found")
		return
	}

	link = "\"" + link + "\""
	cmd := exec.Command(nc.Cfg.TorrentClientPath, nc.Cfg.TorrentClientArgs...)
	cmd.Args = append(cmd.Args, link)
	cmd.Args = cmd.Args[1:] //Why they include app name in the arguments???
	if err := cmd.Start(); err != nil {
		gocuiReturnError(nc.Gui, err)
	}
}

func (nc *nyaaCui) LoadNextPage() {
	if nc.LoadedPages >= nc.MaxPages {
		return
	}
	nc.LoadedPages++
	go func() {
		resultPage, _ := nyaa_scraper.SearchSpecificPage(
			nc.SearchTerm,
			nc.Category,
			nc.Filter,
			nc.LoadedPages,
		)
		nc.Results = append(nc.Results, resultPage.Results...)
		nc.Gui.Update(func(gui *gocui.Gui) error {
			_, oy := nc.ResultsView.Origin()
			_, y := nc.ResultsView.Cursor()

			gui.DeleteView(ncInfoView)
			gui.DeleteView(ncResultsView)

			nc.Layout(gui)
			nc.ResultsView.SetOrigin(0, oy)
			nc.ResultsView.SetCursor(0, y)

			return nil
		})
	}()
}

func (nc *nyaaCui) ChangeCategory() {
	categories := make([]fmt.Stringer, len(nyaa_scraper.Categories))
	for i := range categories {
		categories[i] = nyaa_scraper.Categories[i]
	}
	selIdxChan, cleanUp, err := dialog.ListSelect(nc.Gui, "Select category", categories)
	if err != nil {
		gocuiReturnError(nc.Gui, err)
	}
	go func() {
		idx, ok := <-selIdxChan
		nc.Gui.Update(cleanUp)
		if ok {
			nc.Category = nyaa_scraper.Categories[idx]
			nc.Reload(nc.Gui)
		}
	}()
}

func (nc *nyaaCui) ChangeFilter() {
	filters := make([]fmt.Stringer, len(nyaa_scraper.Filters))
	for i := range filters {
		filters[i] = nyaa_scraper.Filters[i]
	}
	selIdxChan, cleanUp, err := dialog.ListSelect(nc.Gui, "Select filter", filters)
	if err != nil {
		gocuiReturnError(nc.Gui, err)
	}
	go func() {
		idx, ok := <-selIdxChan
		nc.Gui.Update(cleanUp)
		if ok {
			nc.Filter = nyaa_scraper.Filters[idx]
			nc.Reload(nc.Gui)
		}
	}()
}

func (nc *nyaaCui) setGuiKeyBindings(gui *gocui.Gui) {
	gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quitGocui)
}

func quitGocui(gui *gocui.Gui, view *gocui.View) error {
	return gocui.ErrQuit
}

func gocuiReturnError(gui *gocui.Gui, err error) {
	gui.Update(func(gui *gocui.Gui) error {
		return err
	})
}
<commit_msg>Display entry progress in nyaa_cui info bar<commit_after>package main

import (
	"fmt"
	"math"
	"os/exec"
	"strings"

	"github.com/aqatl/mal/dialog"
	"github.com/aqatl/mal/nyaa_scraper"
	"github.com/fatih/color"
	"github.com/jroimartin/gocui"
	"github.com/urfave/cli"
)

func malNyaaCui(ctx *cli.Context) error {
	_, list, err := loadMAL(ctx)
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	entry := list.GetByID(cfg.SelectedID)
	if entry == nil {
		return fmt.Errorf("no entry found")
	}
	return startNyaaCui(
		cfg,
		entry.Title,
		fmt.Sprintf("%s %d/%d", entry.Title, entry.WatchedEpisodes, entry.Episodes),
	)
}

func alNyaaCui(ctx *cli.Context) error {
	al, err := loadAniList()
	if err != nil {
		return err
	}
	cfg := LoadConfig()

	entry := al.GetMediaListById(cfg.ALSelectedID)
	if entry == nil {
		return fmt.Errorf("no entry found")
	}

	searchTerm := entry.Title.UserPreferred
	if ctx.Bool("alt") {
		alts := make([]string, 0, 3+len(entry.Synonyms))
		if t := entry.Title.English; t != "" {
			alts = append(alts, t)
		}
		if t := entry.Title.Native; t != "" {
			alts = append(alts, t)
		}
		if t := entry.Title.Romaji; t != "" {
			alts = append(alts, t)
		}
		alts = append(alts, entry.Synonyms...)
		fmt.Printf("Select desired title\n\n")
		if searchTerm = chooseStrFromSlice(alts); searchTerm == "" {
			return fmt.Errorf("no alternative titles")
		}
	} else if ctx.NArg() > 0 {
		searchTerm = strings.Join(ctx.Args(), " ")
	}

	return startNyaaCui(
		cfg,
		searchTerm,
		fmt.Sprintf("%s %d/%d", searchTerm, entry.Progress, entry.Episodes),
	)
}

func startNyaaCui(cfg *Config, searchTerm, displayedInfo string) error {
	gui, err := gocui.NewGui(gocui.Output256)
	defer gui.Close()
	if err != nil {
		return fmt.Errorf("gocui error: %v", err)
	}
	nc := &nyaaCui{
		Gui: gui,
		Cfg: cfg,

		SearchTerm:    searchTerm,
		DisplayedInfo: displayedInfo,
		Category:      nyaa_scraper.AnimeEnglishTranslated,
		Filter:        nyaa_scraper.NoFilter,
	}
	gui.SetManager(nc)
	nc.setGuiKeyBindings(gui)

	gui.Cursor = false
	gui.Mouse = false
	gui.Highlight = true
	gui.SelFgColor = gocui.ColorGreen

	gui.Update(func(gui *gocui.Gui) error {
		nc.Reload(gui)
		return nil
	})

	if err = gui.MainLoop(); err != nil && err != gocui.ErrQuit {
		return err
	}
	return nil
}

const (
	ncInfoView      = "ncInfoView"
	ncResultsView   = "ncResultsView "
	ncShortcutsView = "ncShortcutsView"
)

type nyaaCui struct {
	Gui *gocui.Gui
	Cfg *Config

	SearchTerm    string
	DisplayedInfo string
	Category      nyaa_scraper.NyaaCategory
	Filter        nyaa_scraper.NyaaFilter

	Results     []nyaa_scraper.NyaaEntry
	MaxResults  int
	MaxPages    int
	LoadedPages int

	ResultsView *gocui.View
}

var red = color.New(color.FgRed).SprintFunc()
var cyan = color.New(color.FgCyan).SprintFunc()
var blue = color.New(color.FgBlue).SprintFunc()
var green = color.New(color.FgGreen).SprintFunc()

func (nc *nyaaCui) Layout(gui *gocui.Gui) error {
	w, h := gui.Size()
	if v, err := gui.SetView(ncInfoView, 0, 0, w-1, 2); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Info"
		v.Editable = false

		fmt.Fprintf(v, "[%s]: displaying %d out of %d results",
			nc.DisplayedInfo, len(nc.Results), nc.MaxResults)
	}

	if v, err := gui.SetView(ncResultsView, 0, 3, w-1, h-4); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Search results"
		v.SelBgColor = gocui.ColorGreen
		v.SelBgColor = gocui.ColorGreen
		v.SelFgColor = gocui.ColorBlack
		v.Highlight = true
		v.Editable = true
		v.Editor = gocui.EditorFunc(nc.GetEditor())

		gui.SetCurrentView(ncResultsView)
		nc.ResultsView = v

		//TODO Better/clearer results printing
		for _, result := range nc.Results {
			fmt.Fprintln(v,
				result.Title,
				red(result.Size),
				cyan(result.DateAdded.Format("15:04:05 02-01-2006 MST")),
				green(result.Seeders),
				red(result.Leechers),
				blue(result.CompletedDownloads),
			)
		}
	}

	if v, err := gui.SetView(ncShortcutsView, 0, h-3, w-1, h-1); err != nil {
		if err != gocui.ErrUnknownView {
			return err
		}

		v.Title = "Shortcuts"
		v.Editable = false

		c := color.New(color.FgCyan).SprintFunc()
		fmt.Fprintln(v, c("d"), "download", c("l"), "load next page",
			c("c"), "category", c("f"), "filters")
	}

	return nil
}

func (nc *nyaaCui) GetEditor() func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
	return func(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
		switch {
		case key == gocui.KeyArrowDown || ch == 'j':
			_, oy := v.Origin()
			_, y := v.Cursor()
			y += oy
			if y < len(nc.Results)-1 {
				v.MoveCursor(0, 1, false)
			}
		case key == gocui.KeyArrowUp || ch == 'k':
			v.MoveCursor(0, -1, false)
		case ch == 'd':
			_, y := v.Cursor()
			_, oy := v.Origin()
			y += oy
			nc.Download(y)
		case ch == 'l':
			nc.LoadNextPage()
		case ch == 'c':
			nc.ChangeCategory()
		case ch == 'f':
			nc.ChangeFilter()
		}
	}
}

func (nc *nyaaCui) Reload(gui *gocui.Gui) {
	var resultPage nyaa_scraper.NyaaResultPage
	var searchErr error
	f := func() {
		resultPage, searchErr = nyaa_scraper.Search(nc.SearchTerm, nc.Category, nc.Filter)
	}
	jobDone, err := dialog.StuffLoader(dialog.FitMessage(gui, "Loading "+nc.SearchTerm), f)
	if err != nil {
		gocuiReturnError(gui, err)
	}
	go func() {
		ok := <-jobDone
		if searchErr != nil {
			dialog.JustShowOkDialog(gui, "Error", searchErr.Error())
			return
		}
		if ok {
			nc.Results = resultPage.Results
			nc.MaxResults = resultPage.DisplayedOutOf
			nc.MaxPages = int(math.Ceil(float64(resultPage.DisplayedOutOf) /
				float64(resultPage.DisplayedTo-resultPage.DisplayedFrom+1)))
			nc.LoadedPages = 1
		}

		gui.Update(func(gui *gocui.Gui) error {
			gui.DeleteView(ncResultsView)
			gui.DeleteView(ncInfoView)
			return nil
		})
	}()
}

func (nc *nyaaCui) Download(yIdx int) {
	if yIdx >= len(nc.Results) {
		return
	}

	link := ""
	if entry := nc.Results[yIdx]; entry.MagnetLink != "" {
		link = entry.MagnetLink
	} else if entry.TorrentLink != "" {
		link = entry.TorrentLink
	} else {
		dialog.JustShowOkDialog(nc.Gui, "Error", "No link found")
		return
	}

	link = "\"" + link + "\""
	cmd := exec.Command(nc.Cfg.TorrentClientPath, nc.Cfg.TorrentClientArgs...)
	cmd.Args = append(cmd.Args, link)
	cmd.Args = cmd.Args[1:] //Why they include app name in the arguments???
	if err := cmd.Start(); err != nil {
		gocuiReturnError(nc.Gui, err)
	}
}

func (nc *nyaaCui) LoadNextPage() {
	if nc.LoadedPages >= nc.MaxPages {
		return
	}
	nc.LoadedPages++
	go func() {
		resultPage, _ := nyaa_scraper.SearchSpecificPage(
			nc.SearchTerm,
			nc.Category,
			nc.Filter,
			nc.LoadedPages,
		)
		nc.Results = append(nc.Results, resultPage.Results...)
		nc.Gui.Update(func(gui *gocui.Gui) error {
			_, oy := nc.ResultsView.Origin()
			_, y := nc.ResultsView.Cursor()

			gui.DeleteView(ncInfoView)
			gui.DeleteView(ncResultsView)

			nc.Layout(gui)
			nc.ResultsView.SetOrigin(0, oy)
			nc.ResultsView.SetCursor(0, y)

			return nil
		})
	}()
}

func (nc *nyaaCui) ChangeCategory() {
	categories := make([]fmt.Stringer, len(nyaa_scraper.Categories))
	for i := range categories {
		categories[i] = nyaa_scraper.Categories[i]
	}
	selIdxChan, cleanUp, err := dialog.ListSelect(nc.Gui, "Select category", categories)
	if err != nil {
		gocuiReturnError(nc.Gui, err)
	}
	go func() {
		idx, ok := <-selIdxChan
		nc.Gui.Update(cleanUp)
		if ok {
			nc.Category = nyaa_scraper.Categories[idx]
			nc.Reload(nc.Gui)
		}
	}()
}

func (nc *nyaaCui) ChangeFilter() {
	filters := make([]fmt.Stringer, len(nyaa_scraper.Filters))
	for i := range filters {
		filters[i] = nyaa_scraper.Filters[i]
	}
	selIdxChan, cleanUp, err := dialog.ListSelect(nc.Gui, "Select filter", filters)
	if err != nil {
		gocuiReturnError(nc.Gui, err)
	}
	go func() {
		idx, ok := <-selIdxChan
		nc.Gui.Update(cleanUp)
		if ok {
			nc.Filter = nyaa_scraper.Filters[idx]
			nc.Reload(nc.Gui)
		}
	}()
}

func (nc *nyaaCui) setGuiKeyBindings(gui *gocui.Gui) {
	gui.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quitGocui)
}

func quitGocui(gui *gocui.Gui, view *gocui.View) error {
	return gocui.ErrQuit
}

func gocuiReturnError(gui *gocui.Gui, err error) {
	gui.Update(func(gui *gocui.Gui) error {
		return err
	})
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"github.com/dexterlb/mpvipc"
	"github.com/sirupsen/logrus"
	"github.com/vansante/go-event-emitter"
	"os"
	"os/exec"
	"sync"
	"time"
)

const (
	MPV_INIT_RETRY_ATTEMPTS = 5
	MAX_MPV_LOAD_WAIT       = time.Duration(time.Second * 20)
)

type MpvControl struct {
	mpvBinPath   string
	mpvInputPath string

	mpvProcess   *exec.Cmd
	mpvIsRunning bool
	mpvConn      *mpvipc.Connection
	mpvMutex     sync.Mutex
	mpvEvents    *eventemitter.Emitter
}

func NewMpvControl(mpvBinPath, mpvInputPath string) (control *MpvControl, err error) {
	if mpvBinPath == "" {
		mpvBinPath = "mpv"
	}
	if mpvInputPath == "" {
		mpvInputPath = ".mpv-input"
	}
	control = &MpvControl{
		mpvBinPath:   mpvBinPath,
		mpvInputPath: mpvInputPath,
		mpvIsRunning: false,
		mpvEvents:    eventemitter.NewEmitter(),
	}

	err = control.initMpv()
	return
}

func (c *MpvControl) initMpv() (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	fi, err := os.Stat(c.mpvInputPath)
	if err == nil && !fi.IsDir() {
		logrus.Infof("MpvControl.initMpv: Removing existing mpv input on: %s", c.mpvInputPath)
		err = os.Remove(c.mpvInputPath)
		if err != nil {
			logrus.Errorf("MpvControl.initMpv: Error removing existing mpv input [%s] %v", c.mpvInputPath, err)
			return
		}
	}

	err = c.startMpv()
	if err != nil {
		logrus.Errorf("MpvControl.initMpv: Error starting mpv [%s] %v", c.mpvBinPath, err)
		return
	}

	attempts := 0
	for {
		// Give MPV a second or so to start and create the input socket
		time.Sleep(500 * time.Millisecond)

		logrus.Infof("MpvControl.initMpv: Attempting to open ipc connection to mpv [%s]", c.mpvInputPath)
		c.mpvConn = mpvipc.NewConnection(c.mpvInputPath)
		err = c.mpvConn.Open()
		if err != nil {
			if attempts >= MPV_INIT_RETRY_ATTEMPTS {
				logrus.Errorf("MpvControl.initMpv: Error opening ipc connection to mpv [%s] %v", c.mpvInputPath, err)
				return
			}
		} else {
			err = nil
			break
		}
		attempts++
	}

	logrus.Infof("MpvControl.initMpv: Connected to mpv ipc [%s]", c.mpvInputPath)

	// Turn on all events.
	c.mpvConn.Call("enable_event", "all")

	go func() {
		events, stopListening := c.mpvConn.NewEventListener()
		for event := range events {
			c.mpvEvents.EmitEvent(event.Name, event)
		}
		stopListening <- struct{}{}
	}()

	return
}

func (c *MpvControl) startMpv() (err error) {
	logrus.Infof("MpvControl.startMpv: Starting MPV %s with control %s in idle mode", c.mpvBinPath, c.mpvInputPath)

	command := exec.Command(c.mpvBinPath, "--no-video", "--idle", "--input-ipc-server="+c.mpvInputPath)
	c.mpvProcess = command

	err = command.Start()
	c.mpvIsRunning = err == nil

	if err != nil {
		logrus.Errorf("MpvControl.startMpv: Error starting mpv [%s | %s] %v", c.mpvBinPath, c.mpvInputPath, err)
		return
	}

	go func() {
		err := command.Wait()

		c.mpvMutex.Lock()
		c.mpvIsRunning = false
		c.mpvProcess = nil
		c.mpvMutex.Unlock()

		logrus.Infof("MpvControl.startMpv: mpv has exited [%s | %s] %v", c.mpvBinPath, c.mpvInputPath, err)
	}()
	return
}

func (c *MpvControl) checkRunning() (err error) {
	if c.mpvIsRunning && c.mpvProcess != nil {
		return
	}
	logrus.Warn("MpvControl.checkRunning: mpv is not running, restarting")
	err = c.initMpv()
	if err != nil {
		logrus.Errorf("MpvControl.checkRunning: Error restarting mpv: %v", err)
		return
	}
	return
}

func (c *MpvControl) LoadFile(uri string) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.LoadFile: Running check error: %v", err)
		return
	}

	err = c.stop()
	if err != nil {
		return
	}

	waitForLoad := make(chan bool)
	c.mpvEvents.ListenOnce("file-loaded", func(arguments ...interface{}) {
		waitForLoad <- true
	})

	// Start an event listener to wait for the file to load.
	_, err = c.mpvConn.Call("loadfile", uri, "replace")
	if err != nil {
		logrus.Errorf("MpvControl.LoadFile: Error sending loadfile command [%s] %v", uri, err)
		return
	}

	timeoutChan := time.NewTimer(MAX_MPV_LOAD_WAIT).C
	select {
	case <-waitForLoad:
		return
	case <-timeoutChan:
		logrus.Warnf("MpvControl.LoadFile: Load file timeout, did not receive file-loaded event in %d", MAX_MPV_LOAD_WAIT)
		_, err = c.mpvConn.Call("stop")
		if err != nil {
			logrus.Errorf("MpvControl.LoadFile: Error calling stop after timeout: %v", err)
			c.checkRunning()
			return
		}
		err = fmt.Errorf("error loading file, mpv did not respond in time")
		return
	}
	return
}

func (c *MpvControl) Seek(positionSeconds int) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.Seek: Running check error: %v", err)
		return
	}

	err = c.mpvConn.Set("time-pos", positionSeconds)
	if err != nil {
		logrus.Errorf("MpvControl.Seek: Error sending time-pos command [%d] %v", positionSeconds, err)
		return
	}
	return
}

func (c *MpvControl) Pause(pauseState bool) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.Pause: Running check error: %v", err)
		return
	}

	err = c.mpvConn.Set("pause", pauseState)
	if err != nil {
		logrus.Errorf("MpvControl.Pause: Error sending pause command [%v] %v", pauseState, err)
		return
	}
	return
}

func (c *MpvControl) Stop() (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	return c.stop()
}

func (c *MpvControl) stop() (err error) {
	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.stop: Running check error: %v", err)
		return
	}

	_, err = c.mpvConn.Call("stop")
	if err != nil {
		logrus.Errorf("MpvControl.stop: Error sending stop command: %v", err)
		return
	}
	return
}
<commit_msg>Fix case sensitive import<commit_after>package util

import (
	"fmt"
	"github.com/Sirupsen/logrus"
	"github.com/dexterlb/mpvipc"
	"github.com/vansante/go-event-emitter"
	"os"
	"os/exec"
	"sync"
	"time"
)

const (
	MPV_INIT_RETRY_ATTEMPTS = 5
	MAX_MPV_LOAD_WAIT       = time.Duration(time.Second * 20)
)

type MpvControl struct {
	mpvBinPath   string
	mpvInputPath string

	mpvProcess   *exec.Cmd
	mpvIsRunning bool
	mpvConn      *mpvipc.Connection
	mpvMutex     sync.Mutex
	mpvEvents    *eventemitter.Emitter
}

func NewMpvControl(mpvBinPath, mpvInputPath string) (control *MpvControl, err error) {
	if mpvBinPath == "" {
		mpvBinPath = "mpv"
	}
	if mpvInputPath == "" {
		mpvInputPath = ".mpv-input"
	}
	control = &MpvControl{
		mpvBinPath:   mpvBinPath,
		mpvInputPath: mpvInputPath,
		mpvIsRunning: false,
		mpvEvents:    eventemitter.NewEmitter(),
	}

	err = control.initMpv()
	return
}

func (c *MpvControl) initMpv() (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	fi, err := os.Stat(c.mpvInputPath)
	if err == nil && !fi.IsDir() {
		logrus.Infof("MpvControl.initMpv: Removing existing mpv input on: %s", c.mpvInputPath)
		err = os.Remove(c.mpvInputPath)
		if err != nil {
			logrus.Errorf("MpvControl.initMpv: Error removing existing mpv input [%s] %v", c.mpvInputPath, err)
			return
		}
	}

	err = c.startMpv()
	if err != nil {
		logrus.Errorf("MpvControl.initMpv: Error starting mpv [%s] %v", c.mpvBinPath, err)
		return
	}

	attempts := 0
	for {
		// Give MPV a second or so to start and create the input socket
		time.Sleep(500 * time.Millisecond)

		logrus.Infof("MpvControl.initMpv: Attempting to open ipc connection to mpv [%s]", c.mpvInputPath)
		c.mpvConn = mpvipc.NewConnection(c.mpvInputPath)
		err = c.mpvConn.Open()
		if err != nil {
			if attempts >= MPV_INIT_RETRY_ATTEMPTS {
				logrus.Errorf("MpvControl.initMpv: Error opening ipc connection to mpv [%s] %v", c.mpvInputPath, err)
				return
			}
		} else {
			err = nil
			break
		}
		attempts++
	}

	logrus.Infof("MpvControl.initMpv: Connected to mpv ipc [%s]", c.mpvInputPath)

	// Turn on all events.
	c.mpvConn.Call("enable_event", "all")

	go func() {
		events, stopListening := c.mpvConn.NewEventListener()
		for event := range events {
			c.mpvEvents.EmitEvent(event.Name, event)
		}
		stopListening <- struct{}{}
	}()

	return
}

func (c *MpvControl) startMpv() (err error) {
	logrus.Infof("MpvControl.startMpv: Starting MPV %s with control %s in idle mode", c.mpvBinPath, c.mpvInputPath)

	command := exec.Command(c.mpvBinPath, "--no-video", "--idle", "--input-ipc-server="+c.mpvInputPath)
	c.mpvProcess = command

	err = command.Start()
	c.mpvIsRunning = err == nil

	if err != nil {
		logrus.Errorf("MpvControl.startMpv: Error starting mpv [%s | %s] %v", c.mpvBinPath, c.mpvInputPath, err)
		return
	}

	go func() {
		err := command.Wait()

		c.mpvMutex.Lock()
		c.mpvIsRunning = false
		c.mpvProcess = nil
		c.mpvMutex.Unlock()

		logrus.Infof("MpvControl.startMpv: mpv has exited [%s | %s] %v", c.mpvBinPath, c.mpvInputPath, err)
	}()
	return
}

func (c *MpvControl) checkRunning() (err error) {
	if c.mpvIsRunning && c.mpvProcess != nil {
		return
	}
	logrus.Warn("MpvControl.checkRunning: mpv is not running, restarting")
	err = c.initMpv()
	if err != nil {
		logrus.Errorf("MpvControl.checkRunning: Error restarting mpv: %v", err)
		return
	}
	return
}

func (c *MpvControl) LoadFile(uri string) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.LoadFile: Running check error: %v", err)
		return
	}

	err = c.stop()
	if err != nil {
		return
	}

	waitForLoad := make(chan bool)
	c.mpvEvents.ListenOnce("file-loaded", func(arguments ...interface{}) {
		waitForLoad <- true
	})

	// Start an event listener to wait for the file to load.
	_, err = c.mpvConn.Call("loadfile", uri, "replace")
	if err != nil {
		logrus.Errorf("MpvControl.LoadFile: Error sending loadfile command [%s] %v", uri, err)
		return
	}

	timeoutChan := time.NewTimer(MAX_MPV_LOAD_WAIT).C
	select {
	case <-waitForLoad:
		return
	case <-timeoutChan:
		logrus.Warnf("MpvControl.LoadFile: Load file timeout, did not receive file-loaded event in %d", MAX_MPV_LOAD_WAIT)
		_, err = c.mpvConn.Call("stop")
		if err != nil {
			logrus.Errorf("MpvControl.LoadFile: Error calling stop after timeout: %v", err)
			c.checkRunning()
			return
		}
		err = fmt.Errorf("error loading file, mpv did not respond in time")
		return
	}
	return
}

func (c *MpvControl) Seek(positionSeconds int) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.Seek: Running check error: %v", err)
		return
	}

	err = c.mpvConn.Set("time-pos", positionSeconds)
	if err != nil {
		logrus.Errorf("MpvControl.Seek: Error sending time-pos command [%d] %v", positionSeconds, err)
		return
	}
	return
}

func (c *MpvControl) Pause(pauseState bool) (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.Pause: Running check error: %v", err)
		return
	}

	err = c.mpvConn.Set("pause", pauseState)
	if err != nil {
		logrus.Errorf("MpvControl.Pause: Error sending pause command [%v] %v", pauseState, err)
		return
	}
	return
}

func (c *MpvControl) Stop() (err error) {
	c.mpvMutex.Lock()
	defer c.mpvMutex.Unlock()

	return c.stop()
}

func (c *MpvControl) stop() (err error) {
	err = c.checkRunning()
	if err != nil {
		logrus.Errorf("MpvControl.stop: Running check error: %v", err)
		return
	}

	_, err = c.mpvConn.Call("stop")
	if err != nil {
		logrus.Errorf("MpvControl.stop: Error sending stop command: %v", err)
		return
	}
	return
}
<|endoftext|>


<commit_before>package skycmd

import (
	"encoding/json"
	"errors"

	"github.com/concourse/dex/connector/oidc"
	"github.com/concourse/flag"
	"github.com/hashicorp/go-multierror"
)

func init() {
	RegisterConnector(&Connector{
		id:         "oidc",
		config:     &OIDCFlags{},
		teamConfig: &OIDCTeamFlags{},
	})
}

type OIDCFlags struct {
	DisplayName        string      `long:"display-name" description:"The auth provider name displayed to users on the login page"`
	Issuer             string      `long:"issuer" description:"(Required) An OIDC issuer URL that will be used to discover provider configuration using the .well-known/openid-configuration"`
	ClientID           string      `long:"client-id" description:"(Required) Client id"`
	ClientSecret       string      `long:"client-secret" description:"(Required) Client secret"`
	Scopes             []string    `long:"scope" description:"Any additional scopes of [openid] that need to be requested during authorization. Default to [openid, profile, email]."`
	GroupsKey          string      `long:"groups-key" default:"groups" description:"The groups key indicates which claim to use to map external groups to Concourse teams."`
	UserNameKey        string      `long:"user-name-key" default:"username" description:"The user name key indicates which claim to use to map an external user name to a Concourse user name."`
	HostedDomains      []string    `long:"hosted-domains" description:"List of whitelisted domains when using Google, only users from a listed domain will be allowed to log in"`
	CACerts            []flag.File `long:"ca-cert" description:"CA Certificate"`
	InsecureSkipVerify bool        `long:"skip-ssl-validation" description:"Skip SSL validation"`
	DisableGroups      bool        `long:"disable-groups" description:"Disable OIDC groups claims"`
	SkipEmailVerified  bool        `long:"skip-email-verified" description: "Override email_verified field"`
}

func (flag *OIDCFlags) Name() string {
	if flag.DisplayName != "" {
		return flag.DisplayName
	}
	return "OIDC"
}

func (flag *OIDCFlags) Validate() error {
	var errs *multierror.Error

	if flag.Issuer == "" {
		errs = multierror.Append(errs, errors.New("Missing issuer"))
	}

	if flag.ClientID == "" {
		errs = multierror.Append(errs, errors.New("Missing client-id"))
	}

	if flag.ClientSecret == "" {
		errs = multierror.Append(errs, errors.New("Missing client-secret"))
	}

	return errs.ErrorOrNil()
}

func (flag *OIDCFlags) Serialize(redirectURI string) ([]byte, error) {
	if err := flag.Validate(); err != nil {
		return nil, err
	}

	caCerts := []string{}
	for _, file := range flag.CACerts {
		caCerts = append(caCerts, file.Path())
	}

	config := oidc.Config{
		Issuer:                    flag.Issuer,
		ClientID:                  flag.ClientID,
		ClientSecret:              flag.ClientSecret,
		Scopes:                    flag.Scopes,
		UserNameKey:               flag.UserNameKey,
		HostedDomains:             flag.HostedDomains,
		RootCAs:                   caCerts,
		InsecureSkipVerify:        flag.InsecureSkipVerify,
		RedirectURI:               redirectURI,
		InsecureEnableGroups:      !flag.DisableGroups,
		InsecureSkipEmailVerified: flag.SkipEmailVerified,
	}

	config.ClaimMapping.GroupsKey = flag.GroupsKey
	config.ClaimMapping.PreferredUsernameKey = flag.UserNameKey

	return json.Marshal(config)
}

type OIDCTeamFlags struct {
	Users  []string `json:"users" long:"user" description:"A whitelisted OIDC user" value-name:"USERNAME"`
	Groups []string `json:"groups" long:"group" description:"A whitelisted OIDC group" value-name:"GROUP_NAME"`
}

func (flag *OIDCTeamFlags) GetUsers() []string {
	return flag.Users
}

func (flag *OIDCTeamFlags) GetGroups() []string {
	return flag.Groups
}
<commit_msg>Update<commit_after>package skycmd

import (
	"encoding/json"
	"errors"

	"github.com/concourse/dex/connector/oidc"
	"github.com/concourse/flag"
	"github.com/hashicorp/go-multierror"
)

func init() {
	RegisterConnector(&Connector{
		id:         "oidc",
		config:     &OIDCFlags{},
		teamConfig: &OIDCTeamFlags{},
	})
}

type OIDCFlags struct {
	DisplayName         string      `long:"display-name" description:"The auth provider name displayed to users on the login page"`
	Issuer              string      `long:"issuer" description:"(Required) An OIDC issuer URL that will be used to discover provider configuration using the .well-known/openid-configuration"`
	ClientID            string      `long:"client-id" description:"(Required) Client id"`
	ClientSecret        string      `long:"client-secret" description:"(Required) Client secret"`
	Scopes              []string    `long:"scope" description:"Any additional scopes of [openid] that need to be requested during authorization. Default to [openid, profile, email]."`
	GroupsKey           string      `long:"groups-key" default:"groups" description:"The groups key indicates which claim to use to map external groups to Concourse teams."`
	UserNameKey         string      `long:"user-name-key" default:"username" description:"The user name key indicates which claim to use to map an external user name to a Concourse user name."`
	HostedDomains       []string    `long:"hosted-domains" description:"List of whitelisted domains when using Google, only users from a listed domain will be allowed to log in"`
	CACerts             []flag.File `long:"ca-cert" description:"CA Certificate"`
	InsecureSkipVerify  bool        `long:"skip-ssl-validation" description:"Skip SSL validation"`
	DisableGroups       bool        `long:"disable-groups" description:"Disable OIDC groups claims"`
	OverrideEmailVerify bool        `long:"override-email-verified" description:"Enable override of email_verified field"`
}

func (flag *OIDCFlags) Name() string {
	if flag.DisplayName != "" {
		return flag.DisplayName
	}
	return "OIDC"
}

func (flag *OIDCFlags) Validate() error {
	var errs *multierror.Error

	if flag.Issuer == "" {
		errs = multierror.Append(errs, errors.New("Missing issuer"))
	}

	if flag.ClientID == "" {
		errs = multierror.Append(errs, errors.New("Missing client-id"))
	}

	if flag.ClientSecret == "" {
		errs = multierror.Append(errs, errors.New("Missing client-secret"))
	}

	return errs.ErrorOrNil()
}

func (flag *OIDCFlags) Serialize(redirectURI string) ([]byte, error) {
	if err := flag.Validate(); err != nil {
		return nil, err
	}

	caCerts := []string{}
	for _, file := range flag.CACerts {
		caCerts = append(caCerts, file.Path())
	}

	config := oidc.Config{
		Issuer:                    flag.Issuer,
		ClientID:                  flag.ClientID,
		ClientSecret:              flag.ClientSecret,
		Scopes:                    flag.Scopes,
		UserNameKey:               flag.UserNameKey,
		HostedDomains:             flag.HostedDomains,
		RootCAs:                   caCerts,
		InsecureSkipVerify:        flag.InsecureSkipVerify,
		RedirectURI:               redirectURI,
		InsecureEnableGroups:      !flag.DisableGroups,
		InsecureSkipEmailVerified: flag.OverrideEmailVerify,
	}

	config.ClaimMapping.GroupsKey = flag.GroupsKey
	config.ClaimMapping.PreferredUsernameKey = flag.UserNameKey

	return json.Marshal(config)
}

type OIDCTeamFlags struct {
	Users  []string `json:"users" long:"user" description:"A whitelisted OIDC user" value-name:"USERNAME"`
	Groups []string `json:"groups" long:"group" description:"A whitelisted OIDC group" value-name:"GROUP_NAME"`
}

func (flag *OIDCTeamFlags) GetUsers() []string {
	return flag.Users
}

func (flag *OIDCTeamFlags) GetGroups() []string {
	return flag.Groups
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
)

// An individual move
type Move struct {
	Col int
	Row int
}

func (m Move) toMoveString() (string) {
	return fmt.Sprintf("place_move %d %d", m.Col, m.Row)
}

func FromMoveString(move string) (*Move) {
	var row int
	var col int
	fmt.Sscanf(move, "place_move %d %d", &col, &row)
	newmove := Move{Col:col, Row:row}
	return &newmove
}

// Game state and functions
//
//
type State struct {
	Field           [9][9]int8
	MacroBoard      [3][3]int8
	PlayerJustMoved int8
	GameFinished    int8
}

func (s *State) LogState() {
	log.Printf("GameFinished: %d, PlayerJustMoved: %d", s.GameFinished, s.PlayerJustMoved)
	log.Println("MacroBoard")
	for i := 0; i < 3; i++ {
		log.Printf("%v", s.MacroBoard[i])
	}

	log.Println("Field")
	for i := 0; i < 9; i++ {
		if i == 3 || i == 6 {
			log.Println("----------------------")
		}
		field_line := ""
		for j := 0; j < 9; j++ {
			if j == 3 || j == 6 {
				field_line = field_line + "| "
			}
			field_line = field_line + fmt.Sprintf("%d ", s.Field[i][j])
		}
		log.Println(field_line)
	}
}

func (s *State) Clone() *State {
	clone := new(State)
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			clone.Field[i][j] = s.Field[i][j]
		}
	}
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			clone.MacroBoard[i][j] = s.MacroBoard[i][j]
		}
	}
	clone.PlayerJustMoved = s.PlayerJustMoved
	// really shouldn't need this
	clone.GameFinished = clone.GameFinished
	return clone
}

func (s *State) MarkDeadBoards() {
	for macro_i := 0; macro_i < 3; macro_i++ {
		for macro_j := 0; macro_j < 3; macro_j++ {
			if s.MacroBoard[macro_i][macro_j] == 0 {
				live := false
				for i := 0; i < 3; i++ {
					for j := 0; j < 3; j++ {
						if s.Field[macro_i*3 + i][macro_j*3 + j] == 0 {
							live = true
							break
						}
					}
					if live {
						break
					}
				}
				if ! live {
					s.MacroBoard[macro_i][macro_j] = 9
				}
			}
		}
	}
}

func (s *State) GetMoves() []*Move {
	moves := make([]*Move, 0, 9)
	for macro_i := 0; macro_i < 3; macro_i++ {
		for macro_j := 0; macro_j < 3; macro_j++ {
			if s.MacroBoard[macro_i][macro_j] == -1 {
				sub_i := macro_i * 3
				sub_j := macro_j * 3
				for i := 0; i < 3; i++ {
					for j := 0; j < 3; j++ {
						if s.Field[sub_i+i][sub_j+j] == 0 {
							moves = append(moves, &Move{Row: sub_i + i, Col: sub_j + j})
						}
					}
				}
			}
		}
	}
	return moves
}

func (s *State) GetResult(playerJustMoved int8) float64 {
	if s.GameFinished == 0 {
		log.Println("GetResult error, called when game wasn't finished!")
		return -1.0
	}
	if s.GameFinished == 3 { //draw
		return 0.5
	}
	if s.GameFinished == playerJustMoved {
		return 1.0
	}
	return 0.0
}

func (s *State) DoMove(m *Move) {
	if s.Field[m.Row][m.Col] != 0 {
		log.Println("DoMove error, move at row %d, col %d is already %d", m.Row, m.Col, s.Field[m.Row][m.Col])
	}

	if s.PlayerJustMoved == 1 {
		s.PlayerJustMoved = 2
	} else {
		s.PlayerJustMoved = 1
	}
	s.Field[m.Row][m.Col] = s.PlayerJustMoved

	sub_row := (m.Row / 3) * 3
	sub_col := (m.Col / 3) * 3
	macro_row := m.Row / 3
	macro_col := m.Col / 3

	// Find out if someone just won a sub-board
	macro_board_taken := false
	for i := 0; i < 3; i++ {
		if s.Field[sub_row+i][sub_col+0] == s.PlayerJustMoved && s.Field[sub_row+i][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+i][sub_col+2] == s.PlayerJustMoved {
			macro_board_taken = true
		}

		if s.Field[sub_row+0][sub_col+i] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+i] == s.PlayerJustMoved && s.Field[sub_row+2][sub_col+i] == s.PlayerJustMoved {
			macro_board_taken = true
		}
	}
	// diags
	if s.Field[sub_row][sub_col] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+2][sub_col+2] == s.PlayerJustMoved {
		macro_board_taken = true
	}

	if s.Field[sub_row+2][sub_col+0] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+0][sub_col+2] == s.PlayerJustMoved {
		macro_board_taken = true
	}

	if macro_board_taken {
		s.MacroBoard[macro_row][macro_col] = s.PlayerJustMoved
		// Did PlayerJustMoved win?
		s.checkMacroWin()
		if s.GameFinished > 0 {
			return
		}
	}

	// Find out if this board is now dead
	dead := true
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if s.Field[sub_row+i][sub_col+j] == 0 {
				dead = false
				break
			}
		}
		if dead == false {
			break
		}
	}
	if dead {
		s.MacroBoard[macro_row][macro_col] = 9
	}

	// Figure out next macro board opportunities
	next_macro_row := m.Row % 3
	next_macro_col := m.Col % 3
	if s.MacroBoard[next_macro_row][next_macro_col] <= 0 {
		for i := 0; i < 3; i++ {
			for j := 0; j < 3; j++ {
				if s.MacroBoard[i][j] <= 0 {
					s.MacroBoard[i][j] = 0
				}
			}
		}
		s.MacroBoard[next_macro_row][next_macro_col] = -1
	} else {
		macro_avail_count := 0
		for i := 0; i < 3; i++ {
			for j := 0; j < 3; j++ {
				if s.MacroBoard[i][j] <= 0 {
					s.MacroBoard[i][j] = -1
					macro_avail_count++
				}
			}
		}
		if macro_avail_count == 0 {
			s.GameFinished = 3 // Game finished in a draw
		}
	}
}

func (s *State) checkMacroWin() {
	game_won := false
	for i := 0; i < 3; i++ {
		if s.MacroBoard[i][0] == s.PlayerJustMoved && s.MacroBoard[i][1] == s.PlayerJustMoved && s.MacroBoard[i][2] == s.PlayerJustMoved {
			game_won = true
		}

		if s.MacroBoard[0][i] == s.PlayerJustMoved && s.MacroBoard[1][i] == s.PlayerJustMoved && s.MacroBoard[2][i] == s.PlayerJustMoved {
			game_won = true
		}
	}
	// diags
	if s.MacroBoard[0][0] == s.PlayerJustMoved && s.MacroBoard[1][1] == s.PlayerJustMoved && s.MacroBoard[2][2] == s.PlayerJustMoved {
		game_won = true
	}

	if s.MacroBoard[2][0] == s.PlayerJustMoved && s.MacroBoard[1][1] == s.PlayerJustMoved && s.MacroBoard[0][2] == s.PlayerJustMoved {
		game_won = true
	}

	if game_won {
		s.GameFinished = s.PlayerJustMoved
	}
}
<commit_msg>made illegal moves fatal, added a check for the legality of a move<commit_after>package main

import (
	"fmt"
	"log"
)

// An individual move
type Move struct {
	Col int
	Row int
}

func (m Move) toMoveString() (string) {
	return fmt.Sprintf("place_move %d %d", m.Col, m.Row)
}

func FromMoveString(move string) (*Move) {
	var row int
	var col int
	fmt.Sscanf(move, "place_move %d %d", &col, &row)
	newmove := Move{Col:col, Row:row}
	return &newmove
}

// Game state and functions
//
//
type State struct {
	Field           [9][9]int8
	MacroBoard      [3][3]int8
	PlayerJustMoved int8
	GameFinished    int8
}

func (s *State) LogState() {
	log.Printf("GameFinished: %d, PlayerJustMoved: %d", s.GameFinished, s.PlayerJustMoved)
	log.Println("MacroBoard")
	for i := 0; i < 3; i++ {
		log.Printf("%v", s.MacroBoard[i])
	}

	log.Println("Field")
	for i := 0; i < 9; i++ {
		if i == 3 || i == 6 {
			log.Println("----------------------")
		}
		field_line := ""
		for j := 0; j < 9; j++ {
			if j == 3 || j == 6 {
				field_line = field_line + "| "
			}
			field_line = field_line + fmt.Sprintf("%d ", s.Field[i][j])
		}
		log.Println(field_line)
	}
}

func (s *State) Clone() *State {
	clone := new(State)
	for i := 0; i < 9; i++ {
		for j := 0; j < 9; j++ {
			clone.Field[i][j] = s.Field[i][j]
		}
	}
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			clone.MacroBoard[i][j] = s.MacroBoard[i][j]
		}
	}
	clone.PlayerJustMoved = s.PlayerJustMoved
	// really shouldn't need this
	clone.GameFinished = clone.GameFinished
	return clone
}

func (s *State) MarkDeadBoards() {
	for macro_i := 0; macro_i < 3; macro_i++ {
		for macro_j := 0; macro_j < 3; macro_j++ {
			if s.MacroBoard[macro_i][macro_j] == 0 {
				live := false
				for i := 0; i < 3; i++ {
					for j := 0; j < 3; j++ {
						if s.Field[macro_i*3 + i][macro_j*3 + j] == 0 {
							live = true
							break
						}
					}
					if live {
						break
					}
				}
				if ! live {
					s.MacroBoard[macro_i][macro_j] = 9
				}
			}
		}
	}
}

func (s *State) GetMoves() []*Move {
	moves := make([]*Move, 0, 9)
	for macro_i := 0; macro_i < 3; macro_i++ {
		for macro_j := 0; macro_j < 3; macro_j++ {
			if s.MacroBoard[macro_i][macro_j] == -1 {
				sub_i := macro_i * 3
				sub_j := macro_j * 3
				for i := 0; i < 3; i++ {
					for j := 0; j < 3; j++ {
						if s.Field[sub_i+i][sub_j+j] == 0 {
							moves = append(moves, &Move{Row: sub_i + i, Col: sub_j + j})
						}
					}
				}
			}
		}
	}
	return moves
}

func (s *State) GetResult(playerJustMoved int8) float64 {
	if s.GameFinished == 0 {
		log.Println("GetResult error, called when game wasn't finished!")
		return -1.0
	}
	if s.GameFinished == 3 { //draw
		return 0.5
	}
	if s.GameFinished == playerJustMoved {
		return 1.0
	}
	return 0.0
}

func (s *State) DoMove(m *Move) {
	if s.Field[m.Row][m.Col] != 0 {
		log.Fatalf("DoMove error, move at row %d, col %d is already %d", m.Row, m.Col, s.Field[m.Row][m.Col])
	}
	if s.MacroBoard[m.Row/3][m.Col/3] != -1 {
		log.Fatalf("DoMove error, move at row %d, col %d is not a legal move", m.Row, m.Col)
	}

	if s.PlayerJustMoved == 1 {
		s.PlayerJustMoved = 2
	} else {
		s.PlayerJustMoved = 1
	}
	s.Field[m.Row][m.Col] = s.PlayerJustMoved

	sub_row := (m.Row / 3) * 3
	sub_col := (m.Col / 3) * 3
	macro_row := m.Row / 3
	macro_col := m.Col / 3

	// Find out if someone just won a sub-board
	macro_board_taken := false
	for i := 0; i < 3; i++ {
		if s.Field[sub_row+i][sub_col+0] == s.PlayerJustMoved && s.Field[sub_row+i][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+i][sub_col+2] == s.PlayerJustMoved {
			macro_board_taken = true
		}

		if s.Field[sub_row+0][sub_col+i] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+i] == s.PlayerJustMoved && s.Field[sub_row+2][sub_col+i] == s.PlayerJustMoved {
			macro_board_taken = true
		}
	}
	// diags
	if s.Field[sub_row][sub_col] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+2][sub_col+2] == s.PlayerJustMoved {
		macro_board_taken = true
	}

	if s.Field[sub_row+2][sub_col+0] == s.PlayerJustMoved && s.Field[sub_row+1][sub_col+1] == s.PlayerJustMoved && s.Field[sub_row+0][sub_col+2] == s.PlayerJustMoved {
		macro_board_taken = true
	}

	if macro_board_taken {
		s.MacroBoard[macro_row][macro_col] = s.PlayerJustMoved
		// Did PlayerJustMoved win?
		s.checkMacroWin()
		if s.GameFinished > 0 {
			return
		}
	}

	// Find out if this board is now dead
	dead := true
	for i := 0; i < 3; i++ {
		for j := 0; j < 3; j++ {
			if s.Field[sub_row+i][sub_col+j] == 0 {
				dead = false
				break
			}
		}
		if dead == false {
			break
		}
	}
	if dead {
		s.MacroBoard[macro_row][macro_col] = 9
	}

	// Figure out next macro board opportunities
	next_macro_row := m.Row % 3
	next_macro_col := m.Col % 3
	if s.MacroBoard[next_macro_row][next_macro_col] <= 0 {
		for i := 0; i < 3; i++ {
			for j := 0; j < 3; j++ {
				if s.MacroBoard[i][j] <= 0 {
					s.MacroBoard[i][j] = 0
				}
			}
		}
		s.MacroBoard[next_macro_row][next_macro_col] = -1
	} else {
		macro_avail_count := 0
		for i := 0; i < 3; i++ {
			for j := 0; j < 3; j++ {
				if s.MacroBoard[i][j] <= 0 {
					s.MacroBoard[i][j] = -1
					macro_avail_count++
				}
			}
		}
		if macro_avail_count == 0 {
			s.GameFinished = 3 // Game finished in a draw
		}
	}
}

func (s *State) checkMacroWin() {
	game_won := false
	for i := 0; i < 3; i++ {
		if s.MacroBoard[i][0] == s.PlayerJustMoved && s.MacroBoard[i][1] == s.PlayerJustMoved && s.MacroBoard[i][2] == s.PlayerJustMoved {
			game_won = true
		}

		if s.MacroBoard[0][i] == s.PlayerJustMoved && s.MacroBoard[1][i] == s.PlayerJustMoved && s.MacroBoard[2][i] == s.PlayerJustMoved {
			game_won = true
		}
	}
	// diags
	if s.MacroBoard[0][0] == s.PlayerJustMoved && s.MacroBoard[1][1] == s.PlayerJustMoved && s.MacroBoard[2][2] == s.PlayerJustMoved {
		game_won = true
	}

	if s.MacroBoard[2][0] == s.PlayerJustMoved && s.MacroBoard[1][1] == s.PlayerJustMoved && s.MacroBoard[0][2] == s.PlayerJustMoved {
		game_won = true
	}

	if game_won {
		s.GameFinished = s.PlayerJustMoved
	}
}
<|endoftext|>


<commit_before>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"time"
)

type Board struct {
	client         *Client
	ID             string `json:"id"`
	Name           string `json:"name"`
	Desc           string `json:"desc"`
	Closed         bool   `json:"closed"`
	IdOrganization string `json:"idOrganization"`
	Pinned         bool   `json:"pinned"`
	Url            string `json:"url"`
	ShortUrl       string `json:"shortUrl"`
	Prefs          struct {
		PermissionLevel       string            `json:"permissionLevel"`
		Voting                string            `json:"voting"`
		Comments              string            `json:"comments"`
		Invitations           string            `json:"invitations"`
		SelfJoin              bool              `json:"selfjoin"`
		CardCovers            bool              `json:"cardCovers"`
		CardAging             string            `json:"cardAging"`
		CalendarFeedEnabled   bool              `json:"calendarFeedEnabled"`
		Background            string            `json:"background"`
		BackgroundColor       string            `json:"backgroundColor"`
		BackgroundImage       string            `json:"backgroundImage"`
		BackgroundImageScaled []BackgroundImage `json:"backgroundImageScaled"`
		BackgroundTile        bool              `json:"backgroundTile"`
		BackgroundBrightness  string            `json:"backgroundBrightness"`
		CanBePublic           bool              `json:"canBePublic"`
		CanBeOrg              bool              `json:"canBeOrg"`
		CanBePrivate          bool              `json:"canBePrivate"`
		CanInvite             bool              `json:"canInvite"`
	} `json:"prefs"`
	LabelNames struct {
		Black  string `json:"black,omitempty"`
		Blue   string `json:"blue,omitempty"`
		Green  string `json:"green,omitempty"`
		Lime   string `json:"lime,omitempty"`
		Orange string `json:"orange,omitempty"`
		Pink   string `json:"pink,omitempty"`
		Purple string `json:"purple,omitempty"`
		Red    string `json:"red,omitempty"`
		Sky    string `json:"sky,omitempty"`
		Yellow string `json:"yellow,omitempty"`
	} `json:"labelNames"`
	Lists   []*List   `json:"lists"`
	Actions []*Action `json:"actions"`
    Organization Organization `json:"organization"`

}

type BackgroundImage struct {
	Width  int    `json:"width"`
	Height int    `json:"height"`
	URL    string `json:"url"`
}

func (b *Board) CreatedAt() time.Time {
	t, _ := IDToTime(b.ID)
	return t
}

/**
 * Board retrieves a Trello board by its ID.
 */
func (c *Client) GetBoard(boardID string, args Arguments) (board *Board, err error) {
	path := fmt.Sprintf("boards/%s", boardID)
	err = c.Get(path, args, &board)
	if board != nil {
		board.client = c
	}
	return
}

func (m *Member) GetBoards(args Arguments) (boards []*Board, err error) {
	path := fmt.Sprintf("members/%s/boards", m.ID)
	err = m.client.Get(path, args, &boards)
	for i := range boards {
		boards[i].client = m.client
	}
	return
}
<commit_msg>Runs go fmt over board.go after merging PR #25.<commit_after>// Copyright © 2016 Aaron Longwell
//
// Use of this source code is governed by an MIT licese.
// Details in the LICENSE file.

package trello

import (
	"fmt"
	"time"
)

type Board struct {
	client         *Client
	ID             string `json:"id"`
	Name           string `json:"name"`
	Desc           string `json:"desc"`
	Closed         bool   `json:"closed"`
	IdOrganization string `json:"idOrganization"`
	Pinned         bool   `json:"pinned"`
	Url            string `json:"url"`
	ShortUrl       string `json:"shortUrl"`
	Prefs          struct {
		PermissionLevel       string            `json:"permissionLevel"`
		Voting                string            `json:"voting"`
		Comments              string            `json:"comments"`
		Invitations           string            `json:"invitations"`
		SelfJoin              bool              `json:"selfjoin"`
		CardCovers            bool              `json:"cardCovers"`
		CardAging             string            `json:"cardAging"`
		CalendarFeedEnabled   bool              `json:"calendarFeedEnabled"`
		Background            string            `json:"background"`
		BackgroundColor       string            `json:"backgroundColor"`
		BackgroundImage       string            `json:"backgroundImage"`
		BackgroundImageScaled []BackgroundImage `json:"backgroundImageScaled"`
		BackgroundTile        bool              `json:"backgroundTile"`
		BackgroundBrightness  string            `json:"backgroundBrightness"`
		CanBePublic           bool              `json:"canBePublic"`
		CanBeOrg              bool              `json:"canBeOrg"`
		CanBePrivate          bool              `json:"canBePrivate"`
		CanInvite             bool              `json:"canInvite"`
	} `json:"prefs"`
	LabelNames struct {
		Black  string `json:"black,omitempty"`
		Blue   string `json:"blue,omitempty"`
		Green  string `json:"green,omitempty"`
		Lime   string `json:"lime,omitempty"`
		Orange string `json:"orange,omitempty"`
		Pink   string `json:"pink,omitempty"`
		Purple string `json:"purple,omitempty"`
		Red    string `json:"red,omitempty"`
		Sky    string `json:"sky,omitempty"`
		Yellow string `json:"yellow,omitempty"`
	} `json:"labelNames"`
	Lists        []*List      `json:"lists"`
	Actions      []*Action    `json:"actions"`
	Organization Organization `json:"organization"`
}

type BackgroundImage struct {
	Width  int    `json:"width"`
	Height int    `json:"height"`
	URL    string `json:"url"`
}

func (b *Board) CreatedAt() time.Time {
	t, _ := IDToTime(b.ID)
	return t
}

/**
 * Board retrieves a Trello board by its ID.
 */
func (c *Client) GetBoard(boardID string, args Arguments) (board *Board, err error) {
	path := fmt.Sprintf("boards/%s", boardID)
	err = c.Get(path, args, &board)
	if board != nil {
		board.client = c
	}
	return
}

func (m *Member) GetBoards(args Arguments) (boards []*Board, err error) {
	path := fmt.Sprintf("members/%s/boards", m.ID)
	err = m.client.Get(path, args, &boards)
	for i := range boards {
		boards[i].client = m.client
	}
	return
}
<|endoftext|>