<commit_before>// Copyright 2017 Monax Industries Limited
//
// 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 types

import (
	"fmt"

	"github.com/eris-ltd/eris-db/word256"
)

//------------------------------------------------------------------------------------------------

var (
	GlobalPermissionsAddress    = word256.Zero256[:20]
	GlobalPermissionsAddress256 = word256.Zero256
)

// A particular permission
type PermFlag uint64

// Base permission references are like unix (the index is already bit shifted)
const (
	// chain permissions
	Root           PermFlag = 1 << iota // 1
	Send                                // 2
	Call                                // 4
	CreateContract                      // 8
	CreateAccount                       // 16
	Bond                                // 32
	Name                                // 64

	// moderator permissions
	HasBase
	SetBase
	UnsetBase
	SetGlobal
	HasRole
	AddRole
	RmRole

	NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64

	TopPermFlag      PermFlag = 1 << (NumPermissions - 1)
	AllPermFlags     PermFlag = TopPermFlag | (TopPermFlag - 1)
	DefaultPermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole
)

var (
	ZeroBasePermissions    = BasePermissions{0, 0}
	ZeroAccountPermissions = AccountPermissions{
		Base: ZeroBasePermissions,
	}
	DefaultAccountPermissions = AccountPermissions{
		Base: BasePermissions{
			Perms:  DefaultPermFlags,
			SetBit: AllPermFlags,
		},
		Roles: []string{},
	}
)

//---------------------------------------------------------------------------------------------

// Base chain permissions struct
type BasePermissions struct {
	// bit array with "has"/"doesn't have" for each permission
	Perms PermFlag `json:"perms"`

	// bit array with "set"/"not set" for each permission (not-set should fall back to global)
	SetBit PermFlag `json:"set"`
}

// Get a permission value. ty should be a power of 2.
// ErrValueNotSet is returned if the permission's set bit is off,
// and should be caught by caller so the global permission can be fetched
func (p *BasePermissions) Get(ty PermFlag) (bool, error) {
	if ty == 0 {
		return false, ErrInvalidPermission(ty)
	}
	if p.SetBit&ty == 0 {
		return false, ErrValueNotSet(ty)
	}
	return p.Perms&ty > 0, nil
}

// Set a permission bit. Will set the permission's set bit to true.
func (p *BasePermissions) Set(ty PermFlag, value bool) error {
	if ty == 0 {
		return ErrInvalidPermission(ty)
	}
	p.SetBit |= ty
	if value {
		p.Perms |= ty
	} else {
		p.Perms &= ^ty
	}
	return nil
}

// Set the permission's set bit to false
func (p *BasePermissions) Unset(ty PermFlag) error {
	if ty == 0 {
		return ErrInvalidPermission(ty)
	}
	p.SetBit &= ^ty
	return nil
}

// Check if the permission is set
func (p *BasePermissions) IsSet(ty PermFlag) bool {
	if ty == 0 {
		return false
	}
	return p.SetBit&ty > 0
}

// Returns the Perms PermFlag masked with SetBit bit field to give the resultant
// permissions enabled by this BasePermissions
func (p *BasePermissions) ResultantPerms() PermFlag {
	return p.Perms & p.SetBit
}

func (p BasePermissions) String() string {
	return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit)
}

//---------------------------------------------------------------------------------------------

type AccountPermissions struct {
	Base  BasePermissions `json:"base"`
	Roles []string        `json:"roles"`
}

// Returns true if the role is found
func (aP *AccountPermissions) HasRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for _, r := range aP.Roles {
		if r == role {
			return true
		}
	}
	return false
}

// Returns true if the role is added, and false if it already exists
func (aP *AccountPermissions) AddRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for _, r := range aP.Roles {
		if r == role {
			return false
		}
	}
	aP.Roles = append(aP.Roles, role)
	return true
}

// Returns true if the role is removed, and false if it is not found
func (aP *AccountPermissions) RmRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for i, r := range aP.Roles {
		if r == role {
			post := []string{}
			if len(aP.Roles) > i+1 {
				post = aP.Roles[i+1:]
			}
			aP.Roles = append(aP.Roles[:i], post...)
			return true
		}
	}
	return false
}

// Clone clones the account permissions
func (accountPermissions *AccountPermissions) Clone() AccountPermissions {
	// clone base permissions
	basePermissionsClone := accountPermissions.Base
	// clone roles []string
	rolesClone := make([]string, len(accountPermissions.Roles))
	// strings are immutable so copy suffices
	copy(rolesClone, accountPermissions.Roles)

	return AccountPermissions{
		Base:  basePermissionsClone,
		Roles: rolesClone,
	}
}

//--------------------------------------------------------------------------------
// string utilities

// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#"
func PermFlagToString(pf PermFlag) (perm string) {
	switch pf {
	case Root:
		perm = "root"
	case Send:
		perm = "send"
	case Call:
		perm = "call"
	case CreateContract:
		perm = "create_contract"
	case CreateAccount:
		perm = "create_account"
	case Bond:
		perm = "bond"
	case Name:
		perm = "name"
	case HasBase:
		perm = "hasBase"
	case SetBase:
		perm = "setBase"
	case UnsetBase:
		perm = "unsetBase"
	case SetGlobal:
		perm = "setGlobal"
	case HasRole:
		perm = "hasRole"
	case AddRole:
		perm = "addRole"
	case RmRole:
		perm = "removeRole"
	default:
		perm = "#-UNKNOWN-#"
	}
	return
}

func PermStringToFlag(perm string) (pf PermFlag, err error) {
	switch perm {
	case "root":
		pf = Root
	case "send":
		pf = Send
	case "call":
		pf = Call
	case "create_contract":
		pf = CreateContract
	case "create_account":
		pf = CreateAccount
	case "bond":
		pf = Bond
	case "name":
		pf = Name
	case "hasBase":
		pf = HasBase
	case "setBase":
		pf = SetBase
	case "unsetBase":
		pf = UnsetBase
	case "setGlobal":
		pf = SetGlobal
	case "hasRole":
		pf = HasRole
	case "addRole":
		pf = AddRole
	case "removeRole":
		pf = RmRole
	default:
		err = fmt.Errorf("Unknown permission %s", perm)
	}
	return
}
<commit_msg>added a quick fix for unmarshalling types problem in CLI<commit_after>// Copyright 2017 Monax Industries Limited
//
// 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 types

import (
	"fmt"
	"strings"

	"github.com/eris-ltd/eris-db/word256"
)

//------------------------------------------------------------------------------------------------

var (
	GlobalPermissionsAddress    = word256.Zero256[:20]
	GlobalPermissionsAddress256 = word256.Zero256
)

// A particular permission
type PermFlag uint64

// Base permission references are like unix (the index is already bit shifted)
const (
	// chain permissions
	Root           PermFlag = 1 << iota // 1
	Send                                // 2
	Call                                // 4
	CreateContract                      // 8
	CreateAccount                       // 16
	Bond                                // 32
	Name                                // 64

	// moderator permissions
	HasBase
	SetBase
	UnsetBase
	SetGlobal
	HasRole
	AddRole
	RmRole

	NumPermissions uint = 14 // NOTE Adjust this too. We can support upto 64

	TopPermFlag      PermFlag = 1 << (NumPermissions - 1)
	AllPermFlags     PermFlag = TopPermFlag | (TopPermFlag - 1)
	DefaultPermFlags PermFlag = Send | Call | CreateContract | CreateAccount | Bond | Name | HasBase | HasRole
)

var (
	ZeroBasePermissions    = BasePermissions{0, 0}
	ZeroAccountPermissions = AccountPermissions{
		Base: ZeroBasePermissions,
	}
	DefaultAccountPermissions = AccountPermissions{
		Base: BasePermissions{
			Perms:  DefaultPermFlags,
			SetBit: AllPermFlags,
		},
		Roles: []string{},
	}
)

//---------------------------------------------------------------------------------------------

// Base chain permissions struct
type BasePermissions struct {
	// bit array with "has"/"doesn't have" for each permission
	Perms PermFlag `json:"perms"`

	// bit array with "set"/"not set" for each permission (not-set should fall back to global)
	SetBit PermFlag `json:"set"`
}

// Get a permission value. ty should be a power of 2.
// ErrValueNotSet is returned if the permission's set bit is off,
// and should be caught by caller so the global permission can be fetched
func (p *BasePermissions) Get(ty PermFlag) (bool, error) {
	if ty == 0 {
		return false, ErrInvalidPermission(ty)
	}
	if p.SetBit&ty == 0 {
		return false, ErrValueNotSet(ty)
	}
	return p.Perms&ty > 0, nil
}

// Set a permission bit. Will set the permission's set bit to true.
func (p *BasePermissions) Set(ty PermFlag, value bool) error {
	if ty == 0 {
		return ErrInvalidPermission(ty)
	}
	p.SetBit |= ty
	if value {
		p.Perms |= ty
	} else {
		p.Perms &= ^ty
	}
	return nil
}

// Set the permission's set bit to false
func (p *BasePermissions) Unset(ty PermFlag) error {
	if ty == 0 {
		return ErrInvalidPermission(ty)
	}
	p.SetBit &= ^ty
	return nil
}

// Check if the permission is set
func (p *BasePermissions) IsSet(ty PermFlag) bool {
	if ty == 0 {
		return false
	}
	return p.SetBit&ty > 0
}

// Returns the Perms PermFlag masked with SetBit bit field to give the resultant
// permissions enabled by this BasePermissions
func (p *BasePermissions) ResultantPerms() PermFlag {
	return p.Perms & p.SetBit
}

func (p BasePermissions) String() string {
	return fmt.Sprintf("Base: %b; Set: %b", p.Perms, p.SetBit)
}

//---------------------------------------------------------------------------------------------

type AccountPermissions struct {
	Base  BasePermissions `json:"base"`
	Roles []string        `json:"roles"`
}

// Returns true if the role is found
func (aP *AccountPermissions) HasRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for _, r := range aP.Roles {
		if r == role {
			return true
		}
	}
	return false
}

// Returns true if the role is added, and false if it already exists
func (aP *AccountPermissions) AddRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for _, r := range aP.Roles {
		if r == role {
			return false
		}
	}
	aP.Roles = append(aP.Roles, role)
	return true
}

// Returns true if the role is removed, and false if it is not found
func (aP *AccountPermissions) RmRole(role string) bool {
	role = string(word256.RightPadBytes([]byte(role), 32))
	for i, r := range aP.Roles {
		if r == role {
			post := []string{}
			if len(aP.Roles) > i+1 {
				post = aP.Roles[i+1:]
			}
			aP.Roles = append(aP.Roles[:i], post...)
			return true
		}
	}
	return false
}

// Clone clones the account permissions
func (accountPermissions *AccountPermissions) Clone() AccountPermissions {
	// clone base permissions
	basePermissionsClone := accountPermissions.Base
	// clone roles []string
	rolesClone := make([]string, len(accountPermissions.Roles))
	// strings are immutable so copy suffices
	copy(rolesClone, accountPermissions.Roles)

	return AccountPermissions{
		Base:  basePermissionsClone,
		Roles: rolesClone,
	}
}

//--------------------------------------------------------------------------------
// string utilities

// PermFlagToString assumes the permFlag is valid, else returns "#-UNKNOWN-#"
func PermFlagToString(pf PermFlag) (perm string) {
	switch pf {
	case Root:
		perm = "root"
	case Send:
		perm = "send"
	case Call:
		perm = "call"
	case CreateContract:
		perm = "create_contract"
	case CreateAccount:
		perm = "create_account"
	case Bond:
		perm = "bond"
	case Name:
		perm = "name"
	case HasBase:
		perm = "hasBase"
	case SetBase:
		perm = "setBase"
	case UnsetBase:
		perm = "unsetBase"
	case SetGlobal:
		perm = "setGlobal"
	case HasRole:
		perm = "hasRole"
	case AddRole:
		perm = "addRole"
	case RmRole:
		perm = "removeRole"
	default:
		perm = "#-UNKNOWN-#"
	}
	return
}

func PermStringToFlag(perm string) (pf PermFlag, err error) {
	switch strings.ToLower(perm) {
	case "root":
		pf = Root
	case "send":
		pf = Send
	case "call":
		pf = Call
	case "create_contract":
		pf = CreateContract
	case "create_account":
		pf = CreateAccount
	case "bond":
		pf = Bond
	case "name":
		pf = Name
	case "hasbase", "has_base":
		pf = HasBase
	case "setbase", "set_base":
		pf = SetBase
	case "unsetbase", "unset_base":
		pf = UnsetBase
	case "setglobal", "set_global":
		pf = SetGlobal
	case "hasrole", "has_role":
		pf = HasRole
	case "addrole", "add_role":
		pf = AddRole
	case "removerole", "rm_role":
		pf = RmRole
	default:
		err = fmt.Errorf("Unknown permission %s", perm)
	}
	return
}
<|endoftext|>


<commit_before>package discordflo

import (
	"fmt"
	"strings"

	"github.com/bwmarrin/discordgo"
)

// FloFloSession handles the bot and it's commands.
type FloFloSession struct {
	*discordgo.Session
	Token                string
	Prefix               string
	Bot                  bool
	Commands             []*Command
	removeMessageHandler func()
}

// New creates a FloFloSession from a token.
func New(token, prefix string, userbot bool) (*FloFloSession, error) {
	s, err := discordgo.New(token)
	if err != nil {
		return nil, err
	}
	flo := &FloFloSession{
		Session:  s,
		Token:    token,
		Prefix:   prefix,
		Bot:      true,
		Commands: []*Command{},
	}
	flo.ChangeMessageHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
		// Ignore all messages created by other users
		if flo.Bot {
			if m.Author.ID == s.State.User.ID {
				return
			}
		} else {
			if m.Author.ID != s.State.User.ID {
				return
			}
		}

		if len(m.Content) > 0 && (strings.HasPrefix(strings.ToLower(m.Content), strings.ToLower(flo.Prefix))) {
			// Setting values for the commands
			var ctx *Context
			args := strings.Fields(m.Content[len(flo.Prefix):])
			invoked := args[0]
			args = args[1:]
			argstr := m.Content[len(flo.Prefix)+len(invoked):]
			if argstr != "" {
				argstr = argstr[1:]
			}
			channel, err := s.State.Channel(m.ChannelID)
			if err != nil {
				channel, _ = s.State.PrivateChannel(m.ChannelID)
				ctx = &Context{Invoked: invoked, Argstr: argstr, Args: args, Channel: channel, Guild: nil, Mess: m, Sess: flo}
			} else {
				guild, _ := s.State.Guild(channel.GuildID)
				ctx = &Context{Invoked: invoked, Argstr: argstr, Args: args, Channel: channel, Guild: guild, Mess: m, Sess: flo}
			}

			flo.HandleCommands(ctx)
		}
	})
	return flo, err
}

// ChangeMessageHandler handles the changing of the message handler (Lots of handlers.)
func (f *FloFloSession) ChangeMessageHandler(handler func(s *discordgo.Session, m *discordgo.MessageCreate)) {
	undo := f.AddHandler(handler)
	if f.removeMessageHandler != nil {
		f.removeMessageHandler()
	}
	f.removeMessageHandler = undo
}

// AddCommand handles the adding of Commands to the handler.
func (f *FloFloSession) AddCommand(category string, c *Command) {
	c.Category = category
	f.Commands = append(f.Commands, c)
}

// AddPrivateCommand handles the adding of Private Commands to the handler.
func (f *FloFloSession) AddPrivateCommand(category string, check func(ctx *Context) bool, c *Command) {
	c.Check = check
	f.AddCommand(category, c)
}

// HandleSubcommands returns the Context and Command that is being called
// ctx: Context used
// called: Command called
func (f *FloFloSession) HandleSubcommands(ctx *Context, called *Command) (*Context, *Command) {
	if len(ctx.Args) != 0 {
		var scalled *Command
		ok := false
		for _, c := range called.Subcommands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Args[0]) {
				ok = true
				scalled = c
				break
			}
		}
		if ok {
			ctx.Argstr = ctx.Argstr[len(ctx.Args[0]):]
			if ctx.Argstr != "" {
				ctx.Argstr = ctx.Argstr[1:]
			}
			ctx.Invoked += " " + ctx.Args[0]
			ctx.Args = ctx.Args[1:]
			return f.HandleSubcommands(ctx, scalled)
		}
	}
	return ctx, called
}

// HandleCommands handles the Context and calls Command
// ctx: Context used
func (f *FloFloSession) HandleCommands(ctx *Context) {
	if strings.ToLower(ctx.Invoked) == "help" {
		go f.HelpFunction(ctx)
	} else {
		var called *Command
		ok := false
		for _, c := range f.Commands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Invoked) {
				ok = true
				called = c
				break
			}
		}
		if ok {
			rctx, rcalled := f.HandleSubcommands(ctx, called)
			if rcalled.Check(ctx) {
				go rcalled.OnMessage(rctx)
			}
		}
	}
}

// CreateEmbed handles the easy creation of Embeds.
func (f *FloFloSession) CreateEmbed(ctx *Context) *discordgo.MessageEmbed {
	color := ctx.Sess.State.UserColor(f.State.User.ID, ctx.Mess.ChannelID)
	return &discordgo.MessageEmbed{Color: color}
}

// HelpFunction handles the Help command for the CommandHandler
// ctx: Context used
func (f *FloFloSession) HelpFunction(ctx *Context) {
	embed := f.CreateEmbed(ctx)
	var desc string
	if len(ctx.Args) != 0 {
		ctx.Invoked = ""
		command := ctx.Args[0]
		var called *Command
		ok := false
		for _, c := range f.Commands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Args[0]) {
				ok = true
				called = c
				break
			}
		}
		ctx.Args = ctx.Args[1:]
		if ok {
			sctx, scalled := f.HandleSubcommands(ctx, called)
			if scalled.Detailed == "" {
				scalled.Detailed = scalled.Description
			}
			if scalled.Check(ctx) {
				desc = fmt.Sprintf("`%s%s %s`\n%s", f.Prefix, command+sctx.Invoked, scalled.Usage, scalled.Detailed)
			}
			if len(scalled.Subcommands) != 0 {
				desc += "\n\nSubcommands:"
				desc += fmt.Sprintf(" `%shelp %s [subcommand]` for more info!", f.Prefix, command+sctx.Invoked)
				for _, k := range scalled.Subcommands {
					if k.Check(ctx) {
						desc += fmt.Sprintf("\n`%s%s %s %s` - %s", f.Prefix, command, k.Name, k.Usage, k.Description)
					}
				}
			}
		} else {
			desc = "No command called `" + command + "` found!"
		}
	} else {
		desc = fmt.Sprintf(" `%shelp [command]` for more info!", f.Prefix)
		sorted := make(map[string][]*Command)
		for _, c := range f.Commands {
			if c.Check(ctx) {
				if c.Category == "" {
					sorted["Uncategorized"] = append(sorted["Uncategorized"], c)
				} else {
					sorted[c.Category] = append(sorted[c.Category], c)
				}
			}
		}
		for k, v := range sorted {
			var fdesc string
			field := &discordgo.MessageEmbedField{Name: k + ":"}
			for _, command := range v {
				if command.Check(ctx) {
					fdesc += fmt.Sprintf("\n`%s%s %s` - %s", f.Prefix, command.Name, command.Usage, command.Description)
					field.Value = fdesc[1:]
				}
			}
			embed.Fields = append(embed.Fields, field)
		}
	}
	embed.Author = &discordgo.MessageEmbedAuthor{Name: f.State.User.Username, IconURL: discordgo.EndpointUserAvatar(f.State.User.ID, f.State.User.Avatar)}
	embed.Description = desc
	f.ChannelMessageSendEmbed(ctx.Mess.ChannelID, embed)
}
<commit_msg>ADD: Recovering from panicked commands<commit_after>package discordflo

import (
	"fmt"
	"log"
	"strings"

	"github.com/bwmarrin/discordgo"
)

// FloFloSession handles the bot and it's commands.
type FloFloSession struct {
	*discordgo.Session
	Token                string
	Prefix               string
	Bot                  bool
	Commands             []*Command
	removeMessageHandler func()
}

// New creates a FloFloSession from a token.
func New(token, prefix string, userbot bool) (*FloFloSession, error) {
	s, err := discordgo.New(token)
	if err != nil {
		return nil, err
	}
	flo := &FloFloSession{
		Session:  s,
		Token:    token,
		Prefix:   prefix,
		Bot:      true,
		Commands: []*Command{},
	}
	flo.ChangeMessageHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
		// Ignore all messages created by other users
		if flo.Bot {
			if m.Author.ID == s.State.User.ID {
				return
			}
		} else {
			if m.Author.ID != s.State.User.ID {
				return
			}
		}

		if len(m.Content) > 0 && (strings.HasPrefix(strings.ToLower(m.Content), strings.ToLower(flo.Prefix))) {
			// Setting values for the commands
			var ctx *Context
			args := strings.Fields(m.Content[len(flo.Prefix):])
			invoked := args[0]
			args = args[1:]
			argstr := m.Content[len(flo.Prefix)+len(invoked):]
			if argstr != "" {
				argstr = argstr[1:]
			}
			channel, err := s.State.Channel(m.ChannelID)
			if err != nil {
				channel, _ = s.State.PrivateChannel(m.ChannelID)
				ctx = &Context{Invoked: invoked, Argstr: argstr, Args: args, Channel: channel, Guild: nil, Mess: m, Sess: flo}
			} else {
				guild, _ := s.State.Guild(channel.GuildID)
				ctx = &Context{Invoked: invoked, Argstr: argstr, Args: args, Channel: channel, Guild: guild, Mess: m, Sess: flo}
			}
			go flo.HandleCommands(ctx)
		}
	})
	return flo, err
}

// ChangeMessageHandler handles the changing of the message handler (Lots of handlers.)
func (f *FloFloSession) ChangeMessageHandler(handler func(s *discordgo.Session, m *discordgo.MessageCreate)) {
	undo := f.AddHandler(handler)
	if f.removeMessageHandler != nil {
		f.removeMessageHandler()
	}
	f.removeMessageHandler = undo
}

// AddCommand handles the adding of Commands to the handler.
func (f *FloFloSession) AddCommand(category string, c *Command) {
	c.Category = category
	f.Commands = append(f.Commands, c)
}

// AddPrivateCommand handles the adding of Private Commands to the handler.
func (f *FloFloSession) AddPrivateCommand(category string, check func(ctx *Context) bool, c *Command) {
	c.Check = check
	f.AddCommand(category, c)
}

// HandleSubcommands returns the Context and Command that is being called
// ctx: Context used
// called: Command called
func (f *FloFloSession) HandleSubcommands(ctx *Context, called *Command) (*Context, *Command) {
	if len(ctx.Args) != 0 {
		var scalled *Command
		ok := false
		for _, c := range called.Subcommands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Args[0]) {
				ok = true
				scalled = c
				break
			}
		}
		if ok {
			ctx.Argstr = ctx.Argstr[len(ctx.Args[0]):]
			if ctx.Argstr != "" {
				ctx.Argstr = ctx.Argstr[1:]
			}
			ctx.Invoked += " " + ctx.Args[0]
			ctx.Args = ctx.Args[1:]
			return f.HandleSubcommands(ctx, scalled)
		}
	}
	return ctx, called
}

// HandleCommands handles the Context and calls Command
// ctx: Context used
func (f *FloFloSession) HandleCommands(ctx *Context) {
	if strings.ToLower(ctx.Invoked) == "help" {
		go f.HelpFunction(ctx)
	} else {
		var called *Command
		ok := false
		for _, c := range f.Commands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Invoked) {
				ok = true
				called = c
				break
			}
		}
		if ok {
			rctx, rcalled := f.HandleSubcommands(ctx, called)
			if rcalled.Check(ctx) {
				defer func() {
					if x := recover(); x != nil {
						log.Printf("Panicked and recovered: %v", x)
					}
				}()
				rcalled.OnMessage(rctx)
			}
		}
	}
}

// CreateEmbed handles the easy creation of Embeds.
func (f *FloFloSession) CreateEmbed(ctx *Context) *discordgo.MessageEmbed {
	color := ctx.Sess.State.UserColor(f.State.User.ID, ctx.Mess.ChannelID)
	return &discordgo.MessageEmbed{Color: color}
}

// HelpFunction handles the Help command for the CommandHandler
// ctx: Context used
func (f *FloFloSession) HelpFunction(ctx *Context) {
	embed := f.CreateEmbed(ctx)
	var desc string
	if len(ctx.Args) != 0 {
		ctx.Invoked = ""
		command := ctx.Args[0]
		var called *Command
		ok := false
		for _, c := range f.Commands {
			if strings.ToLower(c.Name) == strings.ToLower(ctx.Args[0]) {
				ok = true
				called = c
				break
			}
		}
		ctx.Args = ctx.Args[1:]
		if ok {
			sctx, scalled := f.HandleSubcommands(ctx, called)
			if scalled.Detailed == "" {
				scalled.Detailed = scalled.Description
			}
			if scalled.Check(ctx) {
				desc = fmt.Sprintf("`%s%s %s`\n%s", f.Prefix, command+sctx.Invoked, scalled.Usage, scalled.Detailed)
			}
			if len(scalled.Subcommands) != 0 {
				desc += "\n\nSubcommands:"
				desc += fmt.Sprintf(" `%shelp %s [subcommand]` for more info!", f.Prefix, command+sctx.Invoked)
				for _, k := range scalled.Subcommands {
					if k.Check(ctx) {
						desc += fmt.Sprintf("\n`%s%s %s %s` - %s", f.Prefix, command, k.Name, k.Usage, k.Description)
					}
				}
			}
		} else {
			desc = "No command called `" + command + "` found!"
		}
	} else {
		desc = fmt.Sprintf(" `%shelp [command]` for more info!", f.Prefix)
		sorted := make(map[string][]*Command)
		for _, c := range f.Commands {
			if c.Check(ctx) {
				if c.Category == "" {
					sorted["Uncategorized"] = append(sorted["Uncategorized"], c)
				} else {
					sorted[c.Category] = append(sorted[c.Category], c)
				}
			}
		}
		for k, v := range sorted {
			var fdesc string
			field := &discordgo.MessageEmbedField{Name: k + ":"}
			for _, command := range v {
				if command.Check(ctx) {
					fdesc += fmt.Sprintf("\n`%s%s %s` - %s", f.Prefix, command.Name, command.Usage, command.Description)
					field.Value = fdesc[1:]
				}
			}
			embed.Fields = append(embed.Fields, field)
		}
	}
	embed.Author = &discordgo.MessageEmbedAuthor{Name: f.State.User.Username, IconURL: discordgo.EndpointUserAvatar(f.State.User.ID, f.State.User.Avatar)}
	embed.Description = desc
	f.ChannelMessageSendEmbed(ctx.Mess.ChannelID, embed)
}
<|endoftext|>


<commit_before>package search

import (
	"fmt"
	"net/http"

	"github.com/algolia/algoliasearch-client-go/algolia/call"
	iopt "github.com/algolia/algoliasearch-client-go/algolia/internal/opt"
	"github.com/algolia/algoliasearch-client-go/algolia/opt"
)

// ListClusters list all the clusters managed by MCM.
func (c *Client) ListClusters(opts ...interface{}) (res ListClustersRes, err error) {
	err = c.transport.Request(&res, http.MethodGet, "/1/clusters", nil, call.Read, opts...)
	return
}

// ListUserIDs list all the userIDs managed by MCM.
//
// Pagination can be implemented using the opt.Page and opt.HitsPerPage option
// parameters.
func (c *Client) ListUserIDs(opts ...interface{}) (res ListUserIDsRes, err error) {
	if page := iopt.ExtractPage(opts...); page != nil {
		opts = opt.InsertExtraURLParam(opts, "page", page.Get())
	}
	if hitsPerPage := iopt.ExtractHitsPerPage(opts...); hitsPerPage != nil {
		opts = opt.InsertExtraURLParam(opts, "hitsPerPage", hitsPerPage.Get())
	}
	err = c.transport.Request(&res, http.MethodGet, "/1/clusters/mapping", nil, call.Read, opts...)
	return
}

// GetUserID retrieves the user, managed by MCM, according to the given userID.
func (c *Client) GetUserID(userID string, opts ...interface{}) (res UserID, err error) {
	path := fmt.Sprintf("/1/clusters/mapping/%s", userID)
	err = c.transport.Request(&res, http.MethodGet, path, nil, call.Read, opts...)
	return
}

// AssignUserID assign the given userID to the given cluster.
func (c *Client) AssignUserID(userID, clusterName string, opts ...interface{}) (res AssignUserIDRes, err error) {
	opts = opt.InsertExtraHeader(opts, "X-Algolia-User-ID", userID)
	body := map[string]string{"cluster": clusterName}
	err = c.transport.Request(&res, http.MethodPost, "/1/clusters/mapping", body, call.Write, opts...)
	return
}

// RemoveUserID deletes the given userID managed by MCM.
func (c *Client) RemoveUserID(userID string, opts ...interface{}) (res RemoveUserIDRes, err error) {
	opts = opt.InsertExtraHeader(opts, "X-Algolia-User-ID", userID)
	err = c.transport.Request(&res, http.MethodDelete, "/1/clusters/mapping", nil, call.Write, opts...)
	return
}

// GetTopUserIDs retrieves the top list of userIDs managed by MCM.
func (c *Client) GetTopUserIDs(opts ...interface{}) (res TopUserIDs, err error) {
	err = c.transport.Request(&res, "GET", "/1/clusters/mapping/top", nil, call.Read, opts...)
	return
}

func (c *Client) SearchUserIDs(query string, opts ...interface{}) (res SearchUserIDRes, err error) {
	req := newSearchUserIDsReq(query, opts...)
	err = c.transport.Request(&res, http.MethodPost, "/1/clusters/mapping/search", req, call.Read, opts...)
	return
}
<commit_msg>refactor: simplify option extraction in Client.ListUserIDs<commit_after>package search

import (
	"fmt"
	"net/http"

	"github.com/algolia/algoliasearch-client-go/algolia/call"
	iopt "github.com/algolia/algoliasearch-client-go/algolia/internal/opt"
	"github.com/algolia/algoliasearch-client-go/algolia/opt"
)

// ListClusters list all the clusters managed by MCM.
func (c *Client) ListClusters(opts ...interface{}) (res ListClustersRes, err error) {
	err = c.transport.Request(&res, http.MethodGet, "/1/clusters", nil, call.Read, opts...)
	return
}

// ListUserIDs list all the userIDs managed by MCM.
//
// Pagination can be implemented using the opt.Page and opt.HitsPerPage option
// parameters.
func (c *Client) ListUserIDs(opts ...interface{}) (res ListUserIDsRes, err error) {
	opts = opt.InsertExtraURLParam(opts, "page", iopt.ExtractPage(opts...).Get())
	opts = opt.InsertExtraURLParam(opts, "hitsPerPage", iopt.ExtractHitsPerPage(opts...).Get())
	err = c.transport.Request(&res, http.MethodGet, "/1/clusters/mapping", nil, call.Read, opts...)
	return
}

// GetUserID retrieves the user, managed by MCM, according to the given userID.
func (c *Client) GetUserID(userID string, opts ...interface{}) (res UserID, err error) {
	path := fmt.Sprintf("/1/clusters/mapping/%s", userID)
	err = c.transport.Request(&res, http.MethodGet, path, nil, call.Read, opts...)
	return
}

// AssignUserID assign the given userID to the given cluster.
func (c *Client) AssignUserID(userID, clusterName string, opts ...interface{}) (res AssignUserIDRes, err error) {
	opts = opt.InsertExtraHeader(opts, "X-Algolia-User-ID", userID)
	body := map[string]string{"cluster": clusterName}
	err = c.transport.Request(&res, http.MethodPost, "/1/clusters/mapping", body, call.Write, opts...)
	return
}

// RemoveUserID deletes the given userID managed by MCM.
func (c *Client) RemoveUserID(userID string, opts ...interface{}) (res RemoveUserIDRes, err error) {
	opts = opt.InsertExtraHeader(opts, "X-Algolia-User-ID", userID)
	err = c.transport.Request(&res, http.MethodDelete, "/1/clusters/mapping", nil, call.Write, opts...)
	return
}

// GetTopUserIDs retrieves the top list of userIDs managed by MCM.
func (c *Client) GetTopUserIDs(opts ...interface{}) (res TopUserIDs, err error) {
	err = c.transport.Request(&res, "GET", "/1/clusters/mapping/top", nil, call.Read, opts...)
	return
}

func (c *Client) SearchUserIDs(query string, opts ...interface{}) (res SearchUserIDRes, err error) {
	req := newSearchUserIDsReq(query, opts...)
	err = c.transport.Request(&res, http.MethodPost, "/1/clusters/mapping/search", req, call.Read, opts...)
	return
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

func resource_aws_internet_gateway_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Create the gateway
	log.Printf("[DEBUG] Creating internet gateway")
	resp, err := ec2conn.CreateInternetGateway(nil)
	if err != nil {
		return nil, fmt.Errorf("Error creating subnet: %s", err)
	}

	// Get the ID and store it
	ig := &resp.InternetGateway
	s.ID = ig.InternetGatewayId
	log.Printf("[INFO] InternetGateway ID: %s", s.ID)

	// Update our attributes and return
	return resource_aws_internet_gateway_update(s, d, meta)
}

func resource_aws_internet_gateway_update(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff so we have the latest attributes
	rs := s.MergeDiff(d)

	// A note on the states below: the AWS docs (as of July, 2014) say
	// that the states would be: attached, attaching, detached, detaching,
	// but when running, I noticed that the state is usually "available" when
	// it is attached.

	// If we're already attached, detach it first
	if err := resource_aws_internet_gateway_detach(ec2conn, s); err != nil {
		return s, err
	}

	// Set the VPC ID to empty since we're detached at this point
	delete(rs.Attributes, "vpc_id")

	if attr, ok := d.Attributes["vpc_id"]; ok && attr.New != "" {
		err := resource_aws_internet_gateway_attach(ec2conn, s, attr.New)
		if err != nil {
			return s, err
		}

		rs.Attributes["vpc_id"] = attr.New
	}

	return resource_aws_internet_gateway_update_state(rs, nil)
}

func resource_aws_internet_gateway_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Detach if it is attached
	if err := resource_aws_internet_gateway_detach(ec2conn, s); err != nil {
		return err
	}

	log.Printf("[INFO] Deleting Internet Gateway: %s", s.ID)
	if _, err := ec2conn.DeleteInternetGateway(s.ID); err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
			return nil
		}

		return fmt.Errorf("Error deleting internet gateway: %s", err)
	}

	// Wait for the internet gateway to actually delete
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to delete", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"available"},
		Target:  "",
		Refresh: IGStateRefreshFunc(ec2conn, s.ID),
		Timeout: 10 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to destroy",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	igRaw, _, err := IGStateRefreshFunc(ec2conn, s.ID)()
	if err != nil {
		return s, err
	}
	if igRaw == nil {
		return nil, nil
	}

	ig := igRaw.(*ec2.InternetGateway)
	return resource_aws_internet_gateway_update_state(s, ig)
}

func resource_aws_internet_gateway_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		Attrs: map[string]diff.AttrType{
			"vpc_id": diff.AttrTypeUpdate,
		},
	}

	return b.Diff(s, c)
}

func resource_aws_internet_gateway_attach(
	ec2conn *ec2.EC2,
	s *terraform.ResourceState,
	vpcId string) error {
	log.Printf(
		"[INFO] Attaching Internet Gateway '%s' to VPC '%s'",
		s.ID,
		vpcId)
	_, err := ec2conn.AttachInternetGateway(s.ID, vpcId)
	if err != nil {
		return err
	}

	// Wait for it to be fully attached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to attach", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"detached", "attaching"},
		Target:  "available",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to attach: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_detach(
	ec2conn *ec2.EC2,
	s *terraform.ResourceState) error {
	if s.Attributes["vpc_id"] == "" {
		return nil
	}

	log.Printf(
		"[INFO] Detaching Internet Gateway '%s' from VPC '%s'",
		s.ID,
		s.Attributes["vpc_id"])
	_, err := ec2conn.DetachInternetGateway(s.ID, s.Attributes["vpc_id"])
	if err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok {
			if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				err = nil
			} else if ec2err.Code == "Gateway.NotAttached" {
				err = nil
			}
		}

		if err != nil {
			return err
		}
	}

	delete(s.Attributes, "vpc_id")

	// Wait for it to be fully detached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"attached", "detaching", "available"},
		Target:  "detached",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to detach: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_update_state(
	s *terraform.ResourceState,
	ig *ec2.InternetGateway) (*terraform.ResourceState, error) {
	if s.Attributes["vpc_id"] != "" {
		// We belong to a VPC
		s.Dependencies = []terraform.ResourceDependency{
			terraform.ResourceDependency{ID: s.Attributes["vpc_id"]},
		}
	}

	return s, nil
}

// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an internet gateway.
func IGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]
		return ig, "available", nil
	}
}

// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
// watch the state of an internet gateway's attachment.
func IGAttachStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]

		if len(ig.Attachments) == 0 {
			// No attachments, we're detached
			return ig, "detached", nil
		}

		return ig, ig.Attachments[0].State, nil
	}
}
<commit_msg>providers/aws: internet gateway is more robust<commit_after>package aws

import (
	"fmt"
	"log"
	"time"

	"github.com/hashicorp/terraform/helper/diff"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
	"github.com/mitchellh/goamz/ec2"
)

func resource_aws_internet_gateway_create(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Create the gateway
	log.Printf("[DEBUG] Creating internet gateway")
	resp, err := ec2conn.CreateInternetGateway(nil)
	if err != nil {
		return nil, fmt.Errorf("Error creating subnet: %s", err)
	}

	// Get the ID and store it
	ig := &resp.InternetGateway
	s.ID = ig.InternetGatewayId
	log.Printf("[INFO] InternetGateway ID: %s", s.ID)

	// Update our attributes and return
	return resource_aws_internet_gateway_update(s, d, meta)
}

func resource_aws_internet_gateway_update(
	s *terraform.ResourceState,
	d *terraform.ResourceDiff,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Merge the diff so we have the latest attributes
	rs := s.MergeDiff(d)

	// A note on the states below: the AWS docs (as of July, 2014) say
	// that the states would be: attached, attaching, detached, detaching,
	// but when running, I noticed that the state is usually "available" when
	// it is attached.

	// If we're already attached, detach it first
	if err := resource_aws_internet_gateway_detach(ec2conn, s); err != nil {
		return s, err
	}

	// Set the VPC ID to empty since we're detached at this point
	delete(rs.Attributes, "vpc_id")

	if attr, ok := d.Attributes["vpc_id"]; ok && attr.New != "" {
		err := resource_aws_internet_gateway_attach(ec2conn, s, attr.New)
		if err != nil {
			return s, err
		}

		rs.Attributes["vpc_id"] = attr.New
	}

	return resource_aws_internet_gateway_update_state(rs, nil)
}

func resource_aws_internet_gateway_destroy(
	s *terraform.ResourceState,
	meta interface{}) error {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	// Detach if it is attached
	if err := resource_aws_internet_gateway_detach(ec2conn, s); err != nil {
		return err
	}

	log.Printf("[INFO] Deleting Internet Gateway: %s", s.ID)
	if _, err := ec2conn.DeleteInternetGateway(s.ID); err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
			return nil
		}

		return fmt.Errorf("Error deleting internet gateway: %s", err)
	}

	// Wait for the internet gateway to actually delete
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to delete", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"available"},
		Target:  "",
		Refresh: IGStateRefreshFunc(ec2conn, s.ID),
		Timeout: 10 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to destroy",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_refresh(
	s *terraform.ResourceState,
	meta interface{}) (*terraform.ResourceState, error) {
	p := meta.(*ResourceProvider)
	ec2conn := p.ec2conn

	igRaw, _, err := IGStateRefreshFunc(ec2conn, s.ID)()
	if err != nil {
		return s, err
	}
	if igRaw == nil {
		return nil, nil
	}

	ig := igRaw.(*ec2.InternetGateway)
	return resource_aws_internet_gateway_update_state(s, ig)
}

func resource_aws_internet_gateway_diff(
	s *terraform.ResourceState,
	c *terraform.ResourceConfig,
	meta interface{}) (*terraform.ResourceDiff, error) {
	b := &diff.ResourceBuilder{
		Attrs: map[string]diff.AttrType{
			"vpc_id": diff.AttrTypeUpdate,
		},
	}

	return b.Diff(s, c)
}

func resource_aws_internet_gateway_attach(
	ec2conn *ec2.EC2,
	s *terraform.ResourceState,
	vpcId string) error {
	log.Printf(
		"[INFO] Attaching Internet Gateway '%s' to VPC '%s'",
		s.ID,
		vpcId)
	_, err := ec2conn.AttachInternetGateway(s.ID, vpcId)
	if err != nil {
		return err
	}

	// Wait for it to be fully attached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to attach", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"detached", "attaching"},
		Target:  "available",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to attach: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_detach(
	ec2conn *ec2.EC2,
	s *terraform.ResourceState) error {
	if s.Attributes["vpc_id"] == "" {
		return nil
	}

	log.Printf(
		"[INFO] Detaching Internet Gateway '%s' from VPC '%s'",
		s.ID,
		s.Attributes["vpc_id"])
	wait := true
	_, err := ec2conn.DetachInternetGateway(s.ID, s.Attributes["vpc_id"])
	if err != nil {
		ec2err, ok := err.(*ec2.Error)
		if ok {
			if ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				err = nil
				wait = false
			} else if ec2err.Code == "Gateway.NotAttached" {
				err = nil
				wait = false
			}
		}

		if err != nil {
			return err
		}
	}

	delete(s.Attributes, "vpc_id")

	if !wait {
		return nil
	}

	// Wait for it to be fully detached before continuing
	log.Printf("[DEBUG] Waiting for internet gateway (%s) to detach", s.ID)
	stateConf := &resource.StateChangeConf{
		Pending: []string{"attached", "detaching", "available"},
		Target:  "detached",
		Refresh: IGAttachStateRefreshFunc(ec2conn, s.ID),
		Timeout: 1 * time.Minute,
	}
	if _, err := stateConf.WaitForState(); err != nil {
		return fmt.Errorf(
			"Error waiting for internet gateway (%s) to detach: %s",
			s.ID, err)
	}

	return nil
}

func resource_aws_internet_gateway_update_state(
	s *terraform.ResourceState,
	ig *ec2.InternetGateway) (*terraform.ResourceState, error) {
	if s.Attributes["vpc_id"] != "" {
		// We belong to a VPC
		s.Dependencies = []terraform.ResourceDependency{
			terraform.ResourceDependency{ID: s.Attributes["vpc_id"]},
		}
	}

	return s, nil
}

// IGStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
// an internet gateway.
func IGStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]
		return ig, "available", nil
	}
}

// IGAttachStateRefreshFunc returns a resource.StateRefreshFunc that is used
// watch the state of an internet gateway's attachment.
func IGAttachStateRefreshFunc(conn *ec2.EC2, id string) resource.StateRefreshFunc {
	return func() (interface{}, string, error) {
		resp, err := conn.DescribeInternetGateways([]string{id}, ec2.NewFilter())
		if err != nil {
			ec2err, ok := err.(*ec2.Error)
			if ok && ec2err.Code == "InvalidInternetGatewayID.NotFound" {
				resp = nil
			} else {
				log.Printf("[ERROR] Error on IGStateRefresh: %s", err)
				return nil, "", err
			}
		}

		if resp == nil {
			// Sometimes AWS just has consistency issues and doesn't see
			// our instance yet. Return an empty state.
			return nil, "", nil
		}

		ig := &resp.InternetGateways[0]

		if len(ig.Attachments) == 0 {
			// No attachments, we're detached
			return ig, "detached", nil
		}

		return ig, ig.Attachments[0].State, nil
	}
}
<|endoftext|>


<commit_before>package heroku

import (
	"fmt"
	"testing"

	"github.com/bgentry/heroku-go"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccHerokuDrain_Basic(t *testing.T) {
	var drain heroku.LogDrain

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckHerokuDrainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccCheckHerokuDrainConfig_basic,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckHerokuDrainExists("heroku_drain.foobar", &drain),
					testAccCheckHerokuDrainAttributes(&drain),
					resource.TestCheckResourceAttr(
						"heroku_drain.foobar", "url", "syslog://terraform.example.com:1234"),
					resource.TestCheckResourceAttr(
						"heroku_drain.foobar", "app", "terraform-test-app"),
					resource.TestCheckResourceAttr(
						"heroku_drain.foobar", "token", "foo-bar-baz-qux"),
				),
			},
		},
	})
}

func testAccCheckHerokuDrainDestroy(s *terraform.State) error {
	client := testAccProvider.client

	for _, rs := range s.Resources {
		if rs.Type != "heroku_drain" {
			continue
		}

		_, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID)

		if err == nil {
			return fmt.Errorf("Drain still exists")
		}
	}

	return nil
}

func testAccCheckHerokuDrainAttributes(Drain *heroku.LogDrain) resource.TestCheckFunc {
	return func(s *terraform.State) error {

		if Drain.URL != "syslog://terraform.example.com:1234" {
			return fmt.Errorf("Bad URL: %s", Drain.URL)
		}

		return nil
	}
}

func testAccCheckHerokuDrainExists(n string, Drain *heroku.LogDrain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.Resources[n]

		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.ID == "" {
			return fmt.Errorf("No Drain ID is set")
		}

		client := testAccProvider.client

		foundDrain, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID)

		if err != nil {
			return err
		}

		if foundDrain.Id != rs.ID {
			return fmt.Errorf("Drain not found")
		}

		*Drain = *foundDrain

		return nil
	}
}

const testAccCheckHerokuDrainConfig_basic = `
resource "heroku_app" "foobar" {
    name = "terraform-test-app"
}

resource "heroku_drain" "foobar" {
    app = "${heroku_app.foobar.name}"
    url = "syslog://terraform.example.com:1234"
}`
<commit_msg>providers/heroku: fix drain test<commit_after>package heroku

import (
	"fmt"
	"testing"

	"github.com/bgentry/heroku-go"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccHerokuDrain_Basic(t *testing.T) {
	var drain heroku.LogDrain

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccCheckHerokuDrainDestroy,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccCheckHerokuDrainConfig_basic,
				Check: resource.ComposeTestCheckFunc(
					testAccCheckHerokuDrainExists("heroku_drain.foobar", &drain),
					testAccCheckHerokuDrainAttributes(&drain),
					resource.TestCheckResourceAttr(
						"heroku_drain.foobar", "url", "syslog://terraform.example.com:1234"),
					resource.TestCheckResourceAttr(
						"heroku_drain.foobar", "app", "terraform-test-app"),
				),
			},
		},
	})
}

func testAccCheckHerokuDrainDestroy(s *terraform.State) error {
	client := testAccProvider.client

	for _, rs := range s.Resources {
		if rs.Type != "heroku_drain" {
			continue
		}

		_, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID)

		if err == nil {
			return fmt.Errorf("Drain still exists")
		}
	}

	return nil
}

func testAccCheckHerokuDrainAttributes(Drain *heroku.LogDrain) resource.TestCheckFunc {
	return func(s *terraform.State) error {

		if Drain.URL != "syslog://terraform.example.com:1234" {
			return fmt.Errorf("Bad URL: %s", Drain.URL)
		}

		if Drain.Token == "" {
			return fmt.Errorf("No token: %#v", Drain)
		}

		return nil
	}
}

func testAccCheckHerokuDrainExists(n string, Drain *heroku.LogDrain) resource.TestCheckFunc {
	return func(s *terraform.State) error {
		rs, ok := s.Resources[n]

		if !ok {
			return fmt.Errorf("Not found: %s", n)
		}

		if rs.ID == "" {
			return fmt.Errorf("No Drain ID is set")
		}

		client := testAccProvider.client

		foundDrain, err := client.LogDrainInfo(rs.Attributes["app"], rs.ID)

		if err != nil {
			return err
		}

		if foundDrain.Id != rs.ID {
			return fmt.Errorf("Drain not found")
		}

		*Drain = *foundDrain

		return nil
	}
}

const testAccCheckHerokuDrainConfig_basic = `
resource "heroku_app" "foobar" {
    name = "terraform-test-app"
}

resource "heroku_drain" "foobar" {
    app = "${heroku_app.foobar.name}"
    url = "syslog://terraform.example.com:1234"
}`
<|endoftext|>


<commit_before>package slackboard

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync/atomic"
)

type SlackPayloadAttachmentsField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

type SlackPayloadAttachments struct {
	Fallback string `json:"fallback"`
	Color    string `json:"color"`
	Pretext  string `json:"pretext"`

	AuthorName string `json:"author_name"`
	AuthorLink string `json:"author_link"`
	AuthorIcon string `json:"author_icon"`

	Title     string `json:"title"`
	TitleLink string `json:"title_link"`
	Text      string `json:"text"`

	Field []SlackPayloadAttachmentsField `json:"fields"`

	ImageUrl string `json:"image_url"`
	ThumbUrl string `json:"thumb_url"`
}

type SlackPayload struct {
	Channel     string                    `json:"channel"`
	Username    string                    `json:"username,omitempty"`
	IconEmoji   string                    `json:"icon_emoji,omitempty"`
	Text        string                    `json:"text"`
	Parse       string                    `json:"parse,omitempty"`
	Attachments []SlackPayloadAttachments `json:"attachments"`
}

type SlackboardPayload struct {
	Tag   string `json:"tag"`
	Host  string `json:"host,omitempty"`
	Text  string `json:"text"`
	Sync  bool   `json:"sync,omitempty"`
	Level string `json:"level"`
	Title string `json:"title,omitempty"`
}

type SlackboardDirectPayload struct {
	Payload SlackPayload `json:"payload"`
	Sync    bool         `json:"sync,omitempty"`
}

func sendNotification2Slack(payload *SlackPayload) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	client := &http.Client{}

	resp, err := client.Post(
		ConfSlackboard.Core.SlackURL,
		"application/json",
		strings.NewReader(string(body)))

	if err != nil {
		return err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Slack is not available:%s", resp.Status)
	}

	return nil
}

func NotifyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardPayload
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "failed to read request-body", http.StatusInternalServerError)
		return
	}
	err = json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest(r, req.Tag)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	LogError.Debug("find tag")
	sent := false
	for i, tag := range ConfSlackboard.Tags {
		if tag.Tag == req.Tag {
			atomic.AddUint64(&Topics[i].Count, 1)
			payload := &SlackPayload{
				Channel:   tag.Channel,
				Username:  tag.Username,
				IconEmoji: tag.IconEmoji,
				Text:      req.Text,
				Parse:     tag.Parse,
			}

			var (
				color string
			)

			levelToColorMap := map[string]string{
				"info": "#00ff00", // green
				"warn": "#ffdd00", // yellow
				"crit": "#ff0000", // red
			}

			if color_, ok := levelToColorMap[req.Level]; ok {
				payload.Text = ""
				color = color_
			}

			if req.Title != "" {
				payload.Text = ""
			}

			if color != "" || req.Title != "" {
				payload.Attachments = make([]SlackPayloadAttachments, 1)
				payload.Attachments[0] = SlackPayloadAttachments{
					Color: color,
					Title: req.Title,
					Text:  req.Text,
				}
			}

			if req.Sync {
				err := sendNotification2Slack(payload)
				if err != nil {
					sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
					return
				}
				sent = true
			} else {
				go func() {
					err := sendNotification2Slack(payload)
					if err != nil {
						LogError.Error(fmt.Sprintf("failed to post message to slack:%s", err.Error()))
					}
				}()
			}
		}

	}

	LogError.Debug("response to client")

	if req.Sync {
		if sent {
			sendResponse(w, "ok", http.StatusOK)
		} else {
			msg := fmt.Sprintf("tag:%s is not found", req.Tag)
			sendResponse(w, msg, http.StatusBadRequest)
		}

	} else {
		sendResponse(w, "ok", http.StatusOK)
	}
}

func NotifyDirectlyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-directly-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardDirectPayload
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "failed to read request-body", http.StatusInternalServerError)
		return
	}
	err = json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest(r, req.Payload.Channel)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	if req.Sync {
		err := sendNotification2Slack(&req.Payload)
		if err != nil {
			sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
			return
		}
	} else {
		go func() {
			err := sendNotification2Slack(&req.Payload)
			if err != nil {
				LogError.Error(fmt.Sprintf("failed to post message to slack:%s", err.Error()))
			}
		}()
	}

	LogError.Debug("response to client")
	sendResponse(w, "ok", http.StatusOK)
}
<commit_msg>use http.DefaultClient.<commit_after>package slackboard

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync/atomic"
)

type SlackPayloadAttachmentsField struct {
	Title string `json:"title"`
	Value string `json:"value"`
	Short bool   `json:"short"`
}

type SlackPayloadAttachments struct {
	Fallback string `json:"fallback"`
	Color    string `json:"color"`
	Pretext  string `json:"pretext"`

	AuthorName string `json:"author_name"`
	AuthorLink string `json:"author_link"`
	AuthorIcon string `json:"author_icon"`

	Title     string `json:"title"`
	TitleLink string `json:"title_link"`
	Text      string `json:"text"`

	Field []SlackPayloadAttachmentsField `json:"fields"`

	ImageUrl string `json:"image_url"`
	ThumbUrl string `json:"thumb_url"`
}

type SlackPayload struct {
	Channel     string                    `json:"channel"`
	Username    string                    `json:"username,omitempty"`
	IconEmoji   string                    `json:"icon_emoji,omitempty"`
	Text        string                    `json:"text"`
	Parse       string                    `json:"parse,omitempty"`
	Attachments []SlackPayloadAttachments `json:"attachments"`
}

type SlackboardPayload struct {
	Tag   string `json:"tag"`
	Host  string `json:"host,omitempty"`
	Text  string `json:"text"`
	Sync  bool   `json:"sync,omitempty"`
	Level string `json:"level"`
	Title string `json:"title,omitempty"`
}

type SlackboardDirectPayload struct {
	Payload SlackPayload `json:"payload"`
	Sync    bool         `json:"sync,omitempty"`
}

func sendNotification2Slack(payload *SlackPayload) error {
	body, err := json.Marshal(payload)
	if err != nil {
		return err
	}

	client := http.DefaultClient

	resp, err := client.Post(
		ConfSlackboard.Core.SlackURL,
		"application/json",
		strings.NewReader(string(body)))

	if err != nil {
		return err
	}

	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Slack is not available:%s", resp.Status)
	}

	return nil
}

func NotifyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardPayload
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "failed to read request-body", http.StatusInternalServerError)
		return
	}
	err = json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest(r, req.Tag)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	LogError.Debug("find tag")
	sent := false
	for i, tag := range ConfSlackboard.Tags {
		if tag.Tag == req.Tag {
			atomic.AddUint64(&Topics[i].Count, 1)
			payload := &SlackPayload{
				Channel:   tag.Channel,
				Username:  tag.Username,
				IconEmoji: tag.IconEmoji,
				Text:      req.Text,
				Parse:     tag.Parse,
			}

			var (
				color string
			)

			levelToColorMap := map[string]string{
				"info": "#00ff00", // green
				"warn": "#ffdd00", // yellow
				"crit": "#ff0000", // red
			}

			if color_, ok := levelToColorMap[req.Level]; ok {
				payload.Text = ""
				color = color_
			}

			if req.Title != "" {
				payload.Text = ""
			}

			if color != "" || req.Title != "" {
				payload.Attachments = make([]SlackPayloadAttachments, 1)
				payload.Attachments[0] = SlackPayloadAttachments{
					Color: color,
					Title: req.Title,
					Text:  req.Text,
				}
			}

			if req.Sync {
				err := sendNotification2Slack(payload)
				if err != nil {
					sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
					return
				}
				sent = true
			} else {
				go func() {
					err := sendNotification2Slack(payload)
					if err != nil {
						LogError.Error(fmt.Sprintf("failed to post message to slack:%s", err.Error()))
					}
				}()
			}
		}

	}

	LogError.Debug("response to client")

	if req.Sync {
		if sent {
			sendResponse(w, "ok", http.StatusOK)
		} else {
			msg := fmt.Sprintf("tag:%s is not found", req.Tag)
			sendResponse(w, msg, http.StatusBadRequest)
		}

	} else {
		sendResponse(w, "ok", http.StatusOK)
	}
}

func NotifyDirectlyHandler(w http.ResponseWriter, r *http.Request) {
	LogError.Debug("notify-directly-request is Accepted")

	LogError.Debug("parse request body")
	var req SlackboardDirectPayload
	reqBody, err := ioutil.ReadAll(r.Body)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "failed to read request-body", http.StatusInternalServerError)
		return
	}
	err = json.Unmarshal(reqBody, &req)
	if err != nil {
		LogAcceptedRequest(r, "")
		sendResponse(w, "Request-body is malformed", http.StatusBadRequest)
		return
	}

	LogAcceptedRequest(r, req.Payload.Channel)

	LogError.Debug("method check")
	if r.Method != "POST" {
		sendResponse(w, "invalid method", http.StatusBadRequest)
		return
	}

	if req.Sync {
		err := sendNotification2Slack(&req.Payload)
		if err != nil {
			sendResponse(w, "failed to post message to slack", http.StatusBadGateway)
			return
		}
	} else {
		go func() {
			err := sendNotification2Slack(&req.Payload)
			if err != nil {
				LogError.Error(fmt.Sprintf("failed to post message to slack:%s", err.Error()))
			}
		}()
	}

	LogError.Debug("response to client")
	sendResponse(w, "ok", http.StatusOK)
}
<|endoftext|>


<commit_before>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/pipeline"
	"golang.org/x/sync/errgroup"
	yaml "gopkg.in/yaml.v2"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// SnapcraftMetadata to generate the snap package
type SnapcraftMetadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
}

// AppMetadata for the binaries that will be in the snap package
type AppMetadata struct {
	Command string
	Plugs   []string `yaml:",omitempty"`
	Daemon  string   `yaml:",omitempty"`
}

// Pipe for snapcraft packaging
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating Linux packages with snapcraft"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" {
		return pipeline.Skip("no summary nor description were provided")
	}
	if ctx.Config.Snapcraft.Summary == "" {
		return ErrNoSummary
	}
	if ctx.Config.Snapcraft.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g errgroup.Group
	for platform, groups := range ctx.Binaries {
		if !strings.Contains(platform, "linux") {
			log.WithField("platform", platform).Debug("skipped non-linux builds for snapcraft")
			continue
		}
		arch := linux.Arch(platform)
		for folder, binaries := range groups {
			g.Go(func() error {
				return create(ctx, folder, arch, binaries)
			})
		}
	}
	return g.Wait()
}

func create(ctx *context.Context, folder, arch string, binaries []context.Binary) error {
	var log = log.WithField("arch", arch)
	// prime is the directory that then will be compressed to make the .snap package.
	folderDir := filepath.Join(ctx.Config.Dist, folder)
	primeDir := filepath.Join(folderDir, "prime")
	metaDir := filepath.Join(primeDir, "meta")
	if err := os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &SnapcraftMetadata{
		Version:       ctx.Version,
		Summary:       ctx.Config.Snapcraft.Summary,
		Description:   ctx.Config.Snapcraft.Description,
		Grade:         ctx.Config.Snapcraft.Grade,
		Confinement:   ctx.Config.Snapcraft.Confinement,
		Architectures: []string{arch},
		Apps:          make(map[string]AppMetadata),
	}
	if ctx.Config.Snapcraft.Name != "" {
		metadata.Name = ctx.Config.Snapcraft.Name
	} else {
		metadata.Name = ctx.Config.ProjectName
	}

	for _, binary := range binaries {
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Debug("passed binary to snapcraft")
		appMetadata := AppMetadata{
			Command: binary.Name,
		}
		if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok {
			appMetadata.Plugs = configAppMetadata.Plugs
			appMetadata.Daemon = configAppMetadata.Daemon
		}
		metadata.Apps[binary.Name] = appMetadata

		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		if err := os.Link(binary.Path, destBinaryPath); err != nil {
			return err
		}
	}
	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	if err = ioutil.WriteFile(file, out, 0644); err != nil {
		return err
	}

	snap := filepath.Join(
		ctx.Config.Dist,
		ctx.Config.ProjectName+"_"+metadata.Version+"_"+arch+".snap",
	)
	cmd := exec.Command("snapcraft", "snap", primeDir, "--output", snap)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	ctx.AddArtifact(snap)
	return nil
}
<commit_msg>padronizing snapcraft packages name<commit_after>// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/apex/log"
	"github.com/goreleaser/goreleaser/context"
	"github.com/goreleaser/goreleaser/internal/linux"
	"github.com/goreleaser/goreleaser/pipeline"
	"golang.org/x/sync/errgroup"
	yaml "gopkg.in/yaml.v2"
)

// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")

// ErrNoDescription is shown when no description provided
var ErrNoDescription = errors.New("no description provided for snapcraft")

// ErrNoSummary is shown when no summary provided
var ErrNoSummary = errors.New("no summary provided for snapcraft")

// SnapcraftMetadata to generate the snap package
type SnapcraftMetadata struct {
	Name          string
	Version       string
	Summary       string
	Description   string
	Grade         string `yaml:",omitempty"`
	Confinement   string `yaml:",omitempty"`
	Architectures []string
	Apps          map[string]AppMetadata
}

// AppMetadata for the binaries that will be in the snap package
type AppMetadata struct {
	Command string
	Plugs   []string `yaml:",omitempty"`
	Daemon  string   `yaml:",omitempty"`
}

// Pipe for snapcraft packaging
type Pipe struct{}

// Description of the pipe
func (Pipe) Description() string {
	return "Creating Linux packages with snapcraft"
}

// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
	if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" {
		return pipeline.Skip("no summary nor description were provided")
	}
	if ctx.Config.Snapcraft.Summary == "" {
		return ErrNoSummary
	}
	if ctx.Config.Snapcraft.Description == "" {
		return ErrNoDescription
	}
	_, err := exec.LookPath("snapcraft")
	if err != nil {
		return ErrNoSnapcraft
	}

	var g errgroup.Group
	for platform, groups := range ctx.Binaries {
		if !strings.Contains(platform, "linux") {
			log.WithField("platform", platform).Debug("skipped non-linux builds for snapcraft")
			continue
		}
		arch := linux.Arch(platform)
		for folder, binaries := range groups {
			g.Go(func() error {
				return create(ctx, folder, arch, binaries)
			})
		}
	}
	return g.Wait()
}

func create(ctx *context.Context, folder, arch string, binaries []context.Binary) error {
	var log = log.WithField("arch", arch)
	// prime is the directory that then will be compressed to make the .snap package.
	var folderDir = filepath.Join(ctx.Config.Dist, folder)
	var primeDir = filepath.Join(folderDir, "prime")
	var metaDir = filepath.Join(primeDir, "meta")
	if err := os.MkdirAll(metaDir, 0755); err != nil {
		return err
	}

	var file = filepath.Join(primeDir, "meta", "snap.yaml")
	log.WithField("file", file).Debug("creating snap metadata")

	var metadata = &SnapcraftMetadata{
		Version:       ctx.Version,
		Summary:       ctx.Config.Snapcraft.Summary,
		Description:   ctx.Config.Snapcraft.Description,
		Grade:         ctx.Config.Snapcraft.Grade,
		Confinement:   ctx.Config.Snapcraft.Confinement,
		Architectures: []string{arch},
		Apps:          make(map[string]AppMetadata),
	}
	if ctx.Config.Snapcraft.Name != "" {
		metadata.Name = ctx.Config.Snapcraft.Name
	} else {
		metadata.Name = ctx.Config.ProjectName
	}

	for _, binary := range binaries {
		log.WithField("path", binary.Path).
			WithField("name", binary.Name).
			Debug("passed binary to snapcraft")
		appMetadata := AppMetadata{
			Command: binary.Name,
		}
		if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[binary.Name]; ok {
			appMetadata.Plugs = configAppMetadata.Plugs
			appMetadata.Daemon = configAppMetadata.Daemon
		}
		metadata.Apps[binary.Name] = appMetadata

		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
		if err := os.Link(binary.Path, destBinaryPath); err != nil {
			return err
		}
	}
	out, err := yaml.Marshal(metadata)
	if err != nil {
		return err
	}

	if err = ioutil.WriteFile(file, out, 0644); err != nil {
		return err
	}

	var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
	var cmd = exec.Command("snapcraft", "snap", primeDir, "--output", snap)
	if out, err = cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("failed to generate snap package: %s", string(out))
	}
	ctx.AddArtifact(snap)
	return nil
}
<|endoftext|>


<commit_before>package util

import (
	"errors"
	"fmt"
	"log"
	"math"
	"regexp"
	"time"
)

const (
	// Default time format
	TIMEFORMAT = "2006-01-02 15:04:05 MST"

	// Default time format
	TIMEFORMAT_NO_TZ = "2006-01-02 15:04:05"

	// Time format used by the Google Drive api
	DRIVE_TIMEFORMAT = "2006-01-02T15:04:05.000Z"

	// Timezone for Dexcom interval time values
	INTERNAL_TIMEZONE = "GMT"

	// Let's make days end at 18h00
	HOUR_OF_END_OF_DAY = 18
)

var zoneNameRegexp = regexp.MustCompile("[+-](\\d){4}")

// Beginning of time should be unix epoch 0 but, to optimize some processing
// may iterate overtime starting at this value, we just define the notion
// of Glukit epoch time and have this value be set to something less far back
// but still before anything interesting happened in the Glukit world.
// This maps to 01 Jan 2004 00:00:00 GMT.
var GLUKIT_EPOCH_TIME = time.Unix(1072915200, 0)

var locationCache = make(map[string]*time.Location)

// ParseGoogleDriveDate parses a Google Drive API time value
func ParseGoogleDriveDate(value string) (timeValue time.Time, err error) {
	return time.Parse(DRIVE_TIMEFORMAT, value)
}

// GetTimeInSeconds parses a datetime string and returns its unix timestamp.
func GetTimeInSeconds(timeValue string) (value int64) {
	// time values without timezone info are interpreted as UTC, which is perfect
	if timeValue, err := time.Parse(TIMEFORMAT_NO_TZ, timeValue); err == nil {
		return timeValue.Unix()
	} else {
		log.Printf("Error parsing string", err)
	}
	return 0
}

// GetTimeInSeconds parses a datetime string and returns its unix timestamp.
func GetTimeUTC(timeValue string) (time.Time, error) {
	// time values without timezone info are interpreted as UTC, which is perfect
	return time.Parse(TIMEFORMAT_NO_TZ, timeValue)

}

// GetEndOfDayBoundaryBefore returns the boundary of very last "end of day" before the given time.
// To give an example, if the given time is July 17th 8h00 PST, the boundary returned is going to be
// July 17th 06h00. If the time is July 17th 05h00 PST, the boundary returned is July 16th 06h00.
// Very important: The timeValue's location must be accurate!
func GetEndOfDayBoundaryBefore(timeValue time.Time) (latestEndOfDayBoundary time.Time) {
	if timeValue.Hour() < HOUR_OF_END_OF_DAY {
		// Rewind by one more day
		previousDay := timeValue.Add(time.Duration(-24 * time.Hour))
		latestEndOfDayBoundary = time.Date(previousDay.Year(), previousDay.Month(), previousDay.Day(), HOUR_OF_END_OF_DAY, 0, 0, 0, timeValue.Location())
	} else {
		latestEndOfDayBoundary = time.Date(timeValue.Year(), timeValue.Month(), timeValue.Day(), HOUR_OF_END_OF_DAY, 0, 0, 0, timeValue.Location())
	}

	return latestEndOfDayBoundary
}

// GetMidnightUTCBefore returns the boundary of very last occurence of midnight before the given time.
// To give an example, if the given time is July 17th 2h00 UTC, the boundary returned is going to be
// July 17th 00h00. If the time is July 16th 23h00 PST, the boundary returned is July 16th 00h00.
func GetMidnightUTCBefore(timeValue time.Time) (latestMidnightBoundary time.Time) {
	timeInUTC := timeValue.UTC()
	latestMidnightBoundary = time.Date(timeInUTC.Year(), timeInUTC.Month(), timeInUTC.Day(), 0, 0, 0, 0, time.UTC)
	return latestMidnightBoundary
}

// Returns the timevalue with its timezone set to UTC but without
// printing the timezone in the formatted string
func TimeInUTCNoTz(timevalue time.Time) (localTime string) {
	return timevalue.UTC().Format(TIMEFORMAT_NO_TZ)
}

// GetLocaltimeOffset returns the Fixed location extrapolated by calculating the offset
// of the localtime and the internal time in UTC
func GetLocaltimeOffset(localTime string, internalTime time.Time) (location *time.Location) {
	// Get the local time as if it was UTC (it's not)
	localTimeUTC, err := time.Parse(TIMEFORMAT_NO_TZ, localTime)
	if err != nil {
		Propagate(err)
	}

	// Get the difference between the internal time (actual UTC) and the local time
	durationOffset := localTimeUTC.Sub(internalTime)
	offsetMinutesRemainder := (int64(durationOffset) - int64(durationOffset.Hours())*int64(time.Hour))
	absoluteOffsetMinutesRemainder := int64(math.Abs(float64(offsetMinutesRemainder)))

	fullQuarterHourRemainder := (absoluteOffsetMinutesRemainder / int64(time.Minute) / 15)
	minutesRemainder := (absoluteOffsetMinutesRemainder / int64(time.Minute) % 15)

	quarterHourMultiple := fullQuarterHourRemainder
	if minutesRemainder > 7 {
		quarterHourMultiple++
	}

	if quarterHourMultiple == 4 {
		quarterHourMultiple = 0
	}
	locationName := fmt.Sprintf("%+03d%02d", int64(durationOffset.Hours()), quarterHourMultiple*15)
	return time.FixedZone(locationName, int(durationOffset.Seconds()))
}

// GetLocalTimeInProperLocation returns the parsed local time with the location appropriately set as extrapolated
// by calculating the difference of the internal time vs the local time
func GetLocalTimeInProperLocation(localTime string, internalTime time.Time) (localTimeWithLocation time.Time) {
	location := GetLocaltimeOffset(localTime, internalTime)
	localTimeWithLocation, _ = time.ParseInLocation(TIMEFORMAT_NO_TZ, localTime, location)
	return
}

func GetOrLoadLocationForName(locationName string) (location *time.Location, err error) {
	if location, ok := locationCache[locationName]; !ok {
		location, err = time.LoadLocation(locationName)
		if err != nil {
			if !zoneNameRegexp.MatchString(locationName) {
				return nil, errors.New(fmt.Sprintf("Invalid location name, not a valid timezone location [%s]", locationName))
			} else {
				var hours, minutes int64
				fmt.Sscanf(locationName, "%+03d%02d", &hours, &minutes)
				offsetInMinutes := hours*int64(time.Duration(60)*time.Minute) + minutes
				offsetInSeconds := offsetInMinutes + minutes*int64(time.Duration(60)*time.Second)
				location = time.FixedZone(locationName, int(offsetInSeconds))
				locationCache[locationName] = location
			}
		}

		locationCache[locationName] = location
		return location, nil
	} else {
		return location, nil
	}
}
<commit_msg>Fix TimeZone Detection Function.<commit_after>package util

import (
	"errors"
	"fmt"
	"log"
	"math"
	"regexp"
	"time"
)

const (
	// Default time format
	TIMEFORMAT = "2006-01-02 15:04:05 MST"

	// Default time format
	TIMEFORMAT_NO_TZ = "2006-01-02 15:04:05"

	// Time format used by the Google Drive api
	DRIVE_TIMEFORMAT = "2006-01-02T15:04:05.000Z"

	// Timezone for Dexcom interval time values
	INTERNAL_TIMEZONE = "GMT"

	// Let's make days end at 18h00
	HOUR_OF_END_OF_DAY = 18
)

var zoneNameRegexp = regexp.MustCompile("[+-](\\d){4}")

// Beginning of time should be unix epoch 0 but, to optimize some processing
// may iterate overtime starting at this value, we just define the notion
// of Glukit epoch time and have this value be set to something less far back
// but still before anything interesting happened in the Glukit world.
// This maps to 01 Jan 2004 00:00:00 GMT.
var GLUKIT_EPOCH_TIME = time.Unix(1072915200, 0)

var locationCache = make(map[string]*time.Location)

// ParseGoogleDriveDate parses a Google Drive API time value
func ParseGoogleDriveDate(value string) (timeValue time.Time, err error) {
	return time.Parse(DRIVE_TIMEFORMAT, value)
}

// GetTimeInSeconds parses a datetime string and returns its unix timestamp.
func GetTimeInSeconds(timeValue string) (value int64) {
	// time values without timezone info are interpreted as UTC, which is perfect
	if timeValue, err := time.Parse(TIMEFORMAT_NO_TZ, timeValue); err == nil {
		return timeValue.Unix()
	} else {
		log.Printf("Error parsing string", err)
	}
	return 0
}

// GetTimeInSeconds parses a datetime string and returns its unix timestamp.
func GetTimeUTC(timeValue string) (time.Time, error) {
	// time values without timezone info are interpreted as UTC, which is perfect
	return time.Parse(TIMEFORMAT_NO_TZ, timeValue)

}

// GetEndOfDayBoundaryBefore returns the boundary of very last "end of day" before the given time.
// To give an example, if the given time is July 17th 8h00 PST, the boundary returned is going to be
// July 17th 06h00. If the time is July 17th 05h00 PST, the boundary returned is July 16th 06h00.
// Very important: The timeValue's location must be accurate!
func GetEndOfDayBoundaryBefore(timeValue time.Time) (latestEndOfDayBoundary time.Time) {
	if timeValue.Hour() < HOUR_OF_END_OF_DAY {
		// Rewind by one more day
		previousDay := timeValue.Add(time.Duration(-24 * time.Hour))
		latestEndOfDayBoundary = time.Date(previousDay.Year(), previousDay.Month(), previousDay.Day(), HOUR_OF_END_OF_DAY, 0, 0, 0, timeValue.Location())
	} else {
		latestEndOfDayBoundary = time.Date(timeValue.Year(), timeValue.Month(), timeValue.Day(), HOUR_OF_END_OF_DAY, 0, 0, 0, timeValue.Location())
	}

	return latestEndOfDayBoundary
}

// GetMidnightUTCBefore returns the boundary of very last occurence of midnight before the given time.
// To give an example, if the given time is July 17th 2h00 UTC, the boundary returned is going to be
// July 17th 00h00. If the time is July 16th 23h00 PST, the boundary returned is July 16th 00h00.
func GetMidnightUTCBefore(timeValue time.Time) (latestMidnightBoundary time.Time) {
	timeInUTC := timeValue.UTC()
	latestMidnightBoundary = time.Date(timeInUTC.Year(), timeInUTC.Month(), timeInUTC.Day(), 0, 0, 0, 0, time.UTC)
	return latestMidnightBoundary
}

// Returns the timevalue with its timezone set to UTC but without
// printing the timezone in the formatted string
func TimeInUTCNoTz(timevalue time.Time) (localTime string) {
	return timevalue.UTC().Format(TIMEFORMAT_NO_TZ)
}

// GetLocaltimeOffset returns the Fixed location extrapolated by calculating the offset
// of the localtime and the internal time in UTC
func GetLocaltimeOffset(localTime string, internalTime time.Time) (location *time.Location) {
	// Get the local time as if it was UTC (it's not)
	localTimeUTC, err := time.Parse(TIMEFORMAT_NO_TZ, localTime)
	if err != nil {
		Propagate(err)
	}

	// Get the difference between the internal time (actual UTC) and the local time
	durationOffset := localTimeUTC.Sub(internalTime)

	var truncatedDuration time.Duration
	if math.Signbit(durationOffset.Hours()) {
		minutesOffsetTruncated := int64(math.Ceil(durationOffset.Minutes()/15.-0.5) * 15.)
		truncatedDuration = time.Duration(minutesOffsetTruncated) * time.Minute
	} else {
		minutesOffsetTruncated := int64(math.Floor(durationOffset.Minutes()/15.+0.5) * 15.)
		truncatedDuration = time.Duration(minutesOffsetTruncated) * time.Minute
	}

	minutesOffsetPortion := float64((int64(truncatedDuration) - int64(truncatedDuration.Hours())*int64(time.Hour)) / int64(time.Minute))
	locationName := fmt.Sprintf("%+03d%02d", int(truncatedDuration.Hours()), int64(math.Abs(minutesOffsetPortion)))
	return time.FixedZone(locationName, int(durationOffset.Seconds()))
}

// GetLocalTimeInProperLocation returns the parsed local time with the location appropriately set as extrapolated
// by calculating the difference of the internal time vs the local time
func GetLocalTimeInProperLocation(localTime string, internalTime time.Time) (localTimeWithLocation time.Time) {
	location := GetLocaltimeOffset(localTime, internalTime)
	localTimeWithLocation, _ = time.ParseInLocation(TIMEFORMAT_NO_TZ, localTime, location)
	return
}

func GetOrLoadLocationForName(locationName string) (location *time.Location, err error) {
	if location, ok := locationCache[locationName]; !ok {
		location, err = time.LoadLocation(locationName)
		if err != nil {
			if !zoneNameRegexp.MatchString(locationName) {
				return nil, errors.New(fmt.Sprintf("Invalid location name, not a valid timezone location [%s]", locationName))
			} else {
				var hours, minutes int64
				fmt.Sscanf(locationName, "%+03d%02d", &hours, &minutes)
				offsetInMinutes := hours*int64(time.Duration(60)*time.Minute) + minutes
				offsetInSeconds := offsetInMinutes + minutes*int64(time.Duration(60)*time.Second)
				location = time.FixedZone(locationName, int(offsetInSeconds))
				locationCache[locationName] = location
			}
		}

		locationCache[locationName] = location
		return location, nil
	} else {
		return location, nil
	}
}
<|endoftext|>


<commit_before>package v1

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"zxq.co/ripple/rippleapi/common"
)

type setAllowedData struct {
	UserID  int `json:"user_id"`
	Allowed int `json:"allowed"`
}

// UserManageSetAllowedPOST allows to set the allowed status of an user.
func UserManageSetAllowedPOST(md common.MethodData) common.CodeMessager {
	var data setAllowedData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.Allowed < 0 || data.Allowed > 2 {
		return common.SimpleResponse(400, "Allowed status must be between 0 and 2")
	}
	var banDatetime int64
	var privsSet string
	if data.Allowed == 0 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges & ~3)"
	} else {
		banDatetime = 0
		privsSet = "privileges = (privileges | 3)"
	}
	_, err := md.DB.Exec("UPDATE users SET "+privsSet+", ban_datetime = ? WHERE id = ?", banDatetime, data.UserID)
	if err != nil {
		md.Err(err)
		return Err500
	}
	rapLog(md, fmt.Sprintf("changed UserID:%d's allowed to %d. This was done using the API's terrible ManageSetAllowed.", data.UserID, data.Allowed))
	go fixPrivileges(data.UserID, md.DB)
	query := `
SELECT users.id, users.username, register_datetime, privileges,
	latest_activity, users_stats.username_aka,
	users_stats.country
FROM users
LEFT JOIN users_stats
ON users.id=users_stats.id
WHERE users.id=?
LIMIT 1`
	return userPutsSingle(md, md.DB.QueryRowx(query, data.UserID))
}

type userEditData struct {
	ID          int     `json:"id"`
	Username    *string `json:"username"`
	UsernameAKA *string `json:"username_aka"`
	//Privileges    *uint64      `json:"privileges"`
	Country       *string      `json:"country"`
	SilenceInfo   *silenceInfo `json:"silence_info"`
	ResetUserpage bool         `json:"reset_userpage"`
	//ResetAvatar bool `json:"reset_avatar"`
}

// UserEditPOST allows to edit an user's information.
func UserEditPOST(md common.MethodData) common.CodeMessager {
	var data userEditData
	if err := md.Unmarshal(&data); err != nil {
		fmt.Println(err)
		return ErrBadJSON
	}

	if data.ID == 0 {
		return common.SimpleResponse(404, "That user could not be found")
	}

	var prevUser struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&prevUser, "SELECT username, privileges FROM users WHERE id = ? LIMIT 1", data.ID)

	switch err {
	case nil: // carry on
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found")
	default:
		md.Err(err)
		return Err500
	}

	const initQuery = "UPDATE users SET\n"
	q := initQuery
	var args []interface{}

	// totally did not realise I had to update some fields in users_stats as well
	// and just copy pasting the above code by prefixing "stats" to every
	// variable
	const statsInitQuery = "UPDATE users_stats SET\n"
	statsQ := statsInitQuery
	var statsArgs []interface{}

	if common.UserPrivileges(prevUser.Privileges)&common.AdminPrivilegeManageUsers != 0 &&
		data.ID != md.User.UserID {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	if data.Username != nil {
		if strings.Contains(*data.Username, " ") && strings.Contains(*data.Username, "_") {
			return common.SimpleResponse(400, "Mixed spaces and underscores")
		}
		if usernameAvailable(md, *data.Username, data.ID) {
			return common.SimpleResponse(409, "User with that username exists")
		}
		jsonData, _ := json.Marshal(struct {
			UserID      int    `json:"userID"`
			NewUsername string `json:"newUsername"`
		}{data.ID, *data.Username})
		md.R.Publish("peppy:change_username", string(jsonData))
		appendToUserNotes(md, "Username change: "+prevUser.Username+" -> "+*data.Username, data.ID)
	}
	if data.UsernameAKA != nil {
		statsQ += "username_aka = ?,\n"
		statsArgs = append(statsArgs, *data.UsernameAKA)
	}
	/*if data.Privileges != nil {
		q += "privileges = ?,\n"
		args = append(args, *data.Privileges)
		// UserNormal or UserPublic changed
		if *data.Privileges & 3 != 3 && *data.Privileges & 3 != prevUser.Privileges & 3 {
			q += "ban_datetime = ?"
			args = append(args, meme)
		}
		// https://zxq.co/ripple/old-frontend/src/master/inc/Do.php#L355 ?
		// should also check for AdminManagePrivileges
		// should also check out the code for CM restring/banning
	}*/
	if data.Country != nil {
		statsQ += "country = ?,\n"
		statsArgs = append(statsArgs, *data.Country)
		rapLog(md, fmt.Sprintf("has changed %s country to %s", prevUser.Username, *data.Country))
		appendToUserNotes(md, "country changed to "+*data.Country, data.ID)
	}
	if data.SilenceInfo != nil && md.User.UserPrivileges&common.AdminPrivilegeSilenceUsers != 0 {
		q += "silence_end = ?, silence_reason = ?,\n"
		args = append(args, time.Time(data.SilenceInfo.End).Unix(), data.SilenceInfo.Reason)
	}
	if data.ResetUserpage {
		statsQ += "userpage_content = '',\n"
	}

	if q != initQuery {
		q = q[:len(q)-2] + " WHERE id = ? LIMIT 1"
		args = append(args, data.ID)
		_, err = md.DB.Exec(q, args...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}
	if statsQ != statsInitQuery {
		statsQ = statsQ[:len(statsQ)-2] + " WHERE id = ? LIMIT 1"
		statsArgs = append(statsArgs, data.ID)
		_, err = md.DB.Exec(statsQ, statsArgs...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}

	rapLog(md, fmt.Sprintf("has updated user %s", prevUser.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

type wipeUserData struct {
	ID    int   `json:"id"`
	Modes []int `json:"modes"`
}

// WipeUserPOST wipes an user's scores.
func WipeUserPOST(md common.MethodData) common.CodeMessager {
	var data wipeUserData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.ID == 0 {
		return ErrMissingField("id")
	}
	if len(data.Modes) == 0 {
		return ErrMissingField("modes")
	}

	var userData struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&userData, "SELECT username, privileges FROM users WHERE id = ?", data.ID)
	switch err {
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found!")
	case nil: // carry on
	default:
		md.Err(err)
		return Err500
	}

	if common.UserPrivileges(userData.Privileges)&common.AdminPrivilegeManageUsers != 0 {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	for _, mode := range data.Modes {
		if mode < 0 || mode > 3 {
			continue
		}
		_, err = md.DB.Exec("DELETE FROM scores WHERE userid = ? AND play_mode = ?", data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		_, err = md.DB.Exec(strings.Replace(
			`UPDATE users_stats SET total_score_MODE = 0, ranked_score_MODE = 0, replays_watched_MODE = 0,
			playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
			WHERE id = ?`, "MODE", modesToReadable[mode], -1,
		), data.ID)
		if err != nil {
			md.Err(err)
		}
	}

	rapLog(md, fmt.Sprintf("has wiped %s's account", userData.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func appendToUserNotes(md common.MethodData, message string, user int) {
	message = "\n[" + time.Now().Format("2006-01-02 15:04:05") + "] API: " + message
	_, err := md.DB.Exec("UPDATE users SET notes = CONCAT(COALESCE(notes, ''), ?) WHERE id = ?",
		message, user)
	if err != nil {
		md.Err(err)
	}
}

func usernameAvailable(md common.MethodData, u string, userID int) (r bool) {
	err := md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username_safe = ? AND id != ?)", common.SafeUsername(u), userID).Scan(&r)
	if err != nil && err != sql.ErrNoRows {
		md.Err(err)
	}
	return
}
<commit_msg>Remove 'Username change' note, because that is already handled by pep.py<commit_after>package v1

import (
	"database/sql"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"zxq.co/ripple/rippleapi/common"
)

type setAllowedData struct {
	UserID  int `json:"user_id"`
	Allowed int `json:"allowed"`
}

// UserManageSetAllowedPOST allows to set the allowed status of an user.
func UserManageSetAllowedPOST(md common.MethodData) common.CodeMessager {
	var data setAllowedData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.Allowed < 0 || data.Allowed > 2 {
		return common.SimpleResponse(400, "Allowed status must be between 0 and 2")
	}
	var banDatetime int64
	var privsSet string
	if data.Allowed == 0 {
		banDatetime = time.Now().Unix()
		privsSet = "privileges = (privileges & ~3)"
	} else {
		banDatetime = 0
		privsSet = "privileges = (privileges | 3)"
	}
	_, err := md.DB.Exec("UPDATE users SET "+privsSet+", ban_datetime = ? WHERE id = ?", banDatetime, data.UserID)
	if err != nil {
		md.Err(err)
		return Err500
	}
	rapLog(md, fmt.Sprintf("changed UserID:%d's allowed to %d. This was done using the API's terrible ManageSetAllowed.", data.UserID, data.Allowed))
	go fixPrivileges(data.UserID, md.DB)
	query := `
SELECT users.id, users.username, register_datetime, privileges,
	latest_activity, users_stats.username_aka,
	users_stats.country
FROM users
LEFT JOIN users_stats
ON users.id=users_stats.id
WHERE users.id=?
LIMIT 1`
	return userPutsSingle(md, md.DB.QueryRowx(query, data.UserID))
}

type userEditData struct {
	ID          int     `json:"id"`
	Username    *string `json:"username"`
	UsernameAKA *string `json:"username_aka"`
	//Privileges    *uint64      `json:"privileges"`
	Country       *string      `json:"country"`
	SilenceInfo   *silenceInfo `json:"silence_info"`
	ResetUserpage bool         `json:"reset_userpage"`
	//ResetAvatar bool `json:"reset_avatar"`
}

// UserEditPOST allows to edit an user's information.
func UserEditPOST(md common.MethodData) common.CodeMessager {
	var data userEditData
	if err := md.Unmarshal(&data); err != nil {
		fmt.Println(err)
		return ErrBadJSON
	}

	if data.ID == 0 {
		return common.SimpleResponse(404, "That user could not be found")
	}

	var prevUser struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&prevUser, "SELECT username, privileges FROM users WHERE id = ? LIMIT 1", data.ID)

	switch err {
	case nil: // carry on
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found")
	default:
		md.Err(err)
		return Err500
	}

	const initQuery = "UPDATE users SET\n"
	q := initQuery
	var args []interface{}

	// totally did not realise I had to update some fields in users_stats as well
	// and just copy pasting the above code by prefixing "stats" to every
	// variable
	const statsInitQuery = "UPDATE users_stats SET\n"
	statsQ := statsInitQuery
	var statsArgs []interface{}

	if common.UserPrivileges(prevUser.Privileges)&common.AdminPrivilegeManageUsers != 0 &&
		data.ID != md.User.UserID {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	if data.Username != nil {
		if strings.Contains(*data.Username, " ") && strings.Contains(*data.Username, "_") {
			return common.SimpleResponse(400, "Mixed spaces and underscores")
		}
		if usernameAvailable(md, *data.Username, data.ID) {
			return common.SimpleResponse(409, "User with that username exists")
		}
		jsonData, _ := json.Marshal(struct {
			UserID      int    `json:"userID"`
			NewUsername string `json:"newUsername"`
		}{data.ID, *data.Username})
		md.R.Publish("peppy:change_username", string(jsonData))
	}
	if data.UsernameAKA != nil {
		statsQ += "username_aka = ?,\n"
		statsArgs = append(statsArgs, *data.UsernameAKA)
	}
	/*if data.Privileges != nil {
		q += "privileges = ?,\n"
		args = append(args, *data.Privileges)
		// UserNormal or UserPublic changed
		if *data.Privileges & 3 != 3 && *data.Privileges & 3 != prevUser.Privileges & 3 {
			q += "ban_datetime = ?"
			args = append(args, meme)
		}
		// https://zxq.co/ripple/old-frontend/src/master/inc/Do.php#L355 ?
		// should also check for AdminManagePrivileges
		// should also check out the code for CM restring/banning
	}*/
	if data.Country != nil {
		statsQ += "country = ?,\n"
		statsArgs = append(statsArgs, *data.Country)
		rapLog(md, fmt.Sprintf("has changed %s country to %s", prevUser.Username, *data.Country))
		appendToUserNotes(md, "country changed to "+*data.Country, data.ID)
	}
	if data.SilenceInfo != nil && md.User.UserPrivileges&common.AdminPrivilegeSilenceUsers != 0 {
		q += "silence_end = ?, silence_reason = ?,\n"
		args = append(args, time.Time(data.SilenceInfo.End).Unix(), data.SilenceInfo.Reason)
	}
	if data.ResetUserpage {
		statsQ += "userpage_content = '',\n"
	}

	if q != initQuery {
		q = q[:len(q)-2] + " WHERE id = ? LIMIT 1"
		args = append(args, data.ID)
		_, err = md.DB.Exec(q, args...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}
	if statsQ != statsInitQuery {
		statsQ = statsQ[:len(statsQ)-2] + " WHERE id = ? LIMIT 1"
		statsArgs = append(statsArgs, data.ID)
		_, err = md.DB.Exec(statsQ, statsArgs...)
		if err != nil {
			md.Err(err)
			return Err500
		}
	}

	rapLog(md, fmt.Sprintf("has updated user %s", prevUser.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

type wipeUserData struct {
	ID    int   `json:"id"`
	Modes []int `json:"modes"`
}

// WipeUserPOST wipes an user's scores.
func WipeUserPOST(md common.MethodData) common.CodeMessager {
	var data wipeUserData
	if err := md.Unmarshal(&data); err != nil {
		return ErrBadJSON
	}
	if data.ID == 0 {
		return ErrMissingField("id")
	}
	if len(data.Modes) == 0 {
		return ErrMissingField("modes")
	}

	var userData struct {
		Username   string
		Privileges uint64
	}
	err := md.DB.Get(&userData, "SELECT username, privileges FROM users WHERE id = ?", data.ID)
	switch err {
	case sql.ErrNoRows:
		return common.SimpleResponse(404, "That user could not be found!")
	case nil: // carry on
	default:
		md.Err(err)
		return Err500
	}

	if common.UserPrivileges(userData.Privileges)&common.AdminPrivilegeManageUsers != 0 {
		return common.SimpleResponse(403, "Can't edit that user")
	}

	for _, mode := range data.Modes {
		if mode < 0 || mode > 3 {
			continue
		}
		_, err = md.DB.Exec("DELETE FROM scores WHERE userid = ? AND play_mode = ?", data.ID, mode)
		if err != nil {
			md.Err(err)
		}
		_, err = md.DB.Exec(strings.Replace(
			`UPDATE users_stats SET total_score_MODE = 0, ranked_score_MODE = 0, replays_watched_MODE = 0,
			playcount_MODE = 0, avg_accuracy_MODE = 0, total_hits_MODE = 0, level_MODE = 0, pp_MODE = 0
			WHERE id = ?`, "MODE", modesToReadable[mode], -1,
		), data.ID)
		if err != nil {
			md.Err(err)
		}
	}

	rapLog(md, fmt.Sprintf("has wiped %s's account", userData.Username))

	return userPutsSingle(md, md.DB.QueryRowx(userFields+" WHERE users.id = ? LIMIT 1", data.ID))
}

func appendToUserNotes(md common.MethodData, message string, user int) {
	message = "\n[" + time.Now().Format("2006-01-02 15:04:05") + "] API: " + message
	_, err := md.DB.Exec("UPDATE users SET notes = CONCAT(COALESCE(notes, ''), ?) WHERE id = ?",
		message, user)
	if err != nil {
		md.Err(err)
	}
}

func usernameAvailable(md common.MethodData, u string, userID int) (r bool) {
	err := md.DB.QueryRow("SELECT EXISTS(SELECT 1 FROM users WHERE username_safe = ? AND id != ?)", common.SafeUsername(u), userID).Scan(&r)
	if err != nil && err != sql.ErrNoRows {
		md.Err(err)
	}
	return
}
<|endoftext|>