<commit_before>package topic

import (
	"errors"
	"fmt"
	"socialapi/models"
	"strings"

	"github.com/koding/bongo"
	"github.com/koding/logging"
	"github.com/streadway/amqp"
)

// Controller holds the required parameters for moderation async operations
type Controller struct {
	log logging.Logger
}

// NewController creates a handler for consuming async operations of moderation
func NewController(log logging.Logger) *Controller {
	return &Controller{
		log: log,
	}
}

// DefaultErrHandler handles the errors, we dont need to ack a message, continue
// to the success
func (c *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	c.log.Error("an error occurred putting message back to queue", err)
	delivery.Nack(false, true)
	return false
}

// Create moves the participants and the messages of a leaf channel to the root
// channel, it may remove the messages if the option is passed
func (c *Controller) Create(cl *models.ChannelLink) error {
	return c.process(cl)
}

// Delete just here for referance
func (c *Controller) Delete(cl *models.ChannelLink) error {
	return nil
}

// Blacklist moves the participants and the messages of a leaf channel to the
// root channel, it may remove the messages if the option is passed
func (c *Controller) Blacklist(cl *models.ChannelLink) error {
	return c.process(cl)
}

func (c *Controller) process(cl *models.ChannelLink) error {
	if err := c.validateRequest(cl); err != nil {
		c.log.Error("Validation failed for creating link; skipping, err: %s ", err.Error())
		return nil
	}

	if err := c.moveParticipants(cl); err != nil {
		c.log.Error("Error while processing channel participants, err: %s ", err.Error())
		return err
	}

	if err := c.moveMessages(cl); err != nil {
		c.log.Error("Error while moving messages, err: %s ", err.Error())
		return err
	}

	if err := c.updateInitialChannelIds(cl); err != nil {
		c.log.Error("Error while updating the initial channel ids, err: %s ", err.Error())
		return err
	}

	return nil
}

// Unlink is not implemented yet
func (c *Controller) UnLink(cl *models.ChannelLink) error {
	if err := c.validateRequest(cl); err != nil {
		c.log.Error("Validation failed for creating link; skipping, err: %s ", err.Error())
		return nil
	}

	c.log.Info("nothing to do for unlink for now")

	return nil
}

func (c *Controller) validateRequest(cl *models.ChannelLink) error {
	if cl == nil {
		return errors.New("channel link is not set (nil)")
	}

	if cl.Id == 0 {
		return errors.New("id is not set")
	}

	if cl.RootId == 0 {
		return errors.New("root id is not set")
	}

	if cl.LeafId == 0 {
		return errors.New("leaf id is not set")
	}

	return nil
}

// moveParticipants moves the participants of the leaf node to the root node it
// doesnt update the lastSeenAt time of the participants on channels if the user
// already a participant of the root node, just removes the participation from
// leaf node, if user only participant of the leaf node updates the current
// participation with the new root node's channel id, it is always safe to
// return error whever we encounter one
func (c *Controller) moveParticipants(cl *models.ChannelLink) error {
	var processCount = 100
	var erroredChannelParticipants []models.ChannelParticipant

	for {

		var channelParticipants []models.ChannelParticipant

		m := models.ChannelParticipant{}
		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Model(m).
			Table(m.BongoName()).
			Unscoped().
			Limit(processCount).
			Where("channel_id = ?", cl.LeafId).
			Find(&channelParticipants).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel participants, no need to continue anymore
		if len(channelParticipants) == 0 {
			c.log.Info("doesnt have any participants to process")
			break
		}

		for i, channelParticipant := range channelParticipants {
			// fetch the root channel's participant, if exists
			rootParticipation := models.NewChannelParticipant()
			rootParticipation.ChannelId = cl.RootId
			rootParticipation.AccountId = channelParticipant.AccountId
			err := rootParticipation.FetchParticipant()
			if err != nil && err != bongo.RecordNotFound {
				// dont append to erroredChannelParticipants because we need the
				// data here
				return err
			}

			// if the user is not the participant of root node, update the
			// current ChannelParticipant record with the root node's channel id
			if err == bongo.RecordNotFound {
				channelParticipant.ChannelId = cl.RootId
				if err := channelParticipant.Update(); err != nil {
					c.log.Error("Err while swapping channel ids %s", err.Error())
					erroredChannelParticipants = append(erroredChannelParticipants, channelParticipants[i])
				}

			} else {
				// if we get here it means the user is a member of the new root node
				// if the user is already participant of root channel, delete the
				// leaf node participation

				if err := bongo.B.
					Unscoped().
					Table(m.BongoName()).
					Delete(channelParticipant).
					Error; err != nil {
					c.log.Error("Err while deleting the channel participation %s", err.Error())
					erroredChannelParticipants = append(erroredChannelParticipants, channelParticipants[i])
					continue
				}
			}

			// send deleted event
			bongo.B.AfterDelete(channelParticipants[i])
		}
	}

	// if error happens, return it, next time it will be re-tried
	if len(erroredChannelParticipants) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredChannelParticipants))
	}

	return nil
}

// moveMessages moves the leaf channel's messages to the root node, while moving
// them first iterates over the chanel_message_list and process them one by one,
// if the message already member of the root channel, it doesnt add again, and
// removes it from leaf node immediately. Secondly updates the message's body,
// if the channel is blacklisted removes the hashbang(#) eg: #js -> js, if the
// channel is just linked replaces only the occurences with a hashbang eg: #js
// -> #javascript. At the end, if the message is directly posted to the linked
// channel, it has InitialChannelId, we should replace it with the parent's
// channel id
func (c *Controller) moveMessages(cl *models.ChannelLink) error {
	var processCount = 100

	var erroredMessageLists []models.ChannelMessageList

	rootChannel, err := models.ChannelById(cl.RootId)
	if err != nil {
		c.log.Critical("requested root channel doesnt exist. ChannelId: %d", cl.RootId)
		return nil
	}

	leafChannel, err := models.ChannelById(cl.LeafId)
	if err != nil {
		c.log.Critical("requested leaf channel doesnt exist. ChannelId: %d", cl.LeafId)
		return nil
	}

	// change what
	toBeReplacedSourceString := "#" + leafChannel.Name
	// with what
	toBeReplacedTargetString := "#" + rootChannel.Name

	// if the new root channel is our group channel, than do not replace the topics with group name :)
	if cl.DeleteMessages || rootChannel.TypeConstant == models.Channel_TYPE_GROUP {
		toBeReplacedTargetString = leafChannel.Name
	}

	m := models.ChannelMessageList{}
	for {
		var messageLists []models.ChannelMessageList

		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Unscoped().
			Model(m).
			Table(m.TableName()).
			Limit(processCount).
			Where("channel_id = ?", cl.LeafId).
			Find(&messageLists).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel messages. or no message exits
		if len(messageLists) == 0 {
			break
		}

		for i, messageList := range messageLists {
			// fetch the regarding message
			cm := models.NewChannelMessage()
			// message can be a deleted one
			err := cm.UnscopedById(messageList.MessageId)
			if err != nil && err != bongo.RecordNotFound {
				return err
			}

			if err == bongo.RecordNotFound {
				c.log.Critical("we do have inconsistent data in our db, message with id: %d doesnt exist in channel_message table but we have referance in our channel_message_list table id: %d", messageList.MessageId, messageList.Id)
				continue
			}

			// if deletemessage option is passed delete the messages
			if cl.DeleteMessages {
				err := cm.DeleteMessageAndDependencies(true)
				if err != nil {
					c.log.Error("Err while deleting the mesage %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
				}
				continue
			}

			isInRootChannel, _ := models.NewChannelMessageList().IsInChannel(cm.Id, rootChannel.Id)
			if isInRootChannel {

				// we are deleting the leaf with an unscoped because we dont need the
				// data in our db anymore
				if err := bongo.B.
					Unscoped().
					Model(m).
					Table(m.TableName()).
					Delete(messageList).
					Error; err != nil {
					c.log.Error("Err while deleting the channel message list %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
				}

				// do not forget to send the event, other workers may need it, ps: algoliaconnecter needs it
				go bongo.B.AfterDelete(messageList)

			} else {
				// update the message itself, without callbacks
				if err := bongo.B.
					Unscoped().
					Table(m.TableName()).
					Model(&messageList).
					UpdateColumn("channel_id", cl.RootId).
					Error; err != nil {
					c.log.Error("Err while updating the mesage %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
					continue
				}

				// do not forget to send the event, other workers may need it, ps: algoliaconnecter needs it
				go bongo.B.AfterCreate(messageList)
			}

			// update message here

			// just a little shortcut here, update the initial channel id
			if cm.InitialChannelId == leafChannel.Id {
				cm.InitialChannelId = rootChannel.Id
			}

			// replace all occurences of the leaf node hashbangs with the root
			// nodes. We _can't_ determine if the multiple occurences of the
			// same `Name` constitues a meaningful sentence - yes we can, but it
			// is not feasible for now...
			cm.Body = processWithNewTag(cm.Body, toBeReplacedSourceString, toBeReplacedTargetString)

			// update the message itself
			if err := bongo.B.
				Unscoped().
				Table(cm.TableName()).
				Model(*cm). // should not be a pointer, why? dont ask me for now
				Update(cm).Error; err != nil {
				c.log.Error("Err while updating the mesage %s", err.Error())
				erroredMessageLists = append(erroredMessageLists, messageLists[i])
				continue
			}
			cm.AfterUpdate() // do not forget to send updated event
		}
	}

	if len(erroredMessageLists) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredMessageLists))
	}

	return nil
}

// updateInitialChannelIds updates the message's initial channel id properties,
// we are already updating the channel_message's initial channel id while
// iterating over the messages but there can be some messages that are created
// in that channel initially, but then can be moved to other channels
func (c *Controller) updateInitialChannelIds(cl *models.ChannelLink) error {
	var processCount = 100

	var erroredMessages []models.ChannelMessage

	for {
		var messages []models.ChannelMessage

		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Model(models.ChannelMessage{}).
			Unscoped().
			Limit(processCount).
			Where("initial_channel_id = ?", cl.LeafId).
			Find(&messages).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel messages. or no message exits
		if len(messages) == 0 {
			break
		}

		for i, message := range messages {
			// fetch the regarding message
			cm := models.NewChannelMessage()
			err := cm.UnscopedById(message.Id)
			if err != nil && err != bongo.RecordNotFound {
				return err
			}

			if err == bongo.RecordNotFound {
				// message can be deleted in the mean time
				continue
			}

			cm.InitialChannelId = cl.RootId

			// update the message itself. Used bongo.Update because
			// ChannelMessage's Update method is overwritten
			if err := bongo.B.Update(cm); err != nil {
				c.log.Error("Err while updating the mesage %s", err.Error())
				erroredMessages = append(erroredMessages, messages[i])
			}
		}
	}

	if len(erroredMessages) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredMessages))
	}

	return nil
}

<commit_msg>Socialapi: better renaming for leaf and root channels<commit_after>package topic

import (
	"errors"
	"fmt"
	"socialapi/models"
	"strings"

	"github.com/koding/bongo"
	"github.com/koding/logging"
	"github.com/streadway/amqp"
)

// Controller holds the required parameters for moderation async operations
type Controller struct {
	log logging.Logger
}

// NewController creates a handler for consuming async operations of moderation
func NewController(log logging.Logger) *Controller {
	return &Controller{
		log: log,
	}
}

// DefaultErrHandler handles the errors, we dont need to ack a message, continue
// to the success
func (c *Controller) DefaultErrHandler(delivery amqp.Delivery, err error) bool {
	c.log.Error("an error occurred putting message back to queue", err)
	delivery.Nack(false, true)
	return false
}

// Create moves the participants and the messages of a leaf channel to the root
// channel, it may remove the messages if the option is passed
func (c *Controller) Create(cl *models.ChannelLink) error {
	return c.process(cl)
}

// Delete just here for referance
func (c *Controller) Delete(cl *models.ChannelLink) error {
	return nil
}

// Blacklist moves the participants and the messages of a leaf channel to the
// root channel, it may remove the messages if the option is passed
func (c *Controller) Blacklist(cl *models.ChannelLink) error {
	return c.process(cl)
}

func (c *Controller) process(cl *models.ChannelLink) error {
	if err := c.validateRequest(cl); err != nil {
		c.log.Error("Validation failed for creating link; skipping, err: %s ", err.Error())
		return nil
	}

	if err := c.moveParticipants(cl); err != nil {
		c.log.Error("Error while processing channel participants, err: %s ", err.Error())
		return err
	}

	if err := c.moveMessages(cl); err != nil {
		c.log.Error("Error while moving messages, err: %s ", err.Error())
		return err
	}

	if err := c.updateInitialChannelIds(cl); err != nil {
		c.log.Error("Error while updating the initial channel ids, err: %s ", err.Error())
		return err
	}

	return nil
}

// Unlink is not implemented yet
func (c *Controller) UnLink(cl *models.ChannelLink) error {
	if err := c.validateRequest(cl); err != nil {
		c.log.Error("Validation failed for creating link; skipping, err: %s ", err.Error())
		return nil
	}

	c.log.Info("nothing to do for unlink for now")

	return nil
}

func (c *Controller) validateRequest(cl *models.ChannelLink) error {
	if cl == nil {
		return errors.New("channel link is not set (nil)")
	}

	if cl.Id == 0 {
		return errors.New("id is not set")
	}

	if cl.RootId == 0 {
		return errors.New("root id is not set")
	}

	if cl.LeafId == 0 {
		return errors.New("leaf id is not set")
	}

	return nil
}

// moveParticipants moves the participants of the leaf node to the root node it
// doesnt update the lastSeenAt time of the participants on channels if the user
// already a participant of the root node, just removes the participation from
// leaf node, if user only participant of the leaf node updates the current
// participation with the new root node's channel id, it is always safe to
// return error whever we encounter one
func (c *Controller) moveParticipants(cl *models.ChannelLink) error {
	var processCount = 100
	var erroredChannelParticipants []models.ChannelParticipant

	for {

		var channelParticipants []models.ChannelParticipant

		m := models.ChannelParticipant{}
		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Model(m).
			Table(m.BongoName()).
			Unscoped().
			Limit(processCount).
			Where("channel_id = ?", cl.LeafId).
			Find(&channelParticipants).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel participants, no need to continue anymore
		if len(channelParticipants) == 0 {
			c.log.Info("doesnt have any participants to process")
			break
		}

		for i, channelParticipant := range channelParticipants {
			// fetch the root channel's participant, if exists
			rootParticipation := models.NewChannelParticipant()
			rootParticipation.ChannelId = cl.RootId
			rootParticipation.AccountId = channelParticipant.AccountId
			err := rootParticipation.FetchParticipant()
			if err != nil && err != bongo.RecordNotFound {
				// dont append to erroredChannelParticipants because we need the
				// data here
				return err
			}

			// if the user is not the participant of root node, update the
			// current ChannelParticipant record with the root node's channel id
			if err == bongo.RecordNotFound {
				channelParticipant.ChannelId = cl.RootId
				if err := channelParticipant.Update(); err != nil {
					c.log.Error("Err while swapping channel ids %s", err.Error())
					erroredChannelParticipants = append(erroredChannelParticipants, channelParticipants[i])
				}

			} else {
				// if we get here it means the user is a member of the new root node
				// if the user is already participant of root channel, delete the
				// leaf node participation

				if err := bongo.B.
					Unscoped().
					Table(m.BongoName()).
					Delete(channelParticipant).
					Error; err != nil {
					c.log.Error("Err while deleting the channel participation %s", err.Error())
					erroredChannelParticipants = append(erroredChannelParticipants, channelParticipants[i])
					continue
				}
			}

			// send deleted event
			bongo.B.AfterDelete(channelParticipants[i])
		}
	}

	// if error happens, return it, next time it will be re-tried
	if len(erroredChannelParticipants) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredChannelParticipants))
	}

	return nil
}

// moveMessages moves the leaf channel's messages to the root node, while moving
// them first iterates over the chanel_message_list and process them one by one,
// if the message already member of the root channel, it doesnt add again, and
// removes it from leaf node immediately. Secondly updates the message's body,
// if the channel is blacklisted removes the hashbang(#) eg: #js -> js, if the
// channel is just linked replaces only the occurences with a hashbang eg: #js
// -> #javascript. At the end, if the message is directly posted to the linked
// channel, it has InitialChannelId, we should replace it with the parent's
// channel id
func (c *Controller) moveMessages(cl *models.ChannelLink) error {
	var processCount = 100

	var erroredMessageLists []models.ChannelMessageList

	rootChannel, err := models.ChannelById(cl.RootId)
	if err != nil {
		c.log.Critical("requested root channel doesnt exist. ChannelId: %d", cl.RootId)
		return nil
	}

	leafChannel, err := models.ChannelById(cl.LeafId)
	if err != nil {
		c.log.Critical("requested leaf channel doesnt exist. ChannelId: %d", cl.LeafId)
		return nil
	}

	// change what
	toBeReplacedSourceString := "#" + leafChannel.Name
	// with what
	toBeReplacedTargetString := "#" + rootChannel.Name

	// if the new root channel is our group channel, than do not replace the topics with group name :)
	if cl.DeleteMessages || rootChannel.TypeConstant == models.Channel_TYPE_GROUP {
		toBeReplacedTargetString = leafChannel.Name
	}

	m := models.ChannelMessageList{}
	for {
		var messageLists []models.ChannelMessageList

		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Unscoped().
			Model(m).
			Table(m.TableName()).
			Limit(processCount).
			Where("channel_id = ?", cl.LeafId).
			Find(&messageLists).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel messages. or no message exits
		if len(messageLists) == 0 {
			break
		}

		for i, messageList := range messageLists {
			// fetch the regarding message
			cm := models.NewChannelMessage()
			// message can be a deleted one
			err := cm.UnscopedById(messageList.MessageId)
			if err != nil && err != bongo.RecordNotFound {
				return err
			}

			if err == bongo.RecordNotFound {
				c.log.Critical("we do have inconsistent data in our db, message with id: %d doesnt exist in channel_message table but we have referance in our channel_message_list table id: %d", messageList.MessageId, messageList.Id)
				continue
			}

			// if deletemessage option is passed delete the messages
			if cl.DeleteMessages {
				err := cm.DeleteMessageAndDependencies(true)
				if err != nil {
					c.log.Error("Err while deleting the mesage %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
				}
				continue
			}

			isInRootChannel, _ := models.NewChannelMessageList().IsInChannel(cm.Id, rootChannel.Id)
			if isInRootChannel {

				// we are deleting the leaf with an unscoped because we dont need the
				// data in our db anymore
				if err := bongo.B.
					Unscoped().
					Model(m).
					Table(m.TableName()).
					Delete(messageList).
					Error; err != nil {
					c.log.Error("Err while deleting the channel message list %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
				}

				// do not forget to send the event, other workers may need it, ps: algoliaconnecter needs it
				go bongo.B.AfterDelete(messageList)

			} else {
				// update the message itself, without callbacks
				if err := bongo.B.
					Unscoped().
					Table(m.TableName()).
					Model(&messageList).
					UpdateColumn("channel_id", cl.RootId).
					Error; err != nil {
					c.log.Error("Err while updating the mesage %s", err.Error())
					erroredMessageLists = append(erroredMessageLists, messageLists[i])
					continue
				}

				// do not forget to send the event, other workers may need it, ps: algoliaconnecter needs it
				go bongo.B.AfterCreate(messageList)
			}

			// update message here

			// just a little shortcut here, update the initial channel id
			if cm.InitialChannelId == leafChannel.Id {
				cm.InitialChannelId = rootChannel.Id
			}

			// replace all occurences of the leaf node hashbangs with the root
			// nodes. We _can't_ determine if the multiple occurences of the
			// same `Name` constitues a meaningful sentence - yes we can, but it
			// is not feasible for now...
			cm.Body = processWithNewTag(cm.Body, toBeReplacedSourceString, toBeReplacedTargetString)

			// update the message itself
			if err := bongo.B.
				Unscoped().
				Table(cm.TableName()).
				Model(*cm). // should not be a pointer, why? dont ask me for now
				Update(cm).Error; err != nil {
				c.log.Error("Err while updating the mesage %s", err.Error())
				erroredMessageLists = append(erroredMessageLists, messageLists[i])
				continue
			}
			cm.AfterUpdate() // do not forget to send updated event
		}
	}

	if len(erroredMessageLists) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredMessageLists))
	}

	return nil
}

// updateInitialChannelIds updates the message's initial channel id properties,
// we are already updating the channel_message's initial channel id while
// iterating over the messages but there can be some messages that are created
// in that channel initially, but then can be moved to other channels
func (c *Controller) updateInitialChannelIds(cl *models.ChannelLink) error {
	var processCount = 100

	var erroredMessages []models.ChannelMessage

	for {
		var messages []models.ChannelMessage

		// fetch all records, even deleted ones, because we are not gonna need
		// them anymore
		err := bongo.B.DB.
			Model(models.ChannelMessage{}).
			Unscoped().
			Limit(processCount).
			Where("initial_channel_id = ?", cl.LeafId).
			Find(&messages).Error

		// if we encounter an error do not continue, if we cant find any
		// result, it can be excluded from the error case, because since we
		// will not be able to process any message system will return
		if err != nil && err != bongo.RecordNotFound {
			return err
		}

		// we processed all channel messages. or no message exits
		if len(messages) == 0 {
			break
		}

		for i, message := range messages {
			// fetch the regarding message
			cm := models.NewChannelMessage()
			err := cm.UnscopedById(message.Id)
			if err != nil && err != bongo.RecordNotFound {
				return err
			}

			if err == bongo.RecordNotFound {
				// message can be deleted in the mean time
				continue
			}

			cm.InitialChannelId = cl.RootId

			// update the message itself. Used bongo.Update because
			// ChannelMessage's Update method is overwritten
			if err := bongo.B.Update(cm); err != nil {
				c.log.Error("Err while updating the mesage %s", err.Error())
				erroredMessages = append(erroredMessages, messages[i])
			}
		}
	}

	if len(erroredMessages) != 0 {
		return errors.New(fmt.Sprintf("some errors: %v", erroredMessages))
	}

	return nil
}

func processWithNewTag(body, leaf, root string) string {
	// replace all occurences of the leaf node hashbangs with the root
	// nodes. We _can't_ determine if the multiple occurences of the
	// same `Name` constitues a meaningful sentence - yes we can, but it
	// is not feasible for now...
	body = strings.Replace(body, leaf, root, -1)

	// remove multiple consecutive occurrences of the same tag, if exists
	splittedBody := strings.Split(body, root)
	modifiedBody := make([]string, 0)

	for i := 0; i < len(splittedBody); i++ {
		r := splittedBody[i]
		if r == "" || r == " " {
			if i == 0 || i == len(splittedBody)-1 {
				// if we dont have  any previous or next, add it
				modifiedBody = append(modifiedBody, r)
			}
		} else {
			modifiedBody = append(modifiedBody, r)
		}
	}

	return strings.Join(modifiedBody, root)
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package api

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"

	"github.com/vmware/harbor/src/common/api"
	"github.com/vmware/harbor/src/common/dao"
	"github.com/vmware/harbor/src/common/models"
	"github.com/vmware/harbor/src/common/utils"
	"github.com/vmware/harbor/src/common/utils/log"
	"github.com/vmware/harbor/src/common/utils/registry"
	"github.com/vmware/harbor/src/common/utils/registry/auth"
	registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
	"github.com/vmware/harbor/src/ui/config"
)

// TargetAPI handles request to /api/targets/ping /api/targets/{}
type TargetAPI struct {
	api.BaseAPI
	secretKey string
}

// Prepare validates the user
func (t *TargetAPI) Prepare() {
	var err error
	t.secretKey, err = config.SecretKey()
	if err != nil {
		log.Errorf("failed to get secret key: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	userID := t.ValidateUser()
	isSysAdmin, err := dao.IsAdminRole(userID)
	if err != nil {
		log.Errorf("error occurred in IsAdminRole: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if !isSysAdmin {
		t.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
	}
}

func (t *TargetAPI) ping(endpoint, username, password string) {
	verify, err := config.VerifyRemoteCert()
	if err != nil {
		log.Errorf("failed to check whether insecure or not: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
	registry, err := newRegistryClient(endpoint, !verify, username, password,
		"", "", "")
	if err != nil {
		// timeout, dns resolve error, connection refused, etc.
		if urlErr, ok := err.(*url.Error); ok {
			if netErr, ok := urlErr.Err.(net.Error); ok {
				t.CustomAbort(http.StatusBadRequest, netErr.Error())
			}

			t.CustomAbort(http.StatusBadRequest, urlErr.Error())
		}

		log.Errorf("failed to create registry client: %#v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if err = registry.Ping(); err != nil {
		if regErr, ok := err.(*registry_error.Error); ok {
			t.CustomAbort(regErr.StatusCode, regErr.Detail)
		}

		log.Errorf("failed to ping registry %s: %v", registry.Endpoint.String(), err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

// PingByID ping target by ID
func (t *TargetAPI) PingByID() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
	if target == nil {
		t.CustomAbort(http.StatusNotFound, fmt.Sprintf("target %d not found", id))
	}

	endpoint := target.URL
	username := target.Username
	password := target.Password
	if len(password) != 0 {
		password, err = utils.ReversibleDecrypt(password, t.secretKey)
		if err != nil {
			log.Errorf("failed to decrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}
	t.ping(endpoint, username, password)
}

// Ping validates whether the target is reachable and whether the credential is valid
func (t *TargetAPI) Ping() {
	req := struct {
		Endpoint string `json:"endpoint"`
		Username string `json:"username"`
		Password string `json:"password"`
	}{}
	t.DecodeJSONReq(&req)

	if len(req.Endpoint) == 0 {
		t.CustomAbort(http.StatusBadRequest, "endpoint is required")
	}

	t.ping(req.Endpoint, req.Username, req.Password)
}

// Get ...
func (t *TargetAPI) Get() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	target.Password = ""

	t.Data["json"] = target
	t.ServeJSON()
}

// List ...
func (t *TargetAPI) List() {
	name := t.GetString("name")
	targets, err := dao.FilterRepTargets(name)
	if err != nil {
		log.Errorf("failed to filter targets %s: %v", name, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	for _, target := range targets {
		target.Password = ""
	}

	t.Data["json"] = targets
	t.ServeJSON()
	return
}

// Post ...
func (t *TargetAPI) Post() {
	target := &models.RepTarget{}
	t.DecodeJSONReqAndValidate(target)

	ta, err := dao.GetRepTargetByName(target.Name)
	if err != nil {
		log.Errorf("failed to get target %s: %v", target.Name, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if ta != nil {
		t.CustomAbort(http.StatusConflict, "name is already used")
	}

	ta, err = dao.GetRepTargetByEndpoint(target.URL)
	if err != nil {
		log.Errorf("failed to get target [ %s ]: %v", target.URL, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if ta != nil {
		t.CustomAbort(http.StatusConflict, fmt.Sprintf("the target whose endpoint is %s already exists", target.URL))
	}

	if len(target.Password) != 0 {
		target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
		if err != nil {
			log.Errorf("failed to encrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}

	id, err := dao.AddRepTarget(*target)
	if err != nil {
		log.Errorf("failed to add target: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	t.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
}

// Put ...
func (t *TargetAPI) Put() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	hasEnabledPolicy := false
	for _, policy := range policies {
		if policy.Enabled == 1 {
			hasEnabledPolicy = true
			break
		}
	}

	if hasEnabledPolicy {
		t.CustomAbort(http.StatusBadRequest, "the target is associated with policy which is enabled")
	}

	req := struct {
		Name     *string `json:"name"`
		Endpoint *string `json:"endpoint"`
		Username *string `json:"username"`
		Password *string `json:"password"`
	}{}
	t.DecodeJSONReq(&req)

	originalName := target.Name
	originalURL := target.URL

	if req.Name != nil {
		target.Name = *req.Name
	}
	if req.Endpoint != nil {
		target.URL = *req.Endpoint
	}
	if req.Username != nil {
		target.Username = *req.Username
	}
	if req.Password != nil {
		target.Password = *req.Password
	}

	t.Validate(target)

	if target.Name != originalName {
		ta, err := dao.GetRepTargetByName(target.Name)
		if err != nil {
			log.Errorf("failed to get target %s: %v", target.Name, err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if ta != nil {
			t.CustomAbort(http.StatusConflict, "name is already used")
		}
	}

	if target.URL != originalURL {
		ta, err := dao.GetRepTargetByEndpoint(target.URL)
		if err != nil {
			log.Errorf("failed to get target [ %s ]: %v", target.URL, err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if ta != nil {
			t.CustomAbort(http.StatusConflict, fmt.Sprintf("the target whose endpoint is %s already exists", target.URL))
		}
	}

	if len(target.Password) != 0 {
		target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
		if err != nil {
			log.Errorf("failed to encrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}

	if err := dao.UpdateRepTarget(*target); err != nil {
		log.Errorf("failed to update target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

// Delete ...
func (t *TargetAPI) Delete() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if len(policies) > 0 {
		t.CustomAbort(http.StatusPreconditionFailed, "the target is used by policies, can not be deleted")
	}

	if err = dao.DeleteRepTarget(id); err != nil {
		log.Errorf("failed to delete target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
	scopeActions ...string) (*registry.Registry, error) {
	credential := auth.NewBasicAuthCredential(username, password)

	authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
		"", scopeType, scopeName, scopeActions...)

	store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
	if err != nil {
		return nil, err
	}

	client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// ListPolicies ...
func (t *TargetAPI) ListPolicies() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	t.Data["json"] = policies
	t.ServeJSON()
}
<commit_msg>bug fix<commit_after>/*
   Copyright (c) 2016 VMware, Inc. All Rights Reserved.
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package api

import (
	"fmt"
	"net"
	"net/http"
	"net/url"
	"strconv"

	"github.com/vmware/harbor/src/common/api"
	"github.com/vmware/harbor/src/common/dao"
	"github.com/vmware/harbor/src/common/models"
	"github.com/vmware/harbor/src/common/utils"
	"github.com/vmware/harbor/src/common/utils/log"
	"github.com/vmware/harbor/src/common/utils/registry"
	"github.com/vmware/harbor/src/common/utils/registry/auth"
	registry_error "github.com/vmware/harbor/src/common/utils/registry/error"
	"github.com/vmware/harbor/src/ui/config"
)

// TargetAPI handles request to /api/targets/ping /api/targets/{}
type TargetAPI struct {
	api.BaseAPI
	secretKey string
}

// Prepare validates the user
func (t *TargetAPI) Prepare() {
	var err error
	t.secretKey, err = config.SecretKey()
	if err != nil {
		log.Errorf("failed to get secret key: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	userID := t.ValidateUser()
	isSysAdmin, err := dao.IsAdminRole(userID)
	if err != nil {
		log.Errorf("error occurred in IsAdminRole: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if !isSysAdmin {
		t.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden))
	}
}

func (t *TargetAPI) ping(endpoint, username, password string) {
	verify, err := config.VerifyRemoteCert()
	if err != nil {
		log.Errorf("failed to check whether insecure or not: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
	registry, err := newRegistryClient(endpoint, !verify, username, password,
		"", "", "")
	if err != nil {
		// timeout, dns resolve error, connection refused, etc.
		if urlErr, ok := err.(*url.Error); ok {
			if netErr, ok := urlErr.Err.(net.Error); ok {
				t.CustomAbort(http.StatusBadRequest, netErr.Error())
			}

			t.CustomAbort(http.StatusBadRequest, urlErr.Error())
		}

		log.Errorf("failed to create registry client: %#v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if err = registry.Ping(); err != nil {
		if regErr, ok := err.(*registry_error.Error); ok {
			t.CustomAbort(regErr.StatusCode, regErr.Detail)
		}

		log.Errorf("failed to ping registry %s: %v", registry.Endpoint.String(), err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

// PingByID ping target by ID
func (t *TargetAPI) PingByID() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
	if target == nil {
		t.CustomAbort(http.StatusNotFound, fmt.Sprintf("target %d not found", id))
	}

	endpoint := target.URL
	username := target.Username
	password := target.Password
	if len(password) != 0 {
		password, err = utils.ReversibleDecrypt(password, t.secretKey)
		if err != nil {
			log.Errorf("failed to decrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}
	t.ping(endpoint, username, password)
}

// Ping validates whether the target is reachable and whether the credential is valid
func (t *TargetAPI) Ping() {
	req := struct {
		Endpoint string `json:"endpoint"`
		Username string `json:"username"`
		Password string `json:"password"`
	}{}
	t.DecodeJSONReq(&req)

	if len(req.Endpoint) == 0 {
		t.CustomAbort(http.StatusBadRequest, "endpoint is required")
	}

	t.ping(req.Endpoint, req.Username, req.Password)
}

// Get ...
func (t *TargetAPI) Get() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	target.Password = ""

	t.Data["json"] = target
	t.ServeJSON()
}

// List ...
func (t *TargetAPI) List() {
	name := t.GetString("name")
	targets, err := dao.FilterRepTargets(name)
	if err != nil {
		log.Errorf("failed to filter targets %s: %v", name, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	for _, target := range targets {
		target.Password = ""
	}

	t.Data["json"] = targets
	t.ServeJSON()
	return
}

// Post ...
func (t *TargetAPI) Post() {
	target := &models.RepTarget{}
	t.DecodeJSONReqAndValidate(target)

	ta, err := dao.GetRepTargetByName(target.Name)
	if err != nil {
		log.Errorf("failed to get target %s: %v", target.Name, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if ta != nil {
		t.CustomAbort(http.StatusConflict, "name is already used")
	}

	ta, err = dao.GetRepTargetByEndpoint(target.URL)
	if err != nil {
		log.Errorf("failed to get target [ %s ]: %v", target.URL, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if ta != nil {
		t.CustomAbort(http.StatusConflict, fmt.Sprintf("the target whose endpoint is %s already exists", target.URL))
	}

	if len(target.Password) != 0 {
		target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
		if err != nil {
			log.Errorf("failed to encrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}

	id, err := dao.AddRepTarget(*target)
	if err != nil {
		log.Errorf("failed to add target: %v", err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	t.Redirect(http.StatusCreated, strconv.FormatInt(id, 10))
}

// Put ...
func (t *TargetAPI) Put() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	hasEnabledPolicy := false
	for _, policy := range policies {
		if policy.Enabled == 1 {
			hasEnabledPolicy = true
			break
		}
	}

	if hasEnabledPolicy {
		t.CustomAbort(http.StatusBadRequest, "the target is associated with policy which is enabled")
	}
	if len(target.Password) != 0 {
		target.Password, err = utils.ReversibleDecrypt(target.Password, t.secretKey)
		if err != nil {
			log.Errorf("failed to decrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}

	req := struct {
		Name     *string `json:"name"`
		Endpoint *string `json:"endpoint"`
		Username *string `json:"username"`
		Password *string `json:"password"`
	}{}
	t.DecodeJSONReq(&req)

	originalName := target.Name
	originalURL := target.URL

	if req.Name != nil {
		target.Name = *req.Name
	}
	if req.Endpoint != nil {
		target.URL = *req.Endpoint
	}
	if req.Username != nil {
		target.Username = *req.Username
	}
	if req.Password != nil {
		target.Password = *req.Password
	}

	t.Validate(target)

	if target.Name != originalName {
		ta, err := dao.GetRepTargetByName(target.Name)
		if err != nil {
			log.Errorf("failed to get target %s: %v", target.Name, err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if ta != nil {
			t.CustomAbort(http.StatusConflict, "name is already used")
		}
	}

	if target.URL != originalURL {
		ta, err := dao.GetRepTargetByEndpoint(target.URL)
		if err != nil {
			log.Errorf("failed to get target [ %s ]: %v", target.URL, err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}

		if ta != nil {
			t.CustomAbort(http.StatusConflict, fmt.Sprintf("the target whose endpoint is %s already exists", target.URL))
		}
	}

	if len(target.Password) != 0 {
		target.Password, err = utils.ReversibleEncrypt(target.Password, t.secretKey)
		if err != nil {
			log.Errorf("failed to encrypt password: %v", err)
			t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
		}
	}

	if err := dao.UpdateRepTarget(*target); err != nil {
		log.Errorf("failed to update target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

// Delete ...
func (t *TargetAPI) Delete() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if len(policies) > 0 {
		t.CustomAbort(http.StatusPreconditionFailed, "the target is used by policies, can not be deleted")
	}

	if err = dao.DeleteRepTarget(id); err != nil {
		log.Errorf("failed to delete target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}
}

func newRegistryClient(endpoint string, insecure bool, username, password, scopeType, scopeName string,
	scopeActions ...string) (*registry.Registry, error) {
	credential := auth.NewBasicAuthCredential(username, password)

	authorizer := auth.NewStandardTokenAuthorizer(credential, insecure,
		"", scopeType, scopeName, scopeActions...)

	store, err := auth.NewAuthorizerStore(endpoint, insecure, authorizer)
	if err != nil {
		return nil, err
	}

	client, err := registry.NewRegistryWithModifiers(endpoint, insecure, store)
	if err != nil {
		return nil, err
	}
	return client, nil
}

// ListPolicies ...
func (t *TargetAPI) ListPolicies() {
	id := t.GetIDFromURL()

	target, err := dao.GetRepTarget(id)
	if err != nil {
		log.Errorf("failed to get target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	if target == nil {
		t.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound))
	}

	policies, err := dao.GetRepPolicyByTarget(id)
	if err != nil {
		log.Errorf("failed to get policies according target %d: %v", id, err)
		t.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
	}

	t.Data["json"] = policies
	t.ServeJSON()
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package metrics

import (
	"fmt"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/miekg/dns"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	Port      = os.Getenv("PROMETHEUS_PORT")
	Path      = envOrDefault("PROMETHEUS_PATH", "/metrics")
	Namespace = envOrDefault("PROMETHEUS_NAMESPACE", "skydns")
	Subsystem = envOrDefault("PROMETHEUS_SUBSYSTEM", "skydns")

	requestCount    *prometheus.CounterVec
	requestDuration *prometheus.HistogramVec
	responseSize    *prometheus.HistogramVec
	errorCount      *prometheus.CounterVec
	cacheMiss       *prometheus.CounterVec
)

type (
	System    string
	Cause     string
	CacheType string
)

var (
	Auth    System = "auth"
	Cache   System = "cache"
	Rec     System = "recursive"
	Reverse System = "reverse"
	Stub    System = "stub"

	Nxdomain  Cause = "nxdomain"
	Nodata    Cause = "nodata"
	Truncated Cause = "truncated"
	Refused   Cause = "refused"
	Overflow  Cause = "overflow"
	Fail      Cause = "servfail"

	Response  CacheType = "response"
	Signature CacheType = "signature"
)

func defineMetrics() {
	requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_request_count_total",
		Help:      "Counter of DNS requests made.",
	}, []string{"system"})

	requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_request_duration_seconds",
		Help:      "Histogram of the time (in seconds) each request took to resolve.",
		Buckets:   append([]float64{0.001, 0.003}, prometheus.DefBuckets...),
	}, []string{"system"})

	responseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_response_size_bytes",
		Help:      "Size of the returns response in bytes.",
		Buckets: []float64{0, 512, 1024, 1500, 2048, 4096,
			8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864,
			40960, 45056, 49152, 53248, 57344, 61440, 65536,
		},
	}, []string{"system"})

	errorCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_error_count_total",
		Help:      "Counter of DNS requests resulting in an error.",
	}, []string{"system", "cause"})

	cacheMiss = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_cachemiss_count_total",
		Help:      "Counter of DNS requests that result in a cache miss.",
	}, []string{"cache"})
}

// Metrics registers the DNS metrics to Prometheus, and starts the internal metrics
// server if the environment variable PROMETHEUS_PORT is set.
func Metrics() error {
	// We do this in a function instead of using var + init(), because we want to
	// able to set Namespace and/or Subsystem.
	if Port == "" {
		return nil
	}

	_, err := strconv.Atoi(Port)
	if err != nil {
		fmt.Errorf("bad port for prometheus: %s", Port)
	}

	defineMetrics()

	prometheus.MustRegister(requestCount)
	prometheus.MustRegister(requestDuration)
	prometheus.MustRegister(responseSize)
	prometheus.MustRegister(errorCount)
	prometheus.MustRegister(cacheMiss)

	http.Handle(Path, prometheus.Handler())
	go func() {
		fmt.Errorf("%s", http.ListenAndServe(":"+Port, nil))
	}()
	return nil
}

func ReportDuration(resp *dns.Msg, start time.Time, sys System) {
	if requestDuration == nil || responseSize == nil {
		return
	}

	rlen := float64(0)
	if resp != nil {
		rlen = float64(resp.Len())
	}
	requestDuration.WithLabelValues(string(sys)).Observe(float64(time.Since(start)) / float64(time.Second))
	responseSize.WithLabelValues(string(sys)).Observe(rlen)
}

func ReportRequestCount(req *dns.Msg, sys System) {
	if requestCount == nil {
		println("NIL")
		return
	}

	requestCount.WithLabelValues(string(sys)).Inc()
}

func ReportErrorCount(resp *dns.Msg, sys System) {
	if resp == nil || errorCount == nil {
		return
	}

	if resp.Truncated {
		errorCount.WithLabelValues(string(sys), string(Truncated)).Inc()
		return
	}
	if resp.Len() > dns.MaxMsgSize {
		errorCount.WithLabelValues(string(sys), string(Overflow)).Inc()
		return
	}

	switch resp.Rcode {
	case dns.RcodeServerFailure:
		errorCount.WithLabelValues(string(sys), string(Fail)).Inc()
	case dns.RcodeRefused:
		errorCount.WithLabelValues(string(sys), string(Refused)).Inc()
	case dns.RcodeNameError:
		errorCount.WithLabelValues(string(sys), string(Nxdomain)).Inc()
		// nodata ??
	}

}

func ReportCacheMiss(ca CacheType) {
	if cacheMiss == nil {
		return
	}
	cacheMiss.WithLabelValues(string(ca)).Inc()
}

func envOrDefault(env, def string) string {
	e := os.Getenv(env)
	if e != "" {
		return e
	}
	return def
}
<commit_msg>Remove println<commit_after>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package metrics

import (
	"fmt"
	"net/http"
	"os"
	"strconv"
	"time"

	"github.com/miekg/dns"
	"github.com/prometheus/client_golang/prometheus"
)

var (
	Port      = os.Getenv("PROMETHEUS_PORT")
	Path      = envOrDefault("PROMETHEUS_PATH", "/metrics")
	Namespace = envOrDefault("PROMETHEUS_NAMESPACE", "skydns")
	Subsystem = envOrDefault("PROMETHEUS_SUBSYSTEM", "skydns")

	requestCount    *prometheus.CounterVec
	requestDuration *prometheus.HistogramVec
	responseSize    *prometheus.HistogramVec
	errorCount      *prometheus.CounterVec
	cacheMiss       *prometheus.CounterVec
)

type (
	System    string
	Cause     string
	CacheType string
)

var (
	Auth    System = "auth"
	Cache   System = "cache"
	Rec     System = "recursive"
	Reverse System = "reverse"
	Stub    System = "stub"

	Nxdomain  Cause = "nxdomain"
	Nodata    Cause = "nodata"
	Truncated Cause = "truncated"
	Refused   Cause = "refused"
	Overflow  Cause = "overflow"
	Fail      Cause = "servfail"

	Response  CacheType = "response"
	Signature CacheType = "signature"
)

func defineMetrics() {
	requestCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_request_count_total",
		Help:      "Counter of DNS requests made.",
	}, []string{"system"})

	requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_request_duration_seconds",
		Help:      "Histogram of the time (in seconds) each request took to resolve.",
		Buckets:   append([]float64{0.001, 0.003}, prometheus.DefBuckets...),
	}, []string{"system"})

	responseSize = prometheus.NewHistogramVec(prometheus.HistogramOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_response_size_bytes",
		Help:      "Size of the returns response in bytes.",
		Buckets: []float64{0, 512, 1024, 1500, 2048, 4096,
			8192, 12288, 16384, 20480, 24576, 28672, 32768, 36864,
			40960, 45056, 49152, 53248, 57344, 61440, 65536,
		},
	}, []string{"system"})

	errorCount = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_error_count_total",
		Help:      "Counter of DNS requests resulting in an error.",
	}, []string{"system", "cause"})

	cacheMiss = prometheus.NewCounterVec(prometheus.CounterOpts{
		Namespace: Namespace,
		Subsystem: Subsystem,
		Name:      "dns_cachemiss_count_total",
		Help:      "Counter of DNS requests that result in a cache miss.",
	}, []string{"cache"})
}

// Metrics registers the DNS metrics to Prometheus, and starts the internal metrics
// server if the environment variable PROMETHEUS_PORT is set.
func Metrics() error {
	// We do this in a function instead of using var + init(), because we want to
	// able to set Namespace and/or Subsystem.
	if Port == "" {
		return nil
	}

	_, err := strconv.Atoi(Port)
	if err != nil {
		fmt.Errorf("bad port for prometheus: %s", Port)
	}

	defineMetrics()

	prometheus.MustRegister(requestCount)
	prometheus.MustRegister(requestDuration)
	prometheus.MustRegister(responseSize)
	prometheus.MustRegister(errorCount)
	prometheus.MustRegister(cacheMiss)

	http.Handle(Path, prometheus.Handler())
	go func() {
		fmt.Errorf("%s", http.ListenAndServe(":"+Port, nil))
	}()
	return nil
}

func ReportDuration(resp *dns.Msg, start time.Time, sys System) {
	if requestDuration == nil || responseSize == nil {
		return
	}

	rlen := float64(0)
	if resp != nil {
		rlen = float64(resp.Len())
	}
	requestDuration.WithLabelValues(string(sys)).Observe(float64(time.Since(start)) / float64(time.Second))
	responseSize.WithLabelValues(string(sys)).Observe(rlen)
}

func ReportRequestCount(req *dns.Msg, sys System) {
	if requestCount == nil {
		return
	}

	requestCount.WithLabelValues(string(sys)).Inc()
}

func ReportErrorCount(resp *dns.Msg, sys System) {
	if resp == nil || errorCount == nil {
		return
	}

	if resp.Truncated {
		errorCount.WithLabelValues(string(sys), string(Truncated)).Inc()
		return
	}
	if resp.Len() > dns.MaxMsgSize {
		errorCount.WithLabelValues(string(sys), string(Overflow)).Inc()
		return
	}

	switch resp.Rcode {
	case dns.RcodeServerFailure:
		errorCount.WithLabelValues(string(sys), string(Fail)).Inc()
	case dns.RcodeRefused:
		errorCount.WithLabelValues(string(sys), string(Refused)).Inc()
	case dns.RcodeNameError:
		errorCount.WithLabelValues(string(sys), string(Nxdomain)).Inc()
		// nodata ??
	}

}

func ReportCacheMiss(ca CacheType) {
	if cacheMiss == nil {
		return
	}
	cacheMiss.WithLabelValues(string(ca)).Inc()
}

func envOrDefault(env, def string) string {
	e := os.Getenv(env)
	if e != "" {
		return e
	}
	return def
}
<|endoftext|>


<commit_before>// Package mgoutil provides various methods for working with the mgo.v2 package,
// implementing various oft-used behaviors
package mgoutil

import (
	"github.com/levenlabs/go-llog"
	"gopkg.in/mgo.v2"
)

// Index is used in MustEnsureIndexes to convey all the information needed to
// ensure a background index
//
// DEPRECATED: MustEnsureIndexes is deprecated, and therefore this type is as
// well. This will be removed at some point in the future
type Index struct {
	Name   string // human readable name for the index, only used for the log entry
	Keys   []string
	Coll   string
	Unique bool
	Sparse bool
}

// MustEnsureIndexes takes the given indexes and ensure's they are all in place.
// It will make the calls with Background:true. If any indexes fail a message
// will be logged and the process will exit
//
// DEPRECATED: Use the MustEnsureIndexes method on SessionHelper instead. This
// will be removed at some point in the future
//
// NOTE WHEN SWITCHING TO NEW METHOD: the new method does *not* automatically
// set Background: true like this one does, so be aware of that difference when
// switching over
func MustEnsureIndexes(db *mgo.Database, indexes ...Index) {
	for _, i := range indexes {
		kv := llog.KV{
			"name": i.Name,
			"keys": i.Keys,
			"coll": i.Coll,
			"db":   db.Name,
		}
		llog.Info("ensuring mongo index", kv)
		err := db.C(i.Coll).EnsureIndex(mgo.Index{
			Key:        i.Keys,
			Background: true,
			Unique:     i.Unique,
			Sparse:     i.Sparse,
		})
		if err != nil {
			kv["err"] = err
			llog.Fatal("could not ensure mongo index", kv)
		}
	}
}

// CollectionHelper is a simple helper type which makes it easy to call methods
// on a Database or Collection safely. It will handle the Copy'ing and Close'ing
// of the given Session automatically
type SessionHelper struct {
	Session *mgo.Session
	DB      string
	Coll    string
}

// WithDB handles calls the given function on an instance of the database named
// by the field in the SessionHelper. It will handle Copy'ing and Close'ing the
// Session object automatically.
func (s SessionHelper) WithDB(fn func(*mgo.Database)) {
	session := s.Session.Copy()
	defer session.Close()
	fn(session.DB(s.DB))
}

// WithColl handles calls the given function on an instance of the collection
// named by the field in the SessionHelper. It will handle Copy'ing and
// Close'ing the Session object automatically.
func (s SessionHelper) WithColl(fn func(*mgo.Collection)) {
	s.WithDB(func(db *mgo.Database) {
		fn(db.C(s.Coll))
	})
}

// MustEnsureIndexes takes the given indexes and ensure's they are all in place.
// If any indexes fail a message will be logged and the process will exit
func (s SessionHelper) MustEnsureIndexes(indexes ...mgo.Index) {
	s.WithColl(func(c *mgo.Collection) {
		for _, index := range indexes {
			kv := llog.KV{
				"key":  index.Key,
				"coll": s.Coll,
				"db":   s.DB,
			}
			llog.Info("ensuring mongo index", kv)
			if err := c.EnsureIndex(index); err != nil {
				kv["err"] = err
				llog.Fatal("could not ensure mongo index", kv)
			}
		}
	})
}
<commit_msg>Added EnsureSession<commit_after>// Package mgoutil provides various methods for working with the mgo.v2 package,
// implementing various oft-used behaviors
package mgoutil

import (
	"github.com/levenlabs/go-llog"
	"gopkg.in/mgo.v2"
	"time"
)

// Index is used in MustEnsureIndexes to convey all the information needed to
// ensure a background index
//
// DEPRECATED: MustEnsureIndexes is deprecated, and therefore this type is as
// well. This will be removed at some point in the future
type Index struct {
	Name   string // human readable name for the index, only used for the log entry
	Keys   []string
	Coll   string
	Unique bool
	Sparse bool
}

// MustEnsureIndexes takes the given indexes and ensure's they are all in place.
// It will make the calls with Background:true. If any indexes fail a message
// will be logged and the process will exit
//
// DEPRECATED: Use the MustEnsureIndexes method on SessionHelper instead. This
// will be removed at some point in the future
//
// NOTE WHEN SWITCHING TO NEW METHOD: the new method does *not* automatically
// set Background: true like this one does, so be aware of that difference when
// switching over
func MustEnsureIndexes(db *mgo.Database, indexes ...Index) {
	for _, i := range indexes {
		kv := llog.KV{
			"name": i.Name,
			"keys": i.Keys,
			"coll": i.Coll,
			"db":   db.Name,
		}
		llog.Info("ensuring mongo index", kv)
		err := db.C(i.Coll).EnsureIndex(mgo.Index{
			Key:        i.Keys,
			Background: true,
			Unique:     i.Unique,
			Sparse:     i.Sparse,
		})
		if err != nil {
			kv["err"] = err
			llog.Fatal("could not ensure mongo index", kv)
		}
	}
}

// EnsureSession connections to the given addr with a 5 second timeout, sets the
// session to Safe, sets the mode to PrimaryPreferred, and returns the resulting
// mgo.Session. If it fails to connect it fatals.
func EnsureSession(addr string) *mgo.Session {
	kv := llog.KV{"addr": addr}
	llog.Info("dialing mongo", kv)
	s, err := mgo.DialWithTimeout(addr, 5 * time.Second)
	if err != nil {
		kv["err"] = err
		llog.Fatal("error calling mgo.DialWithTimeout", kv)
	}
	s.SetSafe(&mgo.Safe{})
	s.SetMode(mgo.PrimaryPreferred, true)

	llog.Info("done setting up mongo connection", kv)
	return s
}

// CollectionHelper is a simple helper type which makes it easy to call methods
// on a Database or Collection safely. It will handle the Copy'ing and Close'ing
// of the given Session automatically
type SessionHelper struct {
	Session *mgo.Session
	DB      string
	Coll    string
}

// WithDB handles calls the given function on an instance of the database named
// by the field in the SessionHelper. It will handle Copy'ing and Close'ing the
// Session object automatically.
func (s SessionHelper) WithDB(fn func(*mgo.Database)) {
	session := s.Session.Copy()
	defer session.Close()
	fn(session.DB(s.DB))
}

// WithColl handles calls the given function on an instance of the collection
// named by the field in the SessionHelper. It will handle Copy'ing and
// Close'ing the Session object automatically.
func (s SessionHelper) WithColl(fn func(*mgo.Collection)) {
	s.WithDB(func(db *mgo.Database) {
		fn(db.C(s.Coll))
	})
}

// MustEnsureIndexes takes the given indexes and ensure's they are all in place.
// If any indexes fail a message will be logged and the process will exit
func (s SessionHelper) MustEnsureIndexes(indexes ...mgo.Index) {
	s.WithColl(func(c *mgo.Collection) {
		for _, index := range indexes {
			kv := llog.KV{
				"key":  index.Key,
				"coll": s.Coll,
				"db":   s.DB,
			}
			llog.Info("ensuring mongo index", kv)
			if err := c.EnsureIndex(index); err != nil {
				kv["err"] = err
				llog.Fatal("could not ensure mongo index", kv)
			}
		}
	})
}
<|endoftext|>


<commit_before>package spvwallet

import (
	"net"
	"os"
	"bytes"
	"io/ioutil"
	"sync"
	"math/rand"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/op/go-logging"
	b32 "github.com/tyler-smith/go-bip32"
	b39 "github.com/tyler-smith/go-bip39"
	btc "github.com/btcsuite/btcutil"
)

type SPVWallet struct {
	params           *chaincfg.Params

	peerGroup         map[string] *Peer
	pgMutex           sync.Mutex
	downloadPeer      *Peer
	diconnectChan     chan string

	masterPrivateKey *b32.Key
	masterPublicKey  *b32.Key

	maxFee            uint64
	priorityFee       uint64
	normalFee         uint64
	economicFee       uint64
	feeAPI            string

	repoPath          string

	addrs             []string
	userAgent         string

	db                Datastore
	blockchain        *Blockchain
	state             *TxStore
}

var log = logging.MustGetLogger("bitcoin")

const WALLET_VERSION = "0.1.0"

const MAX_PEERS = 10

func NewSPVWallet(mnemonic string, params *chaincfg.Params, maxFee uint64, lowFee uint64, mediumFee uint64, highFee uint64, feeApi,
	repoPath string, db Datastore, userAgent string, logger logging.Backend) *SPVWallet {

	log.SetBackend(logger)

	seed := b39.NewSeed(mnemonic, "")
	mk, _ := b32.NewMasterKey(seed)

	w := new(SPVWallet)
	w.masterPrivateKey = mk
	w.masterPublicKey = mk.PublicKey()
	w.params = params
	w.maxFee = maxFee
	w.priorityFee = highFee
	w.normalFee = mediumFee
	w.economicFee = lowFee
	w.feeAPI = feeApi
	w.repoPath = repoPath
	w.db = db
	w.userAgent = userAgent
	w.diconnectChan = make(chan string)
	w.peerGroup = make(map[string]*Peer)
	go w.run()
	return w
}

func (w *SPVWallet) run() {
	w.queryDNSSeeds()

	// setup TxStore first (before spvcon)
	w.state = NewTxStore(w.params, w.db, w.masterPrivateKey)

	// shuffle addrs
	for i := range w.addrs {
		j := rand.Intn(i + 1)
		w.addrs[i], w.addrs[j] = w.addrs[j], w.addrs[i]
	}

	// create header db
	bc := NewBlockchain(w.repoPath, w.params)
	w.blockchain = bc
	//bc.Print()

	// If this is a new wallet or restoring from seed. Set the db height to the
	// height of the checkpoint block.
	tipHeight, _ := w.state.GetDBSyncHeight()
	if tipHeight == 0 {
		if w.params.Name == chaincfg.MainNetParams.Name {
			w.state.SetDBSyncHeight(MAINNET_CHECKPOINT_HEIGHT)
		} else if w.params.Name == chaincfg.TestNet3Params.Name {
			w.state.SetDBSyncHeight(TESTNET3_CHECKPOINT_HEIGHT)
		}
	}

	go w.connectToPeers()
	go w.onPeerDisconnect()

	log.Notice(w.CurrentAddress(KeyPurpose(0)))

	log.Notice(w.Balance())
}

// Loop through creating new peers until we reach MAX_PEERS
// If we don't have a download peer set we will set one
func (w *SPVWallet) connectToPeers() {
	for {
		if len(w.peerGroup) < MAX_PEERS {
			var addr string
			addr, w.addrs = w.addrs[len(w.addrs)-1], w.addrs[:len(w.addrs)-1]
			var dp bool
			if w.downloadPeer == nil {
				dp = true
				// Set this temporarily to avoid a race condition which sets two download peers
				w.downloadPeer = &Peer{}
			}
			peer, err := NewPeer(addr, w.blockchain, w.state, w.params, w.userAgent, w.diconnectChan, dp)
			if err != nil {
				if dp {
					// Unset as download peer on failure
					w.downloadPeer = nil
				}
				continue
			}
			if dp {
				w.downloadPeer = peer
			}
			w.pgMutex.Lock()
			w.peerGroup[addr] = peer
			w.pgMutex.Unlock()
		} else {
			break
		}
	}
}

func (w *SPVWallet) onPeerDisconnect() {
	for {
		select {
		case addr := <- w.diconnectChan:
			w.pgMutex.Lock()
			p, ok := w.peerGroup[addr]
			if ok {
				p.con.Close()
				p.connectionState = DEAD
				if p.downloadPeer {
					w.downloadPeer = nil
				}
				delete(w.peerGroup, addr)
			}
			w.pgMutex.Unlock()
			log.Infof("Disconnected from peer %s", addr)
			w.connectToPeers()
		}
	}
}

func (w *SPVWallet) queryDNSSeeds() {
	// Query DNS seeds for addrs. Eventually we will cache these.
	log.Info("Querying DNS seeds...")
	for _, seed := range(w.params.DNSSeeds) {
		addrs, err := net.LookupHost(seed)
		if err != nil {
			continue
		}
		for _, addr := range(addrs) {
			w.addrs = append(w.addrs, addr + ":" + w.params.DefaultPort)
		}
	}
	log.Infof("DNS seeds returned %d addresses.", len(w.addrs))
}

func (w *SPVWallet) openHeaderFile(hfn string) error {
	_, err := os.Stat(hfn)
	if err != nil {
		if os.IsNotExist(err) {
			var b bytes.Buffer
			if w.params.Name == chaincfg.MainNetParams.Name {
				pad := make([]byte, MAINNET_CHECKPOINT_HEIGHT * 80)
				b.Write(pad)
				err = MainnetCheckpoint.Serialize(&b)
				if err != nil {
					return err
				}
			} else if w.params.Name == chaincfg.TestNet3Params.Name {
				pad := make([]byte, TESTNET3_CHECKPOINT_HEIGHT * 80)
				b.Write(pad)
				err = Testnet3Checkpoint.Serialize(&b)
				if err != nil {
					return err
				}
			}
			err = ioutil.WriteFile(hfn, b.Bytes(), 0600)
			if err != nil {
				return err
			}
			log.Debugf("Created hardcoded checkpoint header at %s\n", hfn)
		}
	}

	return nil
}

func (w *SPVWallet) checkIfStxoIsConfirmed(utxo Utxo, stxos []Stxo) bool {
	for _, stxo := range(stxos) {
		if stxo.SpendTxid == utxo.Op.Hash {
			if stxo.Utxo.AtHeight > 0 {
				return true
			} else {
				return w.checkIfStxoIsConfirmed(stxo.Utxo, stxos)
			}
		}
	}
	return false
}

//////////////////////////
//
// API
//

func (w *SPVWallet) CurrencyCode() string {
	return "btc"
}

func (w *SPVWallet) MasterPrivateKey() *b32.Key {
	return w.masterPrivateKey
}

func (w *SPVWallet) MasterPublicKey() *b32.Key {
	return w.masterPublicKey
}

func (w *SPVWallet) CurrentAddress(purpose KeyPurpose) *btc.AddressPubKeyHash {
	key := w.state.GetCurrentKey(purpose)
	addr, _ := btc.NewAddressPubKey(key.PublicKey().Key, w.params)
	return addr.AddressPubKeyHash()
}

func (w *SPVWallet) Balance() (confirmed, unconfirmed int64) {
	utxos, _ := w.db.Utxos().GetAll()
	stxos, _ := w.db.Stxos().GetAll()
	for _, utxo := range utxos {
		if utxo.AtHeight > 0 {
			confirmed += utxo.Value
		} else {
			if w.checkIfStxoIsConfirmed(utxo, stxos) {
				confirmed += utxo.Value
			} else {
				unconfirmed += utxo.Value
			}
		}
	}
	return confirmed, unconfirmed
}

func (w *SPVWallet) Params() *chaincfg.Params {
	return w.params
}<commit_msg>Use leveledbackend logger<commit_after>package spvwallet

import (
	"net"
	"os"
	"bytes"
	"io/ioutil"
	"sync"
	"math/rand"
	"github.com/btcsuite/btcd/chaincfg"
	"github.com/op/go-logging"
	b32 "github.com/tyler-smith/go-bip32"
	b39 "github.com/tyler-smith/go-bip39"
	btc "github.com/btcsuite/btcutil"
)

type SPVWallet struct {
	params           *chaincfg.Params

	peerGroup         map[string] *Peer
	pgMutex           sync.Mutex
	downloadPeer      *Peer
	diconnectChan     chan string

	masterPrivateKey *b32.Key
	masterPublicKey  *b32.Key

	maxFee            uint64
	priorityFee       uint64
	normalFee         uint64
	economicFee       uint64
	feeAPI            string

	repoPath          string

	addrs             []string
	userAgent         string

	db                Datastore
	blockchain        *Blockchain
	state             *TxStore
}

var log = logging.MustGetLogger("bitcoin")

const WALLET_VERSION = "0.1.0"

const MAX_PEERS = 10

func NewSPVWallet(mnemonic string, params *chaincfg.Params, maxFee uint64, lowFee uint64, mediumFee uint64, highFee uint64, feeApi,
	repoPath string, db Datastore, userAgent string, logger logging.LeveledBackend) *SPVWallet {

	log.SetBackend(logger)

	seed := b39.NewSeed(mnemonic, "")
	mk, _ := b32.NewMasterKey(seed)

	w := new(SPVWallet)
	w.masterPrivateKey = mk
	w.masterPublicKey = mk.PublicKey()
	w.params = params
	w.maxFee = maxFee
	w.priorityFee = highFee
	w.normalFee = mediumFee
	w.economicFee = lowFee
	w.feeAPI = feeApi
	w.repoPath = repoPath
	w.db = db
	w.userAgent = userAgent
	w.diconnectChan = make(chan string)
	w.peerGroup = make(map[string]*Peer)
	go w.run()
	return w
}

func (w *SPVWallet) run() {
	w.queryDNSSeeds()

	// setup TxStore first (before spvcon)
	w.state = NewTxStore(w.params, w.db, w.masterPrivateKey)

	// shuffle addrs
	for i := range w.addrs {
		j := rand.Intn(i + 1)
		w.addrs[i], w.addrs[j] = w.addrs[j], w.addrs[i]
	}

	// create header db
	bc := NewBlockchain(w.repoPath, w.params)
	w.blockchain = bc
	//bc.Print()

	// If this is a new wallet or restoring from seed. Set the db height to the
	// height of the checkpoint block.
	tipHeight, _ := w.state.GetDBSyncHeight()
	if tipHeight == 0 {
		if w.params.Name == chaincfg.MainNetParams.Name {
			w.state.SetDBSyncHeight(MAINNET_CHECKPOINT_HEIGHT)
		} else if w.params.Name == chaincfg.TestNet3Params.Name {
			w.state.SetDBSyncHeight(TESTNET3_CHECKPOINT_HEIGHT)
		}
	}

	go w.connectToPeers()
	go w.onPeerDisconnect()

	log.Notice(w.CurrentAddress(KeyPurpose(0)))

	log.Notice(w.Balance())
}

// Loop through creating new peers until we reach MAX_PEERS
// If we don't have a download peer set we will set one
func (w *SPVWallet) connectToPeers() {
	for {
		if len(w.peerGroup) < MAX_PEERS {
			var addr string
			addr, w.addrs = w.addrs[len(w.addrs)-1], w.addrs[:len(w.addrs)-1]
			var dp bool
			if w.downloadPeer == nil {
				dp = true
				// Set this temporarily to avoid a race condition which sets two download peers
				w.downloadPeer = &Peer{}
			}
			peer, err := NewPeer(addr, w.blockchain, w.state, w.params, w.userAgent, w.diconnectChan, dp)
			if err != nil {
				if dp {
					// Unset as download peer on failure
					w.downloadPeer = nil
				}
				continue
			}
			if dp {
				w.downloadPeer = peer
			}
			w.pgMutex.Lock()
			w.peerGroup[addr] = peer
			w.pgMutex.Unlock()
		} else {
			break
		}
	}
}

func (w *SPVWallet) onPeerDisconnect() {
	for {
		select {
		case addr := <- w.diconnectChan:
			w.pgMutex.Lock()
			p, ok := w.peerGroup[addr]
			if ok {
				p.con.Close()
				p.connectionState = DEAD
				if p.downloadPeer {
					w.downloadPeer = nil
				}
				delete(w.peerGroup, addr)
			}
			w.pgMutex.Unlock()
			log.Infof("Disconnected from peer %s", addr)
			w.connectToPeers()
		}
	}
}

func (w *SPVWallet) queryDNSSeeds() {
	// Query DNS seeds for addrs. Eventually we will cache these.
	log.Info("Querying DNS seeds...")
	for _, seed := range(w.params.DNSSeeds) {
		addrs, err := net.LookupHost(seed)
		if err != nil {
			continue
		}
		for _, addr := range(addrs) {
			w.addrs = append(w.addrs, addr + ":" + w.params.DefaultPort)
		}
	}
	log.Infof("DNS seeds returned %d addresses.", len(w.addrs))
}

func (w *SPVWallet) openHeaderFile(hfn string) error {
	_, err := os.Stat(hfn)
	if err != nil {
		if os.IsNotExist(err) {
			var b bytes.Buffer
			if w.params.Name == chaincfg.MainNetParams.Name {
				pad := make([]byte, MAINNET_CHECKPOINT_HEIGHT * 80)
				b.Write(pad)
				err = MainnetCheckpoint.Serialize(&b)
				if err != nil {
					return err
				}
			} else if w.params.Name == chaincfg.TestNet3Params.Name {
				pad := make([]byte, TESTNET3_CHECKPOINT_HEIGHT * 80)
				b.Write(pad)
				err = Testnet3Checkpoint.Serialize(&b)
				if err != nil {
					return err
				}
			}
			err = ioutil.WriteFile(hfn, b.Bytes(), 0600)
			if err != nil {
				return err
			}
			log.Debugf("Created hardcoded checkpoint header at %s\n", hfn)
		}
	}

	return nil
}

func (w *SPVWallet) checkIfStxoIsConfirmed(utxo Utxo, stxos []Stxo) bool {
	for _, stxo := range(stxos) {
		if stxo.SpendTxid == utxo.Op.Hash {
			if stxo.Utxo.AtHeight > 0 {
				return true
			} else {
				return w.checkIfStxoIsConfirmed(stxo.Utxo, stxos)
			}
		}
	}
	return false
}

//////////////////////////
//
// API
//

func (w *SPVWallet) CurrencyCode() string {
	return "btc"
}

func (w *SPVWallet) MasterPrivateKey() *b32.Key {
	return w.masterPrivateKey
}

func (w *SPVWallet) MasterPublicKey() *b32.Key {
	return w.masterPublicKey
}

func (w *SPVWallet) CurrentAddress(purpose KeyPurpose) *btc.AddressPubKeyHash {
	key := w.state.GetCurrentKey(purpose)
	addr, _ := btc.NewAddressPubKey(key.PublicKey().Key, w.params)
	return addr.AddressPubKeyHash()
}

func (w *SPVWallet) Balance() (confirmed, unconfirmed int64) {
	utxos, _ := w.db.Utxos().GetAll()
	stxos, _ := w.db.Stxos().GetAll()
	for _, utxo := range utxos {
		if utxo.AtHeight > 0 {
			confirmed += utxo.Value
		} else {
			if w.checkIfStxoIsConfirmed(utxo, stxos) {
				confirmed += utxo.Value
			} else {
				unconfirmed += utxo.Value
			}
		}
	}
	return confirmed, unconfirmed
}

func (w *SPVWallet) Params() *chaincfg.Params {
	return w.params
}<|endoftext|>


<commit_before>package npm

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/ssut/pocketnpm/db"
	"github.com/ssut/pocketnpm/log"
)

type MirrorClient struct {
	db        *db.PocketBase
	config    *MirrorConfig
	npmClient *NPMClient
}

func NewMirrorClient(db *db.PocketBase, config *MirrorConfig) *MirrorClient {
	// Fix relative path
	config.Path, _ = filepath.Abs(config.Path)
	// Check for directory exists or not
	// If not, try to create an empty directory for it
	if _, err := os.Stat(config.Path); os.IsNotExist(err) {
		log.Debugf("Directory does not exist: %s", config.Path)
		err = os.MkdirAll(config.Path, 0755)
		if err != nil {
			log.Fatalf("Failed to create directory: %s", config.Path)
		} else {
			log.Debugf("Directory has been created: %s", config.Path)
		}
	}

	npmClient := NewNPMClient(config.Registry, config.Path)
	client := &MirrorClient{
		config:    config,
		db:        db,
		npmClient: npmClient,
	}

	return client
}

func (c *MirrorClient) initDocument(allDocs *AllDocsResponse) {
	packages := make([]*db.BarePackage, allDocs.TotalRows)

	for i, doc := range allDocs.Rows {
		packages[i] = &db.BarePackage{
			ID:       doc.ID,
			Revision: doc.Value.Revision,
		}
	}

	log.Debug("Putting packages..")
	c.db.PutPackages(packages)
	c.db.SetSequence(allDocs.Sequence)
	log.Debug("Succeed")
}

func (c *MirrorClient) FirstRun() {
	allDocs := c.npmClient.GetAllDocs()
	log.Infof("Total documents found: %d", allDocs.TotalRows)

	log.Debug("Store all documents by given properties")
	c.initDocument(allDocs)
}

func (c *MirrorClient) Start() {
	// Load all packages with its revision
	packages := c.db.GetImcompletePackages()

	// Array of workers
	var workers = make([]*MirrorWorker, c.config.MaxConnections)
	// Initialize channels
	var workQueue = make(chan *db.BarePackage)
	var workerQueue = make(chan chan *db.BarePackage, c.config.MaxConnections)
	var resultQueue = make(chan *MirrorWorkResult)
	// WaitGroup
	var wg sync.WaitGroup

	// Create mirror workers
	for i := 0; i < c.config.MaxConnections; i++ {
		log.Debugf("Starting worker: %d", i)
		workers[i] = NewMirrorWorker(i, c.npmClient, workerQueue, resultQueue, &wg)
		workers[i].Start()
	}

	// Result handler
	go func(db *db.PocketBase) {
		for {
			result, done := <-resultQueue
			if !done {
				return
			}

			succeed := db.PutCompleted(result.Package, result.Document, result.DocumentRevision, result.Files)
			if succeed {
				log.WithFields(logrus.Fields{
					"sameRev": result.Package.Revision == result.DocumentRevision,
					"files":   len(result.Files),
					"worker":  result.WorkerID,
				}).Infof("Mirrored: %s", result.Package.ID)
			} else {
				log.Errorf("Failed to mirror: %s", result.Package.ID)
			}
		}
	}(c.db)

	// Start dispatcher
	go func() {
		for {
			select {
			case work := <-workQueue:
				// here we received a work request
				go func(work *db.BarePackage) {
					worker := <-workerQueue

					// dispatch work request
					worker <- work
				}(work)
			}
		}
	}()

	// Dispatch all packages
	for _, pkg := range packages {
		wg.Add(1)
		workQueue <- pkg
	}

	// Wait for jobs to be finished
	wg.Wait()

	// Wait for all workers complete
	for _, worker := range workers {
		wg.Add(1)
		worker.Stop()
	}
	wg.Wait()
	log.Info("Done")
}

func (c *MirrorClient) Run() {
	if !c.db.IsInitialized() {
		log.Debug("Database has not been initialized. Init..")
		c.db.Init()
	}

	if !c.db.IsInitialized() {
		log.Fatal("Failed to initialize database")
	}

	stats := c.db.GetStats()
	log.WithFields(logrus.Fields{
		"Packages":  stats.Packages,
		"Marks":     stats.Marks,
		"Documents": stats.Documents,
		"Files":     stats.Files,
	}).Debug("Status for database")

	seq := c.db.GetSequence()
	markedCount := c.db.GetCountOfMarks(true)

	if seq == 0 {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("State marked as first run")
		c.FirstRun()
		c.Start()
	}

	if seq > 0 && markedCount < stats.Packages {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("Continue")
		c.Start()
	}

	if seq > 0 && markedCount == stats.Packages {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("State marked as run for updates")
	}

}
<commit_msg>worker: limit the number of buffers and goroutine creates<commit_after>package npm

import (
	"os"
	"path/filepath"
	"sync"

	"github.com/Sirupsen/logrus"
	"github.com/ssut/pocketnpm/db"
	"github.com/ssut/pocketnpm/log"
)

type MirrorClient struct {
	db        *db.PocketBase
	config    *MirrorConfig
	npmClient *NPMClient
}

func NewMirrorClient(db *db.PocketBase, config *MirrorConfig) *MirrorClient {
	// Fix relative path
	config.Path, _ = filepath.Abs(config.Path)
	// Check for directory exists or not
	// If not, try to create an empty directory for it
	if _, err := os.Stat(config.Path); os.IsNotExist(err) {
		log.Debugf("Directory does not exist: %s", config.Path)
		err = os.MkdirAll(config.Path, 0755)
		if err != nil {
			log.Fatalf("Failed to create directory: %s", config.Path)
		} else {
			log.Debugf("Directory has been created: %s", config.Path)
		}
	}

	npmClient := NewNPMClient(config.Registry, config.Path)
	client := &MirrorClient{
		config:    config,
		db:        db,
		npmClient: npmClient,
	}

	return client
}

func (c *MirrorClient) initDocument(allDocs *AllDocsResponse) {
	packages := make([]*db.BarePackage, allDocs.TotalRows)

	for i, doc := range allDocs.Rows {
		packages[i] = &db.BarePackage{
			ID:       doc.ID,
			Revision: doc.Value.Revision,
		}
	}

	log.Debug("Putting packages..")
	c.db.PutPackages(packages)
	c.db.SetSequence(allDocs.Sequence)
	log.Debug("Succeed")
}

func (c *MirrorClient) FirstRun() {
	allDocs := c.npmClient.GetAllDocs()
	log.Infof("Total documents found: %d", allDocs.TotalRows)

	log.Debug("Store all documents by given properties")
	c.initDocument(allDocs)
}

func (c *MirrorClient) Start() {
	// Load all packages with its revision
	packages := c.db.GetImcompletePackages()

	// Array of workers
	var workers = make([]*MirrorWorker, c.config.MaxConnections)
	// Initialize channels
	// workQueue has a buffer size of 100
	var workQueue = make(chan *db.BarePackage, 100)
	var workerQueue = make(chan chan *db.BarePackage, c.config.MaxConnections)
	var resultQueue = make(chan *MirrorWorkResult)
	// WaitGroup
	var wg sync.WaitGroup

	// Create mirror workers
	for i := 0; i < c.config.MaxConnections; i++ {
		log.Debugf("Starting worker: %d", i)
		workers[i] = NewMirrorWorker(i, c.npmClient, workerQueue, resultQueue, &wg)
		workers[i].Start()
	}

	// Result handler
	go func(db *db.PocketBase) {
		for {
			result, done := <-resultQueue
			if !done {
				return
			}

			succeed := db.PutCompleted(result.Package, result.Document, result.DocumentRevision, result.Files)
			if succeed {
				log.WithFields(logrus.Fields{
					"sameRev": result.Package.Revision == result.DocumentRevision,
					"files":   len(result.Files),
					"worker":  result.WorkerID,
				}).Infof("Mirrored: %s", result.Package.ID)
			} else {
				log.Errorf("Failed to mirror: %s", result.Package.ID)
			}
		}
	}(c.db)

	// Start dispatcher
	go func() {
		for {
			select {
			case work := <-workQueue:
				// here we received a work request
				// goroutine won't be created until the acquired worker is released
				worker := <-workerQueue
				go func(work *db.BarePackage) {
					// dispatch work request
					worker <- work
				}(work)
			}
		}
	}()

	// Dispatch all packages
	for _, pkg := range packages {
		wg.Add(1)
		workQueue <- pkg
	}
	log.Debug("Successfully dispatched all queues")

	// Wait for jobs to be finished
	wg.Wait()

	// Wait for all workers complete
	for _, worker := range workers {
		wg.Add(1)
		worker.Stop()
	}
	wg.Wait()
	log.Info("Done")
}

func (c *MirrorClient) Run() {
	if !c.db.IsInitialized() {
		log.Debug("Database has not been initialized. Init..")
		c.db.Init()
	}

	if !c.db.IsInitialized() {
		log.Fatal("Failed to initialize database")
	}

	stats := c.db.GetStats()
	log.WithFields(logrus.Fields{
		"Packages":  stats.Packages,
		"Marks":     stats.Marks,
		"Documents": stats.Documents,
		"Files":     stats.Files,
	}).Debug("Status for database")

	seq := c.db.GetSequence()
	markedCount := c.db.GetCountOfMarks(true)

	if seq == 0 {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("State marked as first run")
		c.FirstRun()
		c.Start()
	}

	if seq > 0 && markedCount < stats.Packages {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("Continue")
		c.Start()
	}

	if seq > 0 && markedCount == stats.Packages {
		log.WithFields(logrus.Fields{
			"sequence": seq,
			"marked":   markedCount,
		}).Info("State marked as run for updates")
	}

}
<|endoftext|>


<commit_before>/*

memory is a simple example game based on memory--where players take turn
flipping over two cards, and keeping them if they match.

*/
package memory

import (
	"github.com/jkomoros/boardgame"
	"strconv"
	"strings"
)

type gameDelegate struct {
	boardgame.DefaultGameDelegate
}

func (g *gameDelegate) Name() string {
	return "memory"
}

func (g *gameDelegate) DisplayName() string {
	return "Memory"
}

func (g *gameDelegate) DefaultNumPlayeres() int {
	return 2
}

func (g *gameDelegate) LegalNumPlayers(numPlayers int) bool {
	return numPlayers < 4 && numPlayers > 1
}

func (g *gameDelegate) EmptyGameState() boardgame.MutableGameState {

	cards := g.Manager().Chest().Deck(cardsDeckName)

	if cards == nil {
		return nil
	}

	return &gameState{
		CurrentPlayer: 0,
		HiddenCards:   boardgame.NewSizedStack(cards, len(cards.Components())),
		RevealedCards: boardgame.NewSizedStack(cards, len(cards.Components())),
	}
}

func (g *gameDelegate) EmptyPlayerState(playerIndex int) boardgame.MutablePlayerState {

	cards := g.Manager().Chest().Deck(cardsDeckName)

	if cards == nil {
		return nil
	}

	return &playerState{
		playerIndex: playerIndex,
		WonCards:    boardgame.NewGrowableStack(cards, 0),
	}
}

func (g *gameDelegate) DistributeComponentToStarterStack(state boardgame.State, c *boardgame.Component) (boardgame.Stack, error) {
	game, _ := concreteStates(state)

	return game.HiddenCards, nil

}

func (g *gameDelegate) FinishSetUp(state boardgame.MutableState) {
	game, _ := concreteStates(state)

	game.HiddenCards.Shuffle()
}

func (g *gameDelegate) Diagram(state boardgame.State) string {
	game, players := concreteStates(state)

	var result []string

	result = append(result, "Board")

	for i, c := range game.HiddenCards.Components() {

		//If there's no hidden card in this slot, see if there is a revealed one.
		if c == nil {
			c = game.RevealedCards.ComponentAt(i)
		}

		value := "<empty>"

		if c != nil {
			value = c.Values.(*cardValue).Type
		}

		result = append(result, "\t"+value)

	}

	result = append(result, "*****")

	for i, player := range players {
		result = append(result, "Player"+strconv.Itoa(i))
		result = append(result, strconv.Itoa(player.WonCards.NumComponents()))
	}

	return strings.Join(result, "\n")
}

func NewManager(storage boardgame.StorageManager) *boardgame.GameManager {
	chest := boardgame.NewComponentChest()

	cards := boardgame.NewDeck()

	for _, val := range cardNames {
		cards.AddComponentMulti(&cardValue{
			Type: val,
		}, 2)
	}

	cards.SetShadowValues(&cardValue{
		Type: "<hidden>",
	})

	chest.AddDeck(cardsDeckName, cards)

	manager := boardgame.NewGameManager(&gameDelegate{}, chest, storage)

	if manager == nil {
		panic("No manager returned")
	}

	//TODO: add moves

	manager.SetUp()

	return manager
}
<commit_msg>Implement sanitization policy for board. Part of #174.<commit_after>/*

memory is a simple example game based on memory--where players take turn
flipping over two cards, and keeping them if they match.

*/
package memory

import (
	"github.com/jkomoros/boardgame"
	"strconv"
	"strings"
)

type gameDelegate struct {
	boardgame.DefaultGameDelegate
}

func (g *gameDelegate) Name() string {
	return "memory"
}

func (g *gameDelegate) DisplayName() string {
	return "Memory"
}

func (g *gameDelegate) DefaultNumPlayeres() int {
	return 2
}

func (g *gameDelegate) LegalNumPlayers(numPlayers int) bool {
	return numPlayers < 4 && numPlayers > 1
}

func (g *gameDelegate) EmptyGameState() boardgame.MutableGameState {

	cards := g.Manager().Chest().Deck(cardsDeckName)

	if cards == nil {
		return nil
	}

	return &gameState{
		CurrentPlayer: 0,
		HiddenCards:   boardgame.NewSizedStack(cards, len(cards.Components())),
		RevealedCards: boardgame.NewSizedStack(cards, len(cards.Components())),
	}
}

func (g *gameDelegate) EmptyPlayerState(playerIndex int) boardgame.MutablePlayerState {

	cards := g.Manager().Chest().Deck(cardsDeckName)

	if cards == nil {
		return nil
	}

	return &playerState{
		playerIndex: playerIndex,
		WonCards:    boardgame.NewGrowableStack(cards, 0),
	}
}

func (g *gameDelegate) DistributeComponentToStarterStack(state boardgame.State, c *boardgame.Component) (boardgame.Stack, error) {
	game, _ := concreteStates(state)

	return game.HiddenCards, nil

}

func (g *gameDelegate) FinishSetUp(state boardgame.MutableState) {
	game, _ := concreteStates(state)

	game.HiddenCards.Shuffle()
}

func (g *gameDelegate) Diagram(state boardgame.State) string {
	game, players := concreteStates(state)

	var result []string

	result = append(result, "Board")

	for i, c := range game.HiddenCards.Components() {

		//If there's no hidden card in this slot, see if there is a revealed one.
		if c == nil {
			c = game.RevealedCards.ComponentAt(i)
		}

		value := "<empty>"

		if c != nil {
			value = c.Values.(*cardValue).Type
		}

		result = append(result, "\t"+value)

	}

	result = append(result, "*****")

	for i, player := range players {
		result = append(result, "Player"+strconv.Itoa(i))
		result = append(result, strconv.Itoa(player.WonCards.NumComponents()))
	}

	return strings.Join(result, "\n")
}

var policy *boardgame.StatePolicy

func (g *gameDelegate) StateSanitizationPolicy() *boardgame.StatePolicy {

	if policy == nil {
		policy = &boardgame.StatePolicy{
			Game: map[string]boardgame.GroupPolicy{
				"HiddenCards": boardgame.GroupPolicy{
					boardgame.GroupAll: boardgame.PolicyLen,
				},
			},
		}
	}

	return policy

}

func NewManager(storage boardgame.StorageManager) *boardgame.GameManager {
	chest := boardgame.NewComponentChest()

	cards := boardgame.NewDeck()

	for _, val := range cardNames {
		cards.AddComponentMulti(&cardValue{
			Type: val,
		}, 2)
	}

	cards.SetShadowValues(&cardValue{
		Type: "<hidden>",
	})

	chest.AddDeck(cardsDeckName, cards)

	manager := boardgame.NewGameManager(&gameDelegate{}, chest, storage)

	if manager == nil {
		panic("No manager returned")
	}

	//TODO: add moves

	manager.SetUp()

	return manager
}
<|endoftext|>


<commit_before>/* This example show a basic menu similar to that found in the ncurses
 * examples from TLDP */

package main

import gc "code.google.com/p/goncurses"

const (
	HEIGHT = 10
	WIDTH  = 30
)

func main() {
	var active int
	menu := []string{"Choice 1", "Choice 2", "Choice 3", "Choice 4", "Exit"}

	stdscr, _ := gc.Init()
	defer gc.End()

	gc.Raw(true)
	gc.Echo(false)
	gc.Cursor(0)
	stdscr.Clear()
	stdscr.Keypad(true)

	rows, cols := stdscr.Maxyx()
	y, x := (rows-HEIGHT)/2, (cols-WIDTH)/2

	win, _ := gc.NewWindow(HEIGHT, WIDTH, y, x)
	win.Keypad(true)
	stdscr.Print(0, 0,
		"Use arrow keys to go up and down, Press enter to select")
	stdscr.Refresh()

	printmenu(win, menu, active)
	gc.MouseMask(gc.M_B1_CLICKED, nil)

	for {
		ch := stdscr.GetChar()
		switch ch {
		case 'q':
			return
		case gc.KEY_UP:
			if active == 0 {
				active = len(menu) - 1
			} else {
				active -= 1
			}
		case gc.KEY_DOWN:
			if active == len(menu)-1 {
				active = 0
			} else {
				active += 1
			}
		case gc.KEY_MOUSE:
			md, _ := gc.GetMouse()
			new := getactive(x, y, md[0], md[1], menu)
			if new != -1 {
				active = new
			}
			stdscr.Print(23, 0, "Choice #%d: %s selected", active+1,
				menu[active])
			stdscr.ClearToEOL()
			stdscr.Refresh()
		case gc.KEY_RETURN:
			stdscr.Print(23, 0, "Choice #%d: %s selected", active+1,
				menu[active])
			stdscr.ClearToEOL()
			stdscr.Refresh()
		default:
			stdscr.Print(23, 0, "Character pressed = %3d/%c", ch, ch)
			stdscr.ClearToEOL()
			stdscr.Refresh()
		}

		printmenu(win, menu, active)
	}
}

func getactive(x, y, mx, my int, menu []string) int {
	row := my - y - 2
	col := mx - x - 2

	if row < 0 || row > len(menu)-1 {
		return -1
	}

	l := menu[row]

	if col >= 0 && col < len(l) {
		return row
	}
	return -1
}

func printmenu(w gc.Window, menu []string, active int) {
	y, x := 2, 2
	w.Box(0, 0)
	for i, s := range menu {
		if i == active {
			w.AttrOn(gc.A_REVERSE)
			w.Print(y+i, x, s)
			w.AttrOff(gc.A_REVERSE)
		} else {
			w.Print(y+i, x, s)
		}
	}
	w.Refresh()
}
<commit_msg>Update mouse example for MovePrint<commit_after>/* This example show a basic menu similar to that found in the ncurses
 * examples from TLDP */

package main

import gc "code.google.com/p/goncurses"

const (
	HEIGHT = 10
	WIDTH  = 30
)

func main() {
	var active int
	menu := []string{"Choice 1", "Choice 2", "Choice 3", "Choice 4", "Exit"}

	stdscr, _ := gc.Init()
	defer gc.End()

	gc.Raw(true)
	gc.Echo(false)
	gc.Cursor(0)
	stdscr.Clear()
	stdscr.Keypad(true)

	rows, cols := stdscr.Maxyx()
	y, x := (rows-HEIGHT)/2, (cols-WIDTH)/2

	win, _ := gc.NewWindow(HEIGHT, WIDTH, y, x)
	win.Keypad(true)
	stdscr.MovePrint(0, 0,
		"Use arrow keys to go up and down, Press enter to select")
	stdscr.Refresh()

	printmenu(win, menu, active)
	gc.MouseMask(gc.M_B1_CLICKED, nil)

	for {
		ch := stdscr.GetChar()
		switch ch {
		case 'q':
			return
		case gc.KEY_UP:
			if active == 0 {
				active = len(menu) - 1
			} else {
				active -= 1
			}
		case gc.KEY_DOWN:
			if active == len(menu)-1 {
				active = 0
			} else {
				active += 1
			}
		case gc.KEY_MOUSE:
			md, _ := gc.GetMouse()
			new := getactive(x, y, md[0], md[1], menu)
			if new != -1 {
				active = new
			}
			stdscr.MovePrint(23, 0, "Choice #%d: %s selected", active+1,
				menu[active])
			stdscr.ClearToEOL()
			stdscr.Refresh()
		case gc.KEY_RETURN:
			stdscr.MovePrint(23, 0, "Choice #%d: %s selected", active+1,
				menu[active])
			stdscr.ClearToEOL()
			stdscr.Refresh()
		default:
			stdscr.MovePrint(23, 0, "Character pressed = %3d/%c", ch, ch)
			stdscr.ClearToEOL()
			stdscr.Refresh()
		}

		printmenu(win, menu, active)
	}
}

func getactive(x, y, mx, my int, menu []string) int {
	row := my - y - 2
	col := mx - x - 2

	if row < 0 || row > len(menu)-1 {
		return -1
	}

	l := menu[row]

	if col >= 0 && col < len(l) {
		return row
	}
	return -1
}

func printmenu(w gc.Window, menu []string, active int) {
	y, x := 2, 2
	w.Box(0, 0)
	for i, s := range menu {
		if i == active {
			w.AttrOn(gc.A_REVERSE)
			w.MovePrint(y+i, x, s)
			w.AttrOff(gc.A_REVERSE)
		} else {
			w.MovePrint(y+i, x, s)
		}
	}
	w.Refresh()
}
<|endoftext|>