<commit_before>// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"io/ioutil"

	"github.com/ethereum/go-ethereum/accounts"
	"github.com/ethereum/go-ethereum/cmd/utils"
	"github.com/ethereum/go-ethereum/console"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/logger"
	"github.com/ethereum/go-ethereum/logger/glog"
	"gopkg.in/urfave/cli.v1"
)

var (
	walletCommand = cli.Command{
		Name:  "wallet",
		Usage: "ethereum presale wallet",
		Subcommands: []cli.Command{
			{
				Action: importWallet,
				Name:   "import",
				Usage:  "import ethereum presale wallet",
			},
		},
		Description: `

    get wallet import /path/to/my/presale.wallet

will prompt for your password and imports your ether presale account.
It can be used non-interactively with the --password option taking a
passwordfile as argument containing the wallet password in plaintext.

`}
	accountCommand = cli.Command{
		Action: accountList,
		Name:   "account",
		Usage:  "manage accounts",
		Description: `

Manage accounts lets you create new accounts, list all existing accounts,
import a private key into a new account.

'            help' shows a list of subcommands or help for one subcommand.

It supports interactive mode, when you are prompted for password as well as
non-interactive mode where passwords are supplied via a given password file.
Non-interactive mode is only meant for scripted use on test networks or known
safe environments.

Make sure you remember the password you gave when creating a new account (with
either new or import). Without it you are not able to unlock your account.

Note that exporting your key in unencrypted format is NOT supported.

Keys are stored under <DATADIR>/keystore.
It is safe to transfer the entire directory or the individual keys therein
between ethereum nodes by simply copying.
Make sure you backup your keys regularly.

In order to use your account to send transactions, you need to unlock them using
the '--unlock' option. The argument is a space separated list of addresses or
indexes. If used non-interactively with a passwordfile, the file should contain
the respective passwords one per line. If you unlock n accounts and the password
file contains less than n entries, then the last password is meant to apply to
all remaining accounts.

And finally. DO NOT FORGET YOUR PASSWORD.
`,
		Subcommands: []cli.Command{
			{
				Action: accountList,
				Name:   "list",
				Usage:  "print account addresses",
			},
			{
				Action: accountCreate,
				Name:   "new",
				Usage:  "create a new account",
				Description: `

    ethereum account new

Creates a new account. Prints the address.

The account is saved in encrypted format, you are prompted for a passphrase.

You must remember this passphrase to unlock your account in the future.

For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account new

Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way.
					`,
			},
			{
				Action: accountUpdate,
				Name:   "update",
				Usage:  "update an existing account",
				Description: `

    ethereum account update <address>

Update an existing account.

The account is saved in the newest version in encrypted format, you are prompted
for a passphrase to unlock the account and another to save the updated file.

This same command can therefore be used to migrate an account of a deprecated
format to the newest format or change the password for an account.

For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account update <address>

Since only one password can be given, only format update can be performed,
changing your password is only possible interactively.
					`,
			},
			{
				Action: accountImport,
				Name:   "import",
				Usage:  "import a private key into a new account",
				Description: `

    ethereum account import <keyfile>

Imports an unencrypted private key from <keyfile> and creates a new account.
Prints the address.

The keyfile is assumed to contain an unencrypted private key in hexadecimal format.

The account is saved in encrypted format, you are prompted for a passphrase.

You must remember this passphrase to unlock your account in the future.

For non-interactive use the passphrase can be specified with the -password flag:

    ethereum --password <passwordfile> account import <keyfile>

Note:
As you can directly copy your encrypted accounts to another ethereum instance,
this import mechanism is not needed when you transfer an account between
nodes.
					`,
			},
		},
	}
)

func accountList(ctx *cli.Context) error {
	accman := utils.MakeAccountManager(ctx)
	for i, acct := range accman.Accounts() {
		fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
	}
	return nil
}

// tries unlocking the specified account a few times.
func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) {
	account, err := utils.MakeAddress(accman, address)
	if err != nil {
		utils.Fatalf("Could not list accounts: %v", err)
	}
	for trials := 0; trials < 3; trials++ {
		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
		password := getPassPhrase(prompt, false, i, passwords)
		err = accman.Unlock(account, password)
		if err == nil {
			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
			return account, password
		}
		if err, ok := err.(*accounts.AmbiguousAddrError); ok {
			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
			return ambiguousAddrRecovery(accman, err, password), password
		}
		if err != accounts.ErrDecrypt {
			// No need to prompt again if the error is not decryption-related.
			break
		}
	}
	// All trials expended to unlock account, bail out
	utils.Fatalf("Failed to unlock account %s (%v)", address, err)
	return accounts.Account{}, ""
}

// getPassPhrase retrieves the passwor associated with an account, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
	// If a list of passwords was supplied, retrieve from them
	if len(passwords) > 0 {
		if i < len(passwords) {
			return passwords[i]
		}
		return passwords[len(passwords)-1]
	}
	// Otherwise prompt the user for the password
	if prompt != "" {
		fmt.Println(prompt)
	}
	password, err := console.Stdin.PromptPassword("Passphrase: ")
	if err != nil {
		utils.Fatalf("Failed to read passphrase: %v", err)
	}
	if confirmation {
		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
		if err != nil {
			utils.Fatalf("Failed to read passphrase confirmation: %v", err)
		}
		if password != confirm {
			utils.Fatalf("Passphrases do not match")
		}
	}
	return password
}

func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account {
	fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
	for _, a := range err.Matches {
		fmt.Println("  ", a.File)
	}
	fmt.Println("Testing your passphrase against all of them...")
	var match *accounts.Account
	for _, a := range err.Matches {
		if err := am.Unlock(a, auth); err == nil {
			match = &a
			break
		}
	}
	if match == nil {
		utils.Fatalf("None of the listed files could be unlocked.")
	}
	fmt.Printf("Your passphrase unlocked %s\n", match.File)
	fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:")
	for _, a := range err.Matches {
		if a != *match {
			fmt.Println("  ", a.File)
		}
	}
	return *match
}

// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) error {
	accman := utils.MakeAccountManager(ctx)
	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))

	account, err := accman.NewAccount(password)
	if err != nil {
		utils.Fatalf("Failed to create account: %v", err)
	}
	fmt.Printf("Address: {%x}\n", account.Address)
	return nil
}

// accountUpdate transitions an account from a previous format to the current
// one, also providing the possibility to change the pass-phrase.
func accountUpdate(ctx *cli.Context) error {
	if len(ctx.Args()) == 0 {
		utils.Fatalf("No accounts specified to update")
	}
	accman := utils.MakeAccountManager(ctx)

	account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
	newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
	if err := accman.Update(account, oldPassword, newPassword); err != nil {
		utils.Fatalf("Could not update the account: %v", err)
	}
	return nil
}

func importWallet(ctx *cli.Context) error {
	keyfile := ctx.Args().First()
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as argument")
	}
	keyJson, err := ioutil.ReadFile(keyfile)
	if err != nil {
		utils.Fatalf("Could not read wallet file: %v", err)
	}

	accman := utils.MakeAccountManager(ctx)
	passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))

	acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
	if err != nil {
		utils.Fatalf("%v", err)
	}
	fmt.Printf("Address: {%x}\n", acct.Address)
	return nil
}

func accountImport(ctx *cli.Context) error {
	keyfile := ctx.Args().First()
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as argument")
	}
	key, err := crypto.LoadECDSA(keyfile)
	if err != nil {
		utils.Fatalf("keyfile must be given as argument")
	}
	accman := utils.MakeAccountManager(ctx)
	passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
	acct, err := accman.ImportECDSA(key, passphrase)
	if err != nil {
		utils.Fatalf("Could not create the account: %v", err)
	}
	fmt.Printf("Address: {%x}\n", acct.Address)
	return nil
}
<commit_msg>cmd/geth: fix the import error message<commit_after>// Copyright 2016 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"fmt"
	"io/ioutil"

	"github.com/ethereum/go-ethereum/accounts"
	"github.com/ethereum/go-ethereum/cmd/utils"
	"github.com/ethereum/go-ethereum/console"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/logger"
	"github.com/ethereum/go-ethereum/logger/glog"
	"gopkg.in/urfave/cli.v1"
)

var (
	walletCommand = cli.Command{
		Name:  "wallet",
		Usage: "ethereum presale wallet",
		Subcommands: []cli.Command{
			{
				Action: importWallet,
				Name:   "import",
				Usage:  "import ethereum presale wallet",
			},
		},
		Description: `

    get wallet import /path/to/my/presale.wallet

will prompt for your password and imports your ether presale account.
It can be used non-interactively with the --password option taking a
passwordfile as argument containing the wallet password in plaintext.

`}
	accountCommand = cli.Command{
		Action: accountList,
		Name:   "account",
		Usage:  "manage accounts",
		Description: `

Manage accounts lets you create new accounts, list all existing accounts,
import a private key into a new account.

'            help' shows a list of subcommands or help for one subcommand.

It supports interactive mode, when you are prompted for password as well as
non-interactive mode where passwords are supplied via a given password file.
Non-interactive mode is only meant for scripted use on test networks or known
safe environments.

Make sure you remember the password you gave when creating a new account (with
either new or import). Without it you are not able to unlock your account.

Note that exporting your key in unencrypted format is NOT supported.

Keys are stored under <DATADIR>/keystore.
It is safe to transfer the entire directory or the individual keys therein
between ethereum nodes by simply copying.
Make sure you backup your keys regularly.

In order to use your account to send transactions, you need to unlock them using
the '--unlock' option. The argument is a space separated list of addresses or
indexes. If used non-interactively with a passwordfile, the file should contain
the respective passwords one per line. If you unlock n accounts and the password
file contains less than n entries, then the last password is meant to apply to
all remaining accounts.

And finally. DO NOT FORGET YOUR PASSWORD.
`,
		Subcommands: []cli.Command{
			{
				Action: accountList,
				Name:   "list",
				Usage:  "print account addresses",
			},
			{
				Action: accountCreate,
				Name:   "new",
				Usage:  "create a new account",
				Description: `

    ethereum account new

Creates a new account. Prints the address.

The account is saved in encrypted format, you are prompted for a passphrase.

You must remember this passphrase to unlock your account in the future.

For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account new

Note, this is meant to be used for testing only, it is a bad idea to save your
password to file or expose in any other way.
					`,
			},
			{
				Action: accountUpdate,
				Name:   "update",
				Usage:  "update an existing account",
				Description: `

    ethereum account update <address>

Update an existing account.

The account is saved in the newest version in encrypted format, you are prompted
for a passphrase to unlock the account and another to save the updated file.

This same command can therefore be used to migrate an account of a deprecated
format to the newest format or change the password for an account.

For non-interactive use the passphrase can be specified with the --password flag:

    ethereum --password <passwordfile> account update <address>

Since only one password can be given, only format update can be performed,
changing your password is only possible interactively.
					`,
			},
			{
				Action: accountImport,
				Name:   "import",
				Usage:  "import a private key into a new account",
				Description: `

    ethereum account import <keyfile>

Imports an unencrypted private key from <keyfile> and creates a new account.
Prints the address.

The keyfile is assumed to contain an unencrypted private key in hexadecimal format.

The account is saved in encrypted format, you are prompted for a passphrase.

You must remember this passphrase to unlock your account in the future.

For non-interactive use the passphrase can be specified with the -password flag:

    ethereum --password <passwordfile> account import <keyfile>

Note:
As you can directly copy your encrypted accounts to another ethereum instance,
this import mechanism is not needed when you transfer an account between
nodes.
					`,
			},
		},
	}
)

func accountList(ctx *cli.Context) error {
	accman := utils.MakeAccountManager(ctx)
	for i, acct := range accman.Accounts() {
		fmt.Printf("Account #%d: {%x} %s\n", i, acct.Address, acct.File)
	}
	return nil
}

// tries unlocking the specified account a few times.
func unlockAccount(ctx *cli.Context, accman *accounts.Manager, address string, i int, passwords []string) (accounts.Account, string) {
	account, err := utils.MakeAddress(accman, address)
	if err != nil {
		utils.Fatalf("Could not list accounts: %v", err)
	}
	for trials := 0; trials < 3; trials++ {
		prompt := fmt.Sprintf("Unlocking account %s | Attempt %d/%d", address, trials+1, 3)
		password := getPassPhrase(prompt, false, i, passwords)
		err = accman.Unlock(account, password)
		if err == nil {
			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
			return account, password
		}
		if err, ok := err.(*accounts.AmbiguousAddrError); ok {
			glog.V(logger.Info).Infof("Unlocked account %x", account.Address)
			return ambiguousAddrRecovery(accman, err, password), password
		}
		if err != accounts.ErrDecrypt {
			// No need to prompt again if the error is not decryption-related.
			break
		}
	}
	// All trials expended to unlock account, bail out
	utils.Fatalf("Failed to unlock account %s (%v)", address, err)
	return accounts.Account{}, ""
}

// getPassPhrase retrieves the passwor associated with an account, either fetched
// from a list of preloaded passphrases, or requested interactively from the user.
func getPassPhrase(prompt string, confirmation bool, i int, passwords []string) string {
	// If a list of passwords was supplied, retrieve from them
	if len(passwords) > 0 {
		if i < len(passwords) {
			return passwords[i]
		}
		return passwords[len(passwords)-1]
	}
	// Otherwise prompt the user for the password
	if prompt != "" {
		fmt.Println(prompt)
	}
	password, err := console.Stdin.PromptPassword("Passphrase: ")
	if err != nil {
		utils.Fatalf("Failed to read passphrase: %v", err)
	}
	if confirmation {
		confirm, err := console.Stdin.PromptPassword("Repeat passphrase: ")
		if err != nil {
			utils.Fatalf("Failed to read passphrase confirmation: %v", err)
		}
		if password != confirm {
			utils.Fatalf("Passphrases do not match")
		}
	}
	return password
}

func ambiguousAddrRecovery(am *accounts.Manager, err *accounts.AmbiguousAddrError, auth string) accounts.Account {
	fmt.Printf("Multiple key files exist for address %x:\n", err.Addr)
	for _, a := range err.Matches {
		fmt.Println("  ", a.File)
	}
	fmt.Println("Testing your passphrase against all of them...")
	var match *accounts.Account
	for _, a := range err.Matches {
		if err := am.Unlock(a, auth); err == nil {
			match = &a
			break
		}
	}
	if match == nil {
		utils.Fatalf("None of the listed files could be unlocked.")
	}
	fmt.Printf("Your passphrase unlocked %s\n", match.File)
	fmt.Println("In order to avoid this warning, you need to remove the following duplicate key files:")
	for _, a := range err.Matches {
		if a != *match {
			fmt.Println("  ", a.File)
		}
	}
	return *match
}

// accountCreate creates a new account into the keystore defined by the CLI flags.
func accountCreate(ctx *cli.Context) error {
	accman := utils.MakeAccountManager(ctx)
	password := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))

	account, err := accman.NewAccount(password)
	if err != nil {
		utils.Fatalf("Failed to create account: %v", err)
	}
	fmt.Printf("Address: {%x}\n", account.Address)
	return nil
}

// accountUpdate transitions an account from a previous format to the current
// one, also providing the possibility to change the pass-phrase.
func accountUpdate(ctx *cli.Context) error {
	if len(ctx.Args()) == 0 {
		utils.Fatalf("No accounts specified to update")
	}
	accman := utils.MakeAccountManager(ctx)

	account, oldPassword := unlockAccount(ctx, accman, ctx.Args().First(), 0, nil)
	newPassword := getPassPhrase("Please give a new password. Do not forget this password.", true, 0, nil)
	if err := accman.Update(account, oldPassword, newPassword); err != nil {
		utils.Fatalf("Could not update the account: %v", err)
	}
	return nil
}

func importWallet(ctx *cli.Context) error {
	keyfile := ctx.Args().First()
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as argument")
	}
	keyJson, err := ioutil.ReadFile(keyfile)
	if err != nil {
		utils.Fatalf("Could not read wallet file: %v", err)
	}

	accman := utils.MakeAccountManager(ctx)
	passphrase := getPassPhrase("", false, 0, utils.MakePasswordList(ctx))

	acct, err := accman.ImportPreSaleKey(keyJson, passphrase)
	if err != nil {
		utils.Fatalf("%v", err)
	}
	fmt.Printf("Address: {%x}\n", acct.Address)
	return nil
}

func accountImport(ctx *cli.Context) error {
	keyfile := ctx.Args().First()
	if len(keyfile) == 0 {
		utils.Fatalf("keyfile must be given as argument")
	}
	key, err := crypto.LoadECDSA(keyfile)
	if err != nil {
		utils.Fatalf("Failed to load the private key: %v", err)
	}
	accman := utils.MakeAccountManager(ctx)
	passphrase := getPassPhrase("Your new account is locked with a password. Please give a password. Do not forget this password.", true, 0, utils.MakePasswordList(ctx))
	acct, err := accman.ImportECDSA(key, passphrase)
	if err != nil {
		utils.Fatalf("Could not create the account: %v", err)
	}
	fmt.Printf("Address: {%x}\n", acct.Address)
	return nil
}
<|endoftext|>


<commit_before>package hashmap

import (
	"bytes"
	"fmt"
	"reflect"
	"sync"
	"sync/atomic"
	"unsafe"
)

// MaxFillRate is the maximum fill rate for the slice before a resize  will happen
const MaxFillRate = 50

type (
	hashMapData struct {
		keyRightShifts uint64         // 64 - log2 of array size, to be used as index in the data array
		data           unsafe.Pointer // pointer to slice data array
		slice          []*ListElement // storage for the slice for the garbage collector to not clean it up
		count          uint64         // count of filled elements in the slice
	}

	// HashMap implements a read optimized hash map
	HashMap struct {
		mapData    unsafe.Pointer // pointer to a map instance that gets replaced if the map resizes
		linkedList *List          // key sorted linked list of elements
		sync.Mutex                // mutex that is only used for resize operations
	}
)

// New returns a new HashMap.
func New() *HashMap {
	return NewSize(8)
}

// NewSize returns a new HashMap instance with a specific initialization size.
func NewSize(size uint64) *HashMap {
	hashmap := &HashMap{
		linkedList: NewList(),
	}
	hashmap.Grow(size)
	return hashmap
}

// Len returns the number of elements within the map.
func (m *HashMap) Len() uint64 {
	return m.linkedList.Len()
}

// Fillrate returns the fill rate of the map as an percentage integer.
func (m *HashMap) Fillrate() uint64 {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	count := atomic.LoadUint64(&mapData.count)
	sliceLen := uint64(len(mapData.slice))
	return (count * 100) / sliceLen
}

// Get retrieves an element from map under given key.
func (m *HashMap) Get(key uint64) (unsafe.Pointer, bool) {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	index := key >> mapData.keyRightShifts
	sliceDataIndexPointer := (*unsafe.Pointer)(unsafe.Pointer(uintptr(mapData.data) + uintptr(index*intSizeBytes)))
	entry := (*ListElement)(atomic.LoadPointer(sliceDataIndexPointer))

	for {
		if entry == nil {
			return nil, false
		}

		if entry.keyHash == key {
			if atomic.LoadUint64(&entry.deleted) == 0 {
				return entry.value, true
			}
			return nil, false
		}

		if entry.keyHash > key {
			return nil, false
		}

		entry = entry.Next()
	}
}

// Del deletes the key from the map.
func (m *HashMap) Del(key uint64) {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	index := key >> mapData.keyRightShifts
	sliceDataIndexPointer := (*unsafe.Pointer)(unsafe.Pointer(uintptr(mapData.data) + uintptr(index*intSizeBytes)))
	entry := (*ListElement)(atomic.LoadPointer(sliceDataIndexPointer))

	for {
		if entry == nil {
			return
		}

		if entry.keyHash == key {
			m.linkedList.Delete(entry)
			return
		}

		if entry.keyHash > key {
			return
		}

		entry = entry.Next()
	}
}

// Add adds the value under the specified key to the map. An existing item for this key will be overwritten.
func (m *HashMap) Add(key uint64, value unsafe.Pointer) {
	newEntry := &ListElement{
		keyHash: key,
		value:   value,
	}

	for {
		mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
		index := key >> mapData.keyRightShifts
		sliceDataIndexPointer := (*unsafe.Pointer)(unsafe.Pointer(uintptr(mapData.data) + uintptr(index*intSizeBytes)))

		sliceItem := (*ListElement)(atomic.LoadPointer(sliceDataIndexPointer))
		if !m.linkedList.Add(newEntry, sliceItem) {
			continue // a concurrent add did interfere, try again
		}

		if sliceItem == nil {
			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(sliceDataIndexPointer), nil, unsafe.Pointer(newEntry)) {
				newSliceCount := atomic.AddUint64(&mapData.count, 1)
				sliceLen := uint64(len(mapData.slice))
				fillRate := (newSliceCount * 100) / sliceLen

				if fillRate > MaxFillRate { // check if the slice needs to be resized
					m.Lock()
					currentMapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
					if mapData != currentMapData { // double check, a concurrent resize happened
						m.Unlock()
						continue
					}

					m.grow(0)
					m.Unlock()
					return
				}
			}
		} else {
			if newEntry.keyHash < sliceItem.keyHash { // the new item is the smallest for this index
				atomic.CompareAndSwapPointer((*unsafe.Pointer)(sliceDataIndexPointer), unsafe.Pointer(sliceItem), unsafe.Pointer(newEntry))
			}
		}

		currentMapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
		if mapData != currentMapData { // a resize operation happened while we were inserting?
			continue // retry
		}
		return
	}
}

// Grow resizes the hashmap to a new size, gets rounded up to next power of 2.
// To double the size of the hashmap use newSize 0.
func (m *HashMap) Grow(newSize uint64) {
	m.Lock()
	m.grow(newSize)
	m.Unlock()
}

// Grow resizes the hashmap to a new size, gets rounded up to next power of 2.
// To double the size of the hashmap use newSize 0.
func (m *HashMap) grow(newSize uint64) {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	if newSize == 0 {
		newSize = uint64(len(mapData.slice)) << 1
	} else {
		newSize = roundUpPower2(newSize)
	}

	newSlice := make([]*ListElement, newSize)
	header := (*reflect.SliceHeader)(unsafe.Pointer(&newSlice))

	newMapData := &hashMapData{
		keyRightShifts: 64 - log2(newSize),
		data:           unsafe.Pointer(header.Data), // use address of slice data storage
		slice:          newSlice,
	}

	if mapData != nil { // copy hashmap contents to new slice with longer key
		first := m.linkedList.First()
		item := first
		lastIndex := uint64(0)

		for item != nil {
			index := item.keyHash >> newMapData.keyRightShifts

			if item == first || index != lastIndex { // store item with smallest hash key for every index
				newSlice[index] = item
				newMapData.count++
				lastIndex = index
			}
			item = item.Next()
		}
	}

	atomic.StorePointer(&m.mapData, unsafe.Pointer(newMapData))
}

// String returns the map as a string, only keys are printed
func (m *HashMap) String() string {
	buffer := bytes.NewBufferString("")
	buffer.WriteRune('[')

	first := m.linkedList.First()
	item := first

	for item != nil {
		if item != first {
			buffer.WriteRune(',')
		}
		fmt.Fprint(buffer, item.keyHash)

		item = item.Next()
	}

	buffer.WriteRune(']')
	return buffer.String()
}
<commit_msg>Moved code to helper function<commit_after>package hashmap

import (
	"bytes"
	"fmt"
	"reflect"
	"sync"
	"sync/atomic"
	"unsafe"
)

// MaxFillRate is the maximum fill rate for the slice before a resize  will happen
const MaxFillRate = 50

type (
	hashMapData struct {
		keyRightShifts uint64         // 64 - log2 of array size, to be used as index in the data array
		data           unsafe.Pointer // pointer to slice data array
		slice          []*ListElement // storage for the slice for the garbage collector to not clean it up
		count          uint64         // count of filled elements in the slice
	}

	// HashMap implements a read optimized hash map
	HashMap struct {
		mapData    unsafe.Pointer // pointer to a map instance that gets replaced if the map resizes
		linkedList *List          // key sorted linked list of elements
		sync.Mutex                // mutex that is only used for resize operations
	}
)

// New returns a new HashMap.
func New() *HashMap {
	return NewSize(8)
}

// NewSize returns a new HashMap instance with a specific initialization size.
func NewSize(size uint64) *HashMap {
	hashmap := &HashMap{
		linkedList: NewList(),
	}
	hashmap.Grow(size)
	return hashmap
}

// Len returns the number of elements within the map.
func (m *HashMap) Len() uint64 {
	return m.linkedList.Len()
}

// Fillrate returns the fill rate of the map as an percentage integer.
func (m *HashMap) Fillrate() uint64 {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	count := atomic.LoadUint64(&mapData.count)
	sliceLen := uint64(len(mapData.slice))
	return (count * 100) / sliceLen
}

func (m *HashMap) getSliceItemForKey(key uint64) *ListElement {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	index := key >> mapData.keyRightShifts
	sliceDataIndexPointer := (*unsafe.Pointer)(unsafe.Pointer(uintptr(mapData.data) + uintptr(index*intSizeBytes)))
	entry := (*ListElement)(atomic.LoadPointer(sliceDataIndexPointer))
	return entry
}

// Get retrieves an element from map under given key.
func (m *HashMap) Get(key uint64) (unsafe.Pointer, bool) {
	entry := m.getSliceItemForKey(key)

	for {
		if entry == nil {
			return nil, false
		}

		if entry.keyHash == key {
			if atomic.LoadUint64(&entry.deleted) == 0 {
				return entry.value, true
			}
			return nil, false
		}

		if entry.keyHash > key {
			return nil, false
		}

		entry = entry.Next()
	}
}

// Del deletes the key from the map.
func (m *HashMap) Del(key uint64) {
	entry := m.getSliceItemForKey(key)

	for {
		if entry == nil {
			return
		}

		if entry.keyHash == key {
			m.linkedList.Delete(entry)
			return
		}

		if entry.keyHash > key {
			return
		}

		entry = entry.Next()
	}
}

// Add adds the value under the specified key to the map. An existing item for this key will be overwritten.
func (m *HashMap) Add(key uint64, value unsafe.Pointer) {
	newEntry := &ListElement{
		keyHash: key,
		value:   value,
	}

	for {
		mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
		index := key >> mapData.keyRightShifts
		sliceDataIndexPointer := (*unsafe.Pointer)(unsafe.Pointer(uintptr(mapData.data) + uintptr(index*intSizeBytes)))

		sliceItem := (*ListElement)(atomic.LoadPointer(sliceDataIndexPointer))
		if !m.linkedList.Add(newEntry, sliceItem) {
			continue // a concurrent add did interfere, try again
		}

		if sliceItem == nil {
			if atomic.CompareAndSwapPointer((*unsafe.Pointer)(sliceDataIndexPointer), nil, unsafe.Pointer(newEntry)) {
				newSliceCount := atomic.AddUint64(&mapData.count, 1)
				sliceLen := uint64(len(mapData.slice))
				fillRate := (newSliceCount * 100) / sliceLen

				if fillRate > MaxFillRate { // check if the slice needs to be resized
					m.Lock()
					currentMapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
					if mapData != currentMapData { // double check, a concurrent resize happened
						m.Unlock()
						continue
					}

					m.grow(0)
					m.Unlock()
					return
				}
			}
		} else {
			if newEntry.keyHash < sliceItem.keyHash { // the new item is the smallest for this index
				atomic.CompareAndSwapPointer((*unsafe.Pointer)(sliceDataIndexPointer), unsafe.Pointer(sliceItem), unsafe.Pointer(newEntry))
			}
		}

		currentMapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
		if mapData != currentMapData { // a resize operation happened while we were inserting?
			continue // retry
		}
		return
	}
}

// Grow resizes the hashmap to a new size, gets rounded up to next power of 2.
// To double the size of the hashmap use newSize 0.
func (m *HashMap) Grow(newSize uint64) {
	m.Lock()
	m.grow(newSize)
	m.Unlock()
}

// Grow resizes the hashmap to a new size, gets rounded up to next power of 2.
// To double the size of the hashmap use newSize 0.
func (m *HashMap) grow(newSize uint64) {
	mapData := (*hashMapData)(atomic.LoadPointer(&m.mapData))
	if newSize == 0 {
		newSize = uint64(len(mapData.slice)) << 1
	} else {
		newSize = roundUpPower2(newSize)
	}

	newSlice := make([]*ListElement, newSize)
	header := (*reflect.SliceHeader)(unsafe.Pointer(&newSlice))

	newMapData := &hashMapData{
		keyRightShifts: 64 - log2(newSize),
		data:           unsafe.Pointer(header.Data), // use address of slice data storage
		slice:          newSlice,
	}

	if mapData != nil { // copy hashmap contents to new slice with longer key
		first := m.linkedList.First()
		item := first
		lastIndex := uint64(0)

		for item != nil {
			index := item.keyHash >> newMapData.keyRightShifts

			if item == first || index != lastIndex { // store item with smallest hash key for every index
				newSlice[index] = item
				newMapData.count++
				lastIndex = index
			}
			item = item.Next()
		}
	}

	atomic.StorePointer(&m.mapData, unsafe.Pointer(newMapData))
}

// String returns the map as a string, only keys are printed
func (m *HashMap) String() string {
	buffer := bytes.NewBufferString("")
	buffer.WriteRune('[')

	first := m.linkedList.First()
	item := first

	for item != nil {
		if item != first {
			buffer.WriteRune(',')
		}
		fmt.Fprint(buffer, item.keyHash)

		item = item.Next()
	}

	buffer.WriteRune(']')
	return buffer.String()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"testing"

	pb "github.com/brotherlogic/gobuildslave/proto"
)

func TestRunJobOnFail(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestRunJob(t *testing.T) {
	s := getTestServer()
	s.doesBuild = false
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Job was built?")
	}
}

func TestNUpdateJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.UpdateJob(context.Background(), &pb.UpdateRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error updating job: %v", err)
	}
}

func TestNUpdateNoJob(t *testing.T) {
	s := getTestServer()
	_, err := s.UpdateJob(context.Background(), &pb.UpdateRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Error updating job: %v", err)
	}
}

func TestNKillJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.KillJob(context.Background(), &pb.KillRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestNKillJobNotRunning(t *testing.T) {
	s := getTestServer()
	_, err := s.KillJob(context.Background(), &pb.KillRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestDoubleRunJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestListJobs(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Fatalf("Unable to run job: %v", err)
	}

	list, err := s.ListJobs(context.Background(), &pb.ListRequest{})
	if err != nil {
		t.Fatalf("Unable to list jobs: %v", err)
	}

	if len(list.Jobs) != 1 {
		t.Errorf("Problem in the listing")
	}
}

func TestGetSlaveConfig(t *testing.T) {
	s := getTestServer()
	s.Registry.Identifier = "stationone"
	s.disker = &testDisker{disks: []string{"disk1"}}
	config, err := s.SlaveConfig(context.Background(), &pb.ConfigRequest{})
	if err != nil {
		t.Fatalf("Error getting config: %v", err)
	}

	if len(config.Config.Requirements) != 5 {
		t.Errorf("Requirements not been captured: (%v) %v", len(config.Config.Requirements), config)
	}
}

func TestRegMatch(t *testing.T) {
	result := `wlan0     IEEE 802.11  ESSID:SiFi
          Mode:Managed  Frequency:5.745 GHz  Access Point: 70:3A:CB:17:CF:BB
          Bit Rate=433.3 Mb/s   Tx-Power=31 dBm
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
          Link Quality=68/70  Signal level=-42 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:11684  Invalid misc:0   Missed beacon:0

lo        no wireless extensions.

eth0      no wireless extensions.`
	match, match2 := extractBitRate(result)
	if match != "433.3" || match2 != "70:3A:CB:17:CF:BB" {
		t.Errorf("Mismatch :%v:%v:", match, match2)
	}
}
<commit_msg>Fixes test<commit_after>package main

import (
	"context"
	"testing"

	pb "github.com/brotherlogic/gobuildslave/proto"
)

func TestRunJobOnFail(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestRunJob(t *testing.T) {
	s := getTestServer()
	s.doesBuild = false
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Job was built?")
	}
}

func TestNUpdateJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.UpdateJob(context.Background(), &pb.UpdateRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error updating job: %v", err)
	}
}

func TestNUpdateNoJob(t *testing.T) {
	s := getTestServer()
	_, err := s.UpdateJob(context.Background(), &pb.UpdateRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Error updating job: %v", err)
	}
}

func TestNKillJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.KillJob(context.Background(), &pb.KillRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestNKillJobNotRunning(t *testing.T) {
	s := getTestServer()
	_, err := s.KillJob(context.Background(), &pb.KillRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestDoubleRunJob(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})
	_, err = s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err == nil {
		t.Errorf("Error running job: %v", err)
	}
}

func TestListJobs(t *testing.T) {
	s := getTestServer()
	_, err := s.RunJob(context.Background(), &pb.RunRequest{Job: &pb.Job{Name: "test1"}})

	if err != nil {
		t.Fatalf("Unable to run job: %v", err)
	}

	list, err := s.ListJobs(context.Background(), &pb.ListRequest{})
	if err != nil {
		t.Fatalf("Unable to list jobs: %v", err)
	}

	if len(list.Jobs) != 1 {
		t.Errorf("Problem in the listing")
	}
}

func TestGetSlaveConfig(t *testing.T) {
	s := getTestServer()
	s.Registry.Identifier = "stationone"
	s.disker = &testDisker{disks: []string{"disk1"}}
	config, err := s.SlaveConfig(context.Background(), &pb.ConfigRequest{})
	if err != nil {
		t.Fatalf("Error getting config: %v", err)
	}

	if len(config.Config.Requirements) != 5 {
		t.Errorf("Requirements not been captured: (%v) %v", len(config.Config.Requirements), config)
	}
}

func TestRegMatch(t *testing.T) {
	result := `wlan0     IEEE 802.11  ESSID:SiFi
          Mode:Managed  Frequency:5.745 GHz  Access Point: 70:3A:CB:17:CF:BB
          Bit Rate=433.3 Mb/s   Tx-Power=31 dBm
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on
          Link Quality=68/70  Signal level=-42 dBm
          Rx invalid nwid:0  Rx invalid crypt:0  Rx invalid frag:0
          Tx excessive retries:11684  Invalid misc:0   Missed beacon:0

lo        no wireless extensions.

eth0      no wireless extensions.`
	match, match2 := extractBitRate(result)
	if match != "433.3" || match2 != "70:3A:CB:17:CF:BB" {
		t.Errorf("Mismatch :%v:%v:", match, match2)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"

	"github.com/zyedidia/clipboard"
	"github.com/zyedidia/tcell"
)

// TermMessage sends a message to the user in the terminal. This usually occurs before
// micro has been fully initialized -- ie if there is an error in the syntax highlighting
// regular expressions
// The function must be called when the screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
func TermMessage(msg ...interface{}) {
	screenWasNil := screen == nil
	if !screenWasNil {
		screen.Fini()
		screen = nil
	}

	fmt.Println(msg...)
	fmt.Print("\nPress enter to continue")

	reader := bufio.NewReader(os.Stdin)
	reader.ReadString('\n')

	if !screenWasNil {
		InitScreen()
	}
}

// TermError sends an error to the user in the terminal. Like TermMessage except formatted
// as an error
func TermError(filename string, lineNum int, err string) {
	TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
}

// Messenger is an object that makes it easy to send messages to the user
// and get input from the user
type Messenger struct {
	log *Buffer
	// Are we currently prompting the user?
	hasPrompt bool
	// Is there a message to print
	hasMessage bool

	// Message to print
	message string
	// The user's response to a prompt
	response string
	// style to use when drawing the message
	style tcell.Style

	// We have to keep track of the cursor for prompting
	cursorx int

	// This map stores the history for all the different kinds of uses Prompt has
	// It's a map of history type -> history array
	history    map[string][]string
	historyNum int

	// Is the current message a message from the gutter
	gutterMessage bool
}

func (m *Messenger) AddLog(msg string) {
	buffer := m.getBuffer()
	buffer.insert(buffer.End(), []byte(msg+"\n"))
	buffer.Cursor.Loc = buffer.End()
	buffer.Cursor.Relocate()
}

func (m *Messenger) getBuffer() *Buffer {
	if m.log == nil {
		m.log = NewBufferFromString("", "")
		m.log.name = "Log"
	}
	return m.log
}

// Message sends a message to the user
func (m *Messenger) Message(msg ...interface{}) {
	displayMessage := fmt.Sprint(msg...)
	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if m.hasPrompt == false {
		// if there is no active prompt then style and display the message as normal
		m.message = displayMessage

		m.style = defStyle

		if _, ok := colorscheme["message"]; ok {
			m.style = colorscheme["message"]
		}

		m.hasMessage = true
	}
	// add the message to the log regardless of active prompts
	m.AddLog(displayMessage)
}

// Error sends an error message to the user
func (m *Messenger) Error(msg ...interface{}) {
	buf := new(bytes.Buffer)
	fmt.Fprint(buf, msg...)

	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if m.hasPrompt == false {
		// if there is no active prompt then style and display the message as normal
		m.message = buf.String()
		m.style = defStyle.
			Foreground(tcell.ColorBlack).
			Background(tcell.ColorMaroon)

		if _, ok := colorscheme["error-message"]; ok {
			m.style = colorscheme["error-message"]
		}
		m.hasMessage = true
	}
	// add the message to the log regardless of active prompts
	m.AddLog(buf.String())
}

func (m *Messenger) PromptText(msg ...interface{}) {
	displayMessage := fmt.Sprint(msg...)
	// if there is no active prompt then style and display the message as normal
	m.message = displayMessage

	m.style = defStyle

	if _, ok := colorscheme["message"]; ok {
		m.style = colorscheme["message"]
	}

	m.hasMessage = true
	// add the message to the log regardless of active prompts
	m.AddLog(displayMessage)
}

// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)

	_, h := screen.Size()
	for {
		m.Clear()
		m.Display()
		screen.ShowCursor(Count(m.message), h-1)
		screen.Show()
		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyRune:
				if e.Rune() == 'y' || e.Rune() == 'Y' {
					m.AddLog("\t--> y")
					m.hasPrompt = false
					return true, false
				} else if e.Rune() == 'n' || e.Rune() == 'N' {
					m.AddLog("\t--> n")
					m.hasPrompt = false
					return false, false
				}
			case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
				m.AddLog("\t--> (cancel)")
				m.Clear()
				m.Reset()
				m.hasPrompt = false
				return false, true
			}
		}
	}
}

// LetterPrompt gives the user a prompt and waits for a one letter response
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)

	_, h := screen.Size()
	for {
		m.Clear()
		m.Display()
		screen.ShowCursor(Count(m.message), h-1)
		screen.Show()
		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyRune:
				for _, r := range responses {
					if e.Rune() == r {
						m.AddLog("\t--> " + string(r))
						m.Clear()
						m.Reset()
						m.hasPrompt = false
						return r, false
					}
				}
			case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
				m.AddLog("\t--> (cancel)")
				m.Clear()
				m.Reset()
				m.hasPrompt = false
				return ' ', true
			}
		}
	}
}

type Completion int

const (
	NoCompletion Completion = iota
	FileCompletion
	CommandCompletion
	HelpCompletion
	OptionCompletion
	PluginCmdCompletion
	PluginNameCompletion
)

// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)
	if _, ok := m.history[historyType]; !ok {
		m.history[historyType] = []string{""}
	} else {
		m.history[historyType] = append(m.history[historyType], "")
	}
	m.historyNum = len(m.history[historyType]) - 1

	response, canceled := placeholder, true
	m.response = response
	m.cursorx = Count(placeholder)

	RedrawAll()
	for m.hasPrompt {
		var suggestions []string
		m.Clear()

		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
				// Cancel
				m.AddLog("\t--> (cancel)")
				m.hasPrompt = false
			case tcell.KeyEnter:
				// User is done entering their response
				m.AddLog("\t--> " + m.response)
				m.hasPrompt = false
				response, canceled = m.response, false
				m.history[historyType][len(m.history[historyType])-1] = response
			case tcell.KeyTab:
				args := SplitCommandArgs(m.response)
				currentArgNum := len(args) - 1
				currentArg := args[currentArgNum]
				var completionType Completion

				if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
					if command, ok := commands[args[0]]; ok {
						completionTypes = append([]Completion{CommandCompletion}, command.completions...)
					}
				}

				if currentArgNum >= len(completionTypes) {
					completionType = completionTypes[len(completionTypes)-1]
				} else {
					completionType = completionTypes[currentArgNum]
				}

				var chosen string
				if completionType == FileCompletion {
					chosen, suggestions = FileComplete(currentArg)
				} else if completionType == CommandCompletion {
					chosen, suggestions = CommandComplete(currentArg)
				} else if completionType == HelpCompletion {
					chosen, suggestions = HelpComplete(currentArg)
				} else if completionType == OptionCompletion {
					chosen, suggestions = OptionComplete(currentArg)
				} else if completionType == PluginCmdCompletion {
					chosen, suggestions = PluginCmdComplete(currentArg)
				} else if completionType == PluginNameCompletion {
					chosen, suggestions = PluginNameComplete(currentArg)
				} else if completionType < NoCompletion {
					chosen, suggestions = PluginComplete(completionType, currentArg)
				}

				if len(suggestions) > 1 {
					chosen = chosen + CommonSubstring(suggestions...)
				}

				if chosen != "" {
					m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
					m.cursorx = Count(m.response)
				}
			}
		}

		m.HandleEvent(event, m.history[historyType])

		m.Clear()
		for _, v := range tabs[curTab].views {
			v.Display()
		}
		DisplayTabs()
		m.Display()
		if len(suggestions) > 1 {
			m.DisplaySuggestions(suggestions)
		}
		screen.Show()
	}

	m.Clear()
	m.Reset()
	return response, canceled
}

// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
	switch e := event.(type) {
	case *tcell.EventKey:
		if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
			for key, actions := range bindings {
				if e.Key() == key.keyCode {
					if e.Key() == tcell.KeyRune {
						if e.Rune() != key.r {
							continue
						}
					}
					if e.Modifiers() == key.modifiers {
						for _, action := range actions {
							funcName := FuncName(action)
							switch funcName {
							case "main.(*View).CursorUp":
								if m.historyNum > 0 {
									m.historyNum--
									m.response = history[m.historyNum]
									m.cursorx = Count(m.response)
								}
							case "main.(*View).CursorDown":
								if m.historyNum < len(history)-1 {
									m.historyNum++
									m.response = history[m.historyNum]
									m.cursorx = Count(m.response)
								}
							case "main.(*View).CursorLeft":
								if m.cursorx > 0 {
									m.cursorx--
								}
							case "main.(*View).CursorRight":
								if m.cursorx < Count(m.response) {
									m.cursorx++
								}
							case "main.(*View).CursorStart", "main.(*View).StartOfLine":
								m.cursorx = 0
							case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
								m.cursorx = Count(m.response)
							case "main.(*View).Backspace":
								if m.cursorx > 0 {
									m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
									m.cursorx--
								}
							case "main.(*View).Paste":
								clip, _ := clipboard.ReadAll("clipboard")
								m.response = Insert(m.response, m.cursorx, clip)
								m.cursorx += Count(clip)
							}
						}
					}
				}
			}
		}
		switch e.Key() {
		case tcell.KeyRune:
			m.response = Insert(m.response, m.cursorx, string(e.Rune()))
			m.cursorx++
		}
		history[m.historyNum] = m.response

	case *tcell.EventPaste:
		clip := e.Text()
		m.response = Insert(m.response, m.cursorx, clip)
		m.cursorx += Count(clip)
	case *tcell.EventMouse:
		x, y := e.Position()
		x -= Count(m.message)
		button := e.Buttons()
		_, screenH := screen.Size()

		if y == screenH-1 {
			switch button {
			case tcell.Button1:
				m.cursorx = x
				if m.cursorx < 0 {
					m.cursorx = 0
				} else if m.cursorx > Count(m.response) {
					m.cursorx = Count(m.response)
				}
			}
		}
	}
}

// Reset resets the messenger's cursor, message and response
func (m *Messenger) Reset() {
	m.cursorx = 0
	m.message = ""
	m.response = ""
}

// Clear clears the line at the bottom of the editor
func (m *Messenger) Clear() {
	w, h := screen.Size()
	for x := 0; x < w; x++ {
		screen.SetContent(x, h-1, ' ', nil, defStyle)
	}
}

func (m *Messenger) DisplaySuggestions(suggestions []string) {
	w, screenH := screen.Size()

	y := screenH - 2

	statusLineStyle := defStyle.Reverse(true)
	if style, ok := colorscheme["statusline"]; ok {
		statusLineStyle = style
	}

	for x := 0; x < w; x++ {
		screen.SetContent(x, y, ' ', nil, statusLineStyle)
	}

	x := 0
	for _, suggestion := range suggestions {
		for _, c := range suggestion {
			screen.SetContent(x, y, c, nil, statusLineStyle)
			x++
		}
		screen.SetContent(x, y, ' ', nil, statusLineStyle)
		x++
	}
}

// Display displays messages or prompts
func (m *Messenger) Display() {
	_, h := screen.Size()
	if m.hasMessage {
		if m.hasPrompt || globalSettings["infobar"].(bool) {
			runes := []rune(m.message + m.response)
			for x := 0; x < len(runes); x++ {
				screen.SetContent(x, h-1, runes[x], nil, m.style)
			}
		}
	}

	if m.hasPrompt {
		screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
		screen.Show()
	}
}

// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
	lineNum int
	msg     string
	kind    int
}

// These are the different types of messages
const (
	// GutterInfo represents a simple info message
	GutterInfo = iota
	// GutterWarning represents a compiler warning
	GutterWarning
	// GutterError represents a compiler error
	GutterError
)
<commit_msg>multi-byte characters are overlapped in prompt message (#745)<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"os"
	"strconv"

	"github.com/zyedidia/clipboard"
	"github.com/zyedidia/tcell"
	"github.com/mattn/go-runewidth"
)

// TermMessage sends a message to the user in the terminal. This usually occurs before
// micro has been fully initialized -- ie if there is an error in the syntax highlighting
// regular expressions
// The function must be called when the screen is not initialized
// This will write the message, and wait for the user
// to press and key to continue
func TermMessage(msg ...interface{}) {
	screenWasNil := screen == nil
	if !screenWasNil {
		screen.Fini()
		screen = nil
	}

	fmt.Println(msg...)
	fmt.Print("\nPress enter to continue")

	reader := bufio.NewReader(os.Stdin)
	reader.ReadString('\n')

	if !screenWasNil {
		InitScreen()
	}
}

// TermError sends an error to the user in the terminal. Like TermMessage except formatted
// as an error
func TermError(filename string, lineNum int, err string) {
	TermMessage(filename + ", " + strconv.Itoa(lineNum) + ": " + err)
}

// Messenger is an object that makes it easy to send messages to the user
// and get input from the user
type Messenger struct {
	log *Buffer
	// Are we currently prompting the user?
	hasPrompt bool
	// Is there a message to print
	hasMessage bool

	// Message to print
	message string
	// The user's response to a prompt
	response string
	// style to use when drawing the message
	style tcell.Style

	// We have to keep track of the cursor for prompting
	cursorx int

	// This map stores the history for all the different kinds of uses Prompt has
	// It's a map of history type -> history array
	history    map[string][]string
	historyNum int

	// Is the current message a message from the gutter
	gutterMessage bool
}

func (m *Messenger) AddLog(msg string) {
	buffer := m.getBuffer()
	buffer.insert(buffer.End(), []byte(msg+"\n"))
	buffer.Cursor.Loc = buffer.End()
	buffer.Cursor.Relocate()
}

func (m *Messenger) getBuffer() *Buffer {
	if m.log == nil {
		m.log = NewBufferFromString("", "")
		m.log.name = "Log"
	}
	return m.log
}

// Message sends a message to the user
func (m *Messenger) Message(msg ...interface{}) {
	displayMessage := fmt.Sprint(msg...)
	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if m.hasPrompt == false {
		// if there is no active prompt then style and display the message as normal
		m.message = displayMessage

		m.style = defStyle

		if _, ok := colorscheme["message"]; ok {
			m.style = colorscheme["message"]
		}

		m.hasMessage = true
	}
	// add the message to the log regardless of active prompts
	m.AddLog(displayMessage)
}

// Error sends an error message to the user
func (m *Messenger) Error(msg ...interface{}) {
	buf := new(bytes.Buffer)
	fmt.Fprint(buf, msg...)

	// only display a new message if there isn't an active prompt
	// this is to prevent overwriting an existing prompt to the user
	if m.hasPrompt == false {
		// if there is no active prompt then style and display the message as normal
		m.message = buf.String()
		m.style = defStyle.
			Foreground(tcell.ColorBlack).
			Background(tcell.ColorMaroon)

		if _, ok := colorscheme["error-message"]; ok {
			m.style = colorscheme["error-message"]
		}
		m.hasMessage = true
	}
	// add the message to the log regardless of active prompts
	m.AddLog(buf.String())
}

func (m *Messenger) PromptText(msg ...interface{}) {
	displayMessage := fmt.Sprint(msg...)
	// if there is no active prompt then style and display the message as normal
	m.message = displayMessage

	m.style = defStyle

	if _, ok := colorscheme["message"]; ok {
		m.style = colorscheme["message"]
	}

	m.hasMessage = true
	// add the message to the log regardless of active prompts
	m.AddLog(displayMessage)
}

// YesNoPrompt asks the user a yes or no question (waits for y or n) and returns the result
func (m *Messenger) YesNoPrompt(prompt string) (bool, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)

	_, h := screen.Size()
	for {
		m.Clear()
		m.Display()
		screen.ShowCursor(Count(m.message), h-1)
		screen.Show()
		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyRune:
				if e.Rune() == 'y' || e.Rune() == 'Y' {
					m.AddLog("\t--> y")
					m.hasPrompt = false
					return true, false
				} else if e.Rune() == 'n' || e.Rune() == 'N' {
					m.AddLog("\t--> n")
					m.hasPrompt = false
					return false, false
				}
			case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
				m.AddLog("\t--> (cancel)")
				m.Clear()
				m.Reset()
				m.hasPrompt = false
				return false, true
			}
		}
	}
}

// LetterPrompt gives the user a prompt and waits for a one letter response
func (m *Messenger) LetterPrompt(prompt string, responses ...rune) (rune, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)

	_, h := screen.Size()
	for {
		m.Clear()
		m.Display()
		screen.ShowCursor(Count(m.message), h-1)
		screen.Show()
		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyRune:
				for _, r := range responses {
					if e.Rune() == r {
						m.AddLog("\t--> " + string(r))
						m.Clear()
						m.Reset()
						m.hasPrompt = false
						return r, false
					}
				}
			case tcell.KeyCtrlC, tcell.KeyCtrlQ, tcell.KeyEscape:
				m.AddLog("\t--> (cancel)")
				m.Clear()
				m.Reset()
				m.hasPrompt = false
				return ' ', true
			}
		}
	}
}

type Completion int

const (
	NoCompletion Completion = iota
	FileCompletion
	CommandCompletion
	HelpCompletion
	OptionCompletion
	PluginCmdCompletion
	PluginNameCompletion
)

// Prompt sends the user a message and waits for a response to be typed in
// This function blocks the main loop while waiting for input
func (m *Messenger) Prompt(prompt, placeholder, historyType string, completionTypes ...Completion) (string, bool) {
	m.hasPrompt = true
	m.PromptText(prompt)
	if _, ok := m.history[historyType]; !ok {
		m.history[historyType] = []string{""}
	} else {
		m.history[historyType] = append(m.history[historyType], "")
	}
	m.historyNum = len(m.history[historyType]) - 1

	response, canceled := placeholder, true
	m.response = response
	m.cursorx = Count(placeholder)

	RedrawAll()
	for m.hasPrompt {
		var suggestions []string
		m.Clear()

		event := <-events

		switch e := event.(type) {
		case *tcell.EventKey:
			switch e.Key() {
			case tcell.KeyCtrlQ, tcell.KeyCtrlC, tcell.KeyEscape:
				// Cancel
				m.AddLog("\t--> (cancel)")
				m.hasPrompt = false
			case tcell.KeyEnter:
				// User is done entering their response
				m.AddLog("\t--> " + m.response)
				m.hasPrompt = false
				response, canceled = m.response, false
				m.history[historyType][len(m.history[historyType])-1] = response
			case tcell.KeyTab:
				args := SplitCommandArgs(m.response)
				currentArgNum := len(args) - 1
				currentArg := args[currentArgNum]
				var completionType Completion

				if completionTypes[0] == CommandCompletion && currentArgNum > 0 {
					if command, ok := commands[args[0]]; ok {
						completionTypes = append([]Completion{CommandCompletion}, command.completions...)
					}
				}

				if currentArgNum >= len(completionTypes) {
					completionType = completionTypes[len(completionTypes)-1]
				} else {
					completionType = completionTypes[currentArgNum]
				}

				var chosen string
				if completionType == FileCompletion {
					chosen, suggestions = FileComplete(currentArg)
				} else if completionType == CommandCompletion {
					chosen, suggestions = CommandComplete(currentArg)
				} else if completionType == HelpCompletion {
					chosen, suggestions = HelpComplete(currentArg)
				} else if completionType == OptionCompletion {
					chosen, suggestions = OptionComplete(currentArg)
				} else if completionType == PluginCmdCompletion {
					chosen, suggestions = PluginCmdComplete(currentArg)
				} else if completionType == PluginNameCompletion {
					chosen, suggestions = PluginNameComplete(currentArg)
				} else if completionType < NoCompletion {
					chosen, suggestions = PluginComplete(completionType, currentArg)
				}

				if len(suggestions) > 1 {
					chosen = chosen + CommonSubstring(suggestions...)
				}

				if chosen != "" {
					m.response = JoinCommandArgs(append(args[:len(args)-1], chosen)...)
					m.cursorx = Count(m.response)
				}
			}
		}

		m.HandleEvent(event, m.history[historyType])

		m.Clear()
		for _, v := range tabs[curTab].views {
			v.Display()
		}
		DisplayTabs()
		m.Display()
		if len(suggestions) > 1 {
			m.DisplaySuggestions(suggestions)
		}
		screen.Show()
	}

	m.Clear()
	m.Reset()
	return response, canceled
}

// HandleEvent handles an event for the prompter
func (m *Messenger) HandleEvent(event tcell.Event, history []string) {
	switch e := event.(type) {
	case *tcell.EventKey:
		if e.Key() != tcell.KeyRune || e.Modifiers() != 0 {
			for key, actions := range bindings {
				if e.Key() == key.keyCode {
					if e.Key() == tcell.KeyRune {
						if e.Rune() != key.r {
							continue
						}
					}
					if e.Modifiers() == key.modifiers {
						for _, action := range actions {
							funcName := FuncName(action)
							switch funcName {
							case "main.(*View).CursorUp":
								if m.historyNum > 0 {
									m.historyNum--
									m.response = history[m.historyNum]
									m.cursorx = Count(m.response)
								}
							case "main.(*View).CursorDown":
								if m.historyNum < len(history)-1 {
									m.historyNum++
									m.response = history[m.historyNum]
									m.cursorx = Count(m.response)
								}
							case "main.(*View).CursorLeft":
								if m.cursorx > 0 {
									m.cursorx--
								}
							case "main.(*View).CursorRight":
								if m.cursorx < Count(m.response) {
									m.cursorx++
								}
							case "main.(*View).CursorStart", "main.(*View).StartOfLine":
								m.cursorx = 0
							case "main.(*View).CursorEnd", "main.(*View).EndOfLine":
								m.cursorx = Count(m.response)
							case "main.(*View).Backspace":
								if m.cursorx > 0 {
									m.response = string([]rune(m.response)[:m.cursorx-1]) + string([]rune(m.response)[m.cursorx:])
									m.cursorx--
								}
							case "main.(*View).Paste":
								clip, _ := clipboard.ReadAll("clipboard")
								m.response = Insert(m.response, m.cursorx, clip)
								m.cursorx += Count(clip)
							}
						}
					}
				}
			}
		}
		switch e.Key() {
		case tcell.KeyRune:
			m.response = Insert(m.response, m.cursorx, string(e.Rune()))
			m.cursorx++
		}
		history[m.historyNum] = m.response

	case *tcell.EventPaste:
		clip := e.Text()
		m.response = Insert(m.response, m.cursorx, clip)
		m.cursorx += Count(clip)
	case *tcell.EventMouse:
		x, y := e.Position()
		x -= Count(m.message)
		button := e.Buttons()
		_, screenH := screen.Size()

		if y == screenH-1 {
			switch button {
			case tcell.Button1:
				m.cursorx = x
				if m.cursorx < 0 {
					m.cursorx = 0
				} else if m.cursorx > Count(m.response) {
					m.cursorx = Count(m.response)
				}
			}
		}
	}
}

// Reset resets the messenger's cursor, message and response
func (m *Messenger) Reset() {
	m.cursorx = 0
	m.message = ""
	m.response = ""
}

// Clear clears the line at the bottom of the editor
func (m *Messenger) Clear() {
	w, h := screen.Size()
	for x := 0; x < w; x++ {
		screen.SetContent(x, h-1, ' ', nil, defStyle)
	}
}

func (m *Messenger) DisplaySuggestions(suggestions []string) {
	w, screenH := screen.Size()

	y := screenH - 2

	statusLineStyle := defStyle.Reverse(true)
	if style, ok := colorscheme["statusline"]; ok {
		statusLineStyle = style
	}

	for x := 0; x < w; x++ {
		screen.SetContent(x, y, ' ', nil, statusLineStyle)
	}

	x := 0
	for _, suggestion := range suggestions {
		for _, c := range suggestion {
			screen.SetContent(x, y, c, nil, statusLineStyle)
			x++
		}
		screen.SetContent(x, y, ' ', nil, statusLineStyle)
		x++
	}
}

// Display displays messages or prompts
func (m *Messenger) Display() {
	_, h := screen.Size()
	if m.hasMessage {
		if m.hasPrompt || globalSettings["infobar"].(bool) {
			runes := []rune(m.message + m.response)
			posx := 0
			for x := 0; x < len(runes); x++ {
				screen.SetContent(posx, h-1, runes[x], nil, m.style)
				posx += runewidth.RuneWidth(runes[x])
			}
		}
	}

	if m.hasPrompt {
		screen.ShowCursor(Count(m.message)+m.cursorx, h-1)
		screen.Show()
	}
}

// A GutterMessage is a message displayed on the side of the editor
type GutterMessage struct {
	lineNum int
	msg     string
	kind    int
}

// These are the different types of messages
const (
	// GutterInfo represents a simple info message
	GutterInfo = iota
	// GutterWarning represents a compiler warning
	GutterWarning
	// GutterError represents a compiler error
	GutterError
)
<|endoftext|>


<commit_before>package mc

import (
	"time"

	"crypto/md5"
	"io"
	"math"
	"os"

	"path/filepath"

	"fmt"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/gohandy/with"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/app/flow"
	"github.com/materials-commons/mcstore/pkg/files"
	"github.com/materials-commons/mcstore/server/mcstore/mcstoreapi"
	"sync"
)

// projectUploader holds the starting state for a project upload. It controls
// how many threads process requests.
type projectUploader struct {
	db         ProjectDB
	numThreads int
}

// uploadProject will upload the project. It starts a parallel walker to process entries.
func (p *projectUploader) uploadProject() error {
	db := p.db
	project := db.Project()

	// create a func to process entries.
	fn := func(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
		uploader := newUploader(p.db, project)
		uploader.retrier.RetryCount = 1
		uploader.uploadEntries(done, entries, result)
	}

	walker := files.PWalker{
		NumParallel: 1,
		ProcessFn:   fn,
		ProcessDirs: true,
	}

	_, errc := walker.PWalk(project.Path)
	err := <-errc
	return err
}

// uploadFile will upload a single file.
func (p *projectUploader) uploadFile(path string) error {
	uploader := newUploader(p.db, p.db.Project())
	finfo, err := os.Stat(path)
	if err != nil {
		return err
	}

	treeEntry := files.TreeEntry{
		Finfo: finfo,
		Path:  path,
	}

	uploader.handleFileEntry(treeEntry)
	return nil
}

// uploadDirectory will upload a single directory. It will ignore sub directories when ignoreSubDirectories
// is true.
func (p *projectUploader) uploadDirectory(path string, recursive bool) error {
	// create a custom ignore function that will ignore sub directories if recursive is false.
	ignoreFunc := func(pathEntry string, fileInfo os.FileInfo) bool {
		if fileInfo.IsDir() && !recursive {
			// Special case pathEntry is the dir we are uploading. In that case return
			// false (do not ignore)
			if path == pathEntry {
				return false
			}
			return true
		}
		return files.IgnoreDotAndTempFiles(pathEntry, fileInfo)
	}
	db := p.db
	project := db.Project()

	// create a func to process entries.
	fn := func(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
		uploader := newUploader(p.db, project)
		uploader.retrier.RetryCount = 1
		uploader.uploadEntries(done, entries, result)
	}

	walker := files.PWalker{
		NumParallel: 1,
		ProcessFn:   fn,
		ProcessDirs: true,
		IgnoreFn:    ignoreFunc,
	}

	_, errc := walker.PWalk(path)
	err := <-errc
	return err
}

// uploader holds the state for one upload thread.
type uploader struct {
	db        ProjectDB
	serverAPI *mcstoreapi.ServerAPI
	project   *Project
	retrier   with.Retrier
}

// newUploader creates a new uploader. It creates a clone of the database.
func newUploader(db ProjectDB, project *Project) *uploader {
	return &uploader{
		db:        db.Clone(),
		project:   project,
		serverAPI: mcstoreapi.NewServerAPI(),
	}
}

// uploadEntries waits on the entries and done channels. When  entry comes in on
// the entries channel it will process it. If an something comes in on the done
// channel then it will stop processing and return from the go routine.
func (u *uploader) uploadEntries(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
	for entry := range entries {
		select {
		case result <- u.uploadEntry(entry):
		case <-done:
			return
		}
	}
}

// uploadEntry identifies the type of entry it is processing (file or directory) and
// processes it appropriately.
func (u *uploader) uploadEntry(entry files.TreeEntry) string {
	switch {
	case entry.Finfo.IsDir():
		u.handleDirEntry(entry)
	default:
		u.handleFileEntry(entry)
	}
	return ""
}

// handleDirEntry handles processing of directories. It checks if the directory already exists in the
// local database. If it doesn't it will create the directory on the server and insert the directory
// into its local database.
func (u *uploader) handleDirEntry(entry files.TreeEntry) {
	path := filepath.ToSlash(entry.Path)

	_, err := u.db.FindDirectory(path)
	switch {
	case err == app.ErrNotFound:
		u.createDirectory(entry)
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
	default:
		// directory already known nothing to do
		return
	}
}

// createDirectory creates a new directory entry on the server.
func (u *uploader) createDirectory(entry files.TreeEntry) {
	dirPath := filepath.ToSlash(entry.Path)
	req := mcstoreapi.DirectoryRequest{
		ProjectName: u.project.Name,
		ProjectID:   u.project.ProjectID,
		Path:        dirPath,
	}
	dirID, _ := u.getDirectory(req)
	dir := &Directory{
		DirectoryID: dirID,
		Path:        dirPath,
	}
	if _, err := u.db.InsertDirectory(dir); err != nil {
		app.Log.Panicf("Local database returned err, panic!: %s", err)
	}
}

// getDirectoryWithRetry will make the server API GetDirectory call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) getDirectory(req mcstoreapi.DirectoryRequest) (string, error) {
	var (
		dirID string
		err   error
	)
	if dirID, err = u.serverAPI.GetDirectory(req); err != nil {
		app.Log.Errorf("Failed creating directory: %#v/%s", req, err)
		return "", err
	}
	return dirID, nil
}

// handleFileEntry will handle processing file entries. It will check if the file
// has already been uploaded. If it hasn't it will upload the file to the server.
func (u *uploader) handleFileEntry(entry files.TreeEntry) {
	if dir := u.getDirByPath(filepath.Dir(entry.Path)); dir == nil {
		app.Log.Exitf("Should have found dir %s", filepath.Dir(entry.Path))
	} else {
		file := u.getFileByName(entry.Finfo.Name(), dir.ID)
		switch {
		case file == nil:
			fmt.Println("Uploading new file:", entry.Path)
			u.uploadFile(entry, file, dir)
		case entry.Finfo.ModTime().Unix() > file.MTime.Unix():
			fmt.Println("Uploading changed file:", entry.Path)
			u.uploadFile(entry, file, dir)
		default:
			fmt.Println("File already uploaded:", entry.Path)
			// nothing to do
		}
	}
}

// getDirByPath looks up a path in the local database. It normalizes
// the file separator.
func (u *uploader) getDirByPath(path string) *Directory {
	dir, err := u.db.FindDirectory(path)
	switch {
	case err == app.ErrNotFound:
		return nil
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
		return nil
	default:
		// directory already known nothing to do
		return dir
	}
}

// getFileByName looks up a file by name and directory id in the local database.
func (u *uploader) getFileByName(name string, dirID int64) *File {
	f, err := u.db.FindFile(name, dirID)
	switch {
	case err == app.ErrNotFound:
		return nil
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
		return nil
	default:
		return f
	}
}

// uploadFile will upload the file blocks to the server. It will create an upload request
// and then start uploading the blocks. When it completes it will update the local database
// state for the file.
func (u *uploader) uploadFile(entry files.TreeEntry, file *File, dir *Directory) {
	uploadResponse, checksum := u.getUploadResponse(dir.DirectoryID, entry)
	requestID := uploadResponse.RequestID

	var (
		_ = checksum

		// TODO: do something with the starting block (its ignored for now)
		n         int
		err       error
		uploadErr error
		isDone    bool
		mutex     sync.Mutex
	)
	uploadChan := make(chan *flow.Request)
	chunkNumber := 1

	done := make(chan struct{})

	//************** CHANGE THE CODE TO MATCH THIS COMMENT **********
	//************** CHANGE THE CODE TO MATCH THIS COMMENT **********
	//************** CHANGE THE CODE TO MATCH THIS COMMENT **********
	//************** CHANGE THE CODE TO MATCH THIS COMMENT **********
	//************** CHANGE THE CODE TO MATCH THIS COMMENT **********
	// Need to assign to uploadResp and not isDone flag because the uploadResp is used after reading
	// and sending the file.
	uploadFunc := func(doneChan <-chan struct{}, c <-chan *flow.Request) {
		for req := range c {
			select {
			case <-doneChan:
				return

			default:
				uploadResp, err := u.sendFlowData(req)
				if err != nil {
					mutex.Lock()
					uploadErr = err
					mutex.Unlock()
				} else {
					if uploadResp.Done {
						mutex.Lock()
						isDone = true
						mutex.Unlock()
					}
				}
			}
		}
	}

	for i := 0; i < 5; i++ {
		go uploadFunc(done, uploadChan)
	}

	f, _ := os.Open(entry.Path)
	defer f.Close()
	buf := make([]byte, 1024*1024)
	totalChunks := numChunks(entry.Finfo.Size())
	var uploadResp *mcstoreapi.UploadChunkResponse
	for {
		n, err = f.Read(buf)
		if n != 0 {
			// send bytes
			req := &flow.Request{
				FlowChunkNumber:  int32(chunkNumber),
				FlowTotalChunks:  totalChunks,
				FlowChunkSize:    int32(n),
				FlowTotalSize:    entry.Finfo.Size(),
				FlowIdentifier:   requestID,
				FlowFileName:     entry.Finfo.Name(),
				FlowRelativePath: "",
				ProjectID:        u.project.ProjectID,
				DirectoryID:      dir.DirectoryID,
				Chunk:            buf[:n],
			}
			uploadChan <- req
			//			uploadResp, _ = u.sendFlowData(req)
			//			if uploadResp.Done {
			//				break
			//			}
			chunkNumber++
		}
		if err != nil {
			break
		}
	}

	close(done)

	if !isDone {
		app.Log.Errorf("uploadResp not done %#v\n", uploadResp)
		return
	}

	if err != nil && err != io.EOF {
		app.Log.Errorf("Unable to complete read on file for upload: %s", entry.Path)
	} else {
		// done, add or update the entry in the database.
		if file == nil {
			// create new entry
			newFile := File{
				FileID:     uploadResp.FileID,
				Name:       entry.Finfo.Name(),
				Checksum:   checksum,
				Size:       entry.Finfo.Size(),
				MTime:      entry.Finfo.ModTime(),
				LastUpload: time.Now(),
				Directory:  dir.ID,
			}
			u.db.InsertFile(&newFile)
		} else {
			// update existing entry
			file.MTime = entry.Finfo.ModTime()
			file.LastUpload = time.Now()
			if file.Checksum != checksum {
				// Existing file uploaded but we created a new version on the server.
				// We could get here if a previous upload did not complete.
				file.Checksum = checksum
				file.FileID = uploadResp.FileID
				file.Size = entry.Finfo.Size()
			}
			u.db.UpdateFile(file)
		}
	}
}

// getUploadResponse sends an upload request to the server and gets the response.
func (u *uploader) getUploadResponse(directoryID string, entry files.TreeEntry) (*mcstoreapi.CreateUploadResponse, string) {
	checksum, _ := file.HashStr(md5.New(), entry.Path)
	chunkSize := int32(1024 * 1024)
	uploadReq := mcstoreapi.CreateUploadRequest{
		ProjectID:   u.project.ProjectID,
		DirectoryID: directoryID,
		FileName:    entry.Finfo.Name(),
		FileSize:    entry.Finfo.Size(),
		ChunkSize:   chunkSize,
		FileMTime:   entry.Finfo.ModTime().Format(time.RFC1123),
		Checksum:    checksum,
	}
	resp, _ := u.createUploadRequest(uploadReq)
	return resp, checksum
}

// createUploadRequestWithRetry will make the server API CreateUploadRequest call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) createUploadRequest(uploadReq mcstoreapi.CreateUploadRequest) (*mcstoreapi.CreateUploadResponse, error) {
	var (
		resp *mcstoreapi.CreateUploadResponse
		err  error
	)
	if resp, err = u.serverAPI.CreateUpload(uploadReq); err != nil {
		return nil, err
	}

	return resp, nil
}

// numChunks determines the number of chunks that will be sent to the server.
func numChunks(size int64) int32 {
	d := float64(size) / float64(1024*1024)
	n := int(math.Ceil(d))
	return int32(n)
}

// sendFlowDataWithRetry will make the server API SendFlowData call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) sendFlowData(req *flow.Request) (*mcstoreapi.UploadChunkResponse, error) {
	var (
		resp *mcstoreapi.UploadChunkResponse
		err  error
	)
	if resp, err = u.serverAPI.SendFlowData(req); err != nil {
		return nil, err
	}
	return resp, nil
}
<commit_msg>Add waiting for all go routines to complete before checking status.<commit_after>package mc

import (
	"time"

	"crypto/md5"
	"io"
	"math"
	"os"

	"path/filepath"

	"fmt"

	"sync"

	"github.com/materials-commons/gohandy/file"
	"github.com/materials-commons/gohandy/with"
	"github.com/materials-commons/mcstore/pkg/app"
	"github.com/materials-commons/mcstore/pkg/app/flow"
	"github.com/materials-commons/mcstore/pkg/files"
	"github.com/materials-commons/mcstore/server/mcstore/mcstoreapi"
)

// projectUploader holds the starting state for a project upload. It controls
// how many threads process requests.
type projectUploader struct {
	db         ProjectDB
	numThreads int
}

// uploadProject will upload the project. It starts a parallel walker to process entries.
func (p *projectUploader) uploadProject() error {
	db := p.db
	project := db.Project()

	// create a func to process entries.
	fn := func(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
		uploader := newUploader(p.db, project)
		uploader.retrier.RetryCount = 1
		uploader.uploadEntries(done, entries, result)
	}

	walker := files.PWalker{
		NumParallel: 1,
		ProcessFn:   fn,
		ProcessDirs: true,
	}

	_, errc := walker.PWalk(project.Path)
	err := <-errc
	return err
}

// uploadFile will upload a single file.
func (p *projectUploader) uploadFile(path string) error {
	uploader := newUploader(p.db, p.db.Project())
	finfo, err := os.Stat(path)
	if err != nil {
		return err
	}

	treeEntry := files.TreeEntry{
		Finfo: finfo,
		Path:  path,
	}

	uploader.handleFileEntry(treeEntry)
	return nil
}

// uploadDirectory will upload a single directory. It will ignore sub directories when ignoreSubDirectories
// is true.
func (p *projectUploader) uploadDirectory(path string, recursive bool) error {
	// create a custom ignore function that will ignore sub directories if recursive is false.
	ignoreFunc := func(pathEntry string, fileInfo os.FileInfo) bool {
		if fileInfo.IsDir() && !recursive {
			// Special case pathEntry is the dir we are uploading. In that case return
			// false (do not ignore)
			if path == pathEntry {
				return false
			}
			return true
		}
		return files.IgnoreDotAndTempFiles(pathEntry, fileInfo)
	}
	db := p.db
	project := db.Project()

	// create a func to process entries.
	fn := func(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
		uploader := newUploader(p.db, project)
		uploader.retrier.RetryCount = 1
		uploader.uploadEntries(done, entries, result)
	}

	walker := files.PWalker{
		NumParallel: 1,
		ProcessFn:   fn,
		ProcessDirs: true,
		IgnoreFn:    ignoreFunc,
	}

	_, errc := walker.PWalk(path)
	err := <-errc
	return err
}

// uploader holds the state for one upload thread.
type uploader struct {
	db        ProjectDB
	serverAPI *mcstoreapi.ServerAPI
	project   *Project
	retrier   with.Retrier
}

// newUploader creates a new uploader. It creates a clone of the database.
func newUploader(db ProjectDB, project *Project) *uploader {
	return &uploader{
		db:        db.Clone(),
		project:   project,
		serverAPI: mcstoreapi.NewServerAPI(),
	}
}

// uploadEntries waits on the entries and done channels. When  entry comes in on
// the entries channel it will process it. If an something comes in on the done
// channel then it will stop processing and return from the go routine.
func (u *uploader) uploadEntries(done <-chan struct{}, entries <-chan files.TreeEntry, result chan<- string) {
	for entry := range entries {
		select {
		case result <- u.uploadEntry(entry):
		case <-done:
			return
		}
	}
}

// uploadEntry identifies the type of entry it is processing (file or directory) and
// processes it appropriately.
func (u *uploader) uploadEntry(entry files.TreeEntry) string {
	switch {
	case entry.Finfo.IsDir():
		u.handleDirEntry(entry)
	default:
		u.handleFileEntry(entry)
	}
	return ""
}

// handleDirEntry handles processing of directories. It checks if the directory already exists in the
// local database. If it doesn't it will create the directory on the server and insert the directory
// into its local database.
func (u *uploader) handleDirEntry(entry files.TreeEntry) {
	path := filepath.ToSlash(entry.Path)

	_, err := u.db.FindDirectory(path)
	switch {
	case err == app.ErrNotFound:
		u.createDirectory(entry)
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
	default:
		// directory already known nothing to do
		return
	}
}

// createDirectory creates a new directory entry on the server.
func (u *uploader) createDirectory(entry files.TreeEntry) {
	dirPath := filepath.ToSlash(entry.Path)
	req := mcstoreapi.DirectoryRequest{
		ProjectName: u.project.Name,
		ProjectID:   u.project.ProjectID,
		Path:        dirPath,
	}
	dirID, _ := u.getDirectory(req)
	dir := &Directory{
		DirectoryID: dirID,
		Path:        dirPath,
	}
	if _, err := u.db.InsertDirectory(dir); err != nil {
		app.Log.Panicf("Local database returned err, panic!: %s", err)
	}
}

// getDirectoryWithRetry will make the server API GetDirectory call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) getDirectory(req mcstoreapi.DirectoryRequest) (string, error) {
	var (
		dirID string
		err   error
	)
	if dirID, err = u.serverAPI.GetDirectory(req); err != nil {
		app.Log.Errorf("Failed creating directory: %#v/%s", req, err)
		return "", err
	}
	return dirID, nil
}

// handleFileEntry will handle processing file entries. It will check if the file
// has already been uploaded. If it hasn't it will upload the file to the server.
func (u *uploader) handleFileEntry(entry files.TreeEntry) {
	if dir := u.getDirByPath(filepath.Dir(entry.Path)); dir == nil {
		app.Log.Exitf("Should have found dir %s", filepath.Dir(entry.Path))
	} else {
		file := u.getFileByName(entry.Finfo.Name(), dir.ID)
		switch {
		case file == nil:
			fmt.Println("Uploading new file:", entry.Path)
			u.uploadFile(entry, file, dir)
		case entry.Finfo.ModTime().Unix() > file.MTime.Unix():
			fmt.Println("Uploading changed file:", entry.Path)
			u.uploadFile(entry, file, dir)
		default:
			fmt.Println("File already uploaded:", entry.Path)
			// nothing to do
		}
	}
}

// getDirByPath looks up a path in the local database. It normalizes
// the file separator.
func (u *uploader) getDirByPath(path string) *Directory {
	dir, err := u.db.FindDirectory(path)
	switch {
	case err == app.ErrNotFound:
		return nil
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
		return nil
	default:
		// directory already known nothing to do
		return dir
	}
}

// getFileByName looks up a file by name and directory id in the local database.
func (u *uploader) getFileByName(name string, dirID int64) *File {
	f, err := u.db.FindFile(name, dirID)
	switch {
	case err == app.ErrNotFound:
		return nil
	case err != nil:
		app.Log.Panicf("Local database returned err, panic!: %s", err)
		return nil
	default:
		return f
	}
}

// uploadFile will upload the file blocks to the server. It will create an upload request
// and then start uploading the blocks. When it completes it will update the local database
// state for the file.
func (u *uploader) uploadFile(entry files.TreeEntry, file *File, dir *Directory) {
	uploadResponse, checksum := u.getUploadResponse(dir.DirectoryID, entry)
	requestID := uploadResponse.RequestID

	var (
		_ = checksum

		// TODO: do something with the starting block (its ignored for now)
		n          int
		err        error
		uploadErr  error
		mutex      sync.Mutex
		uploadResp *mcstoreapi.UploadChunkResponse
		wg         sync.WaitGroup
	)
	uploadChan := make(chan *flow.Request)
	chunkNumber := 1

	done := make(chan struct{})

	uploadFunc := func(doneChan <-chan struct{}, c <-chan *flow.Request) {
		defer wg.Done()
		for req := range c {
			select {
			case <-doneChan:
				return

			default:
				resp, err := u.sendFlowData(req)
				if err != nil {
					mutex.Lock()
					uploadErr = err
					mutex.Unlock()
				} else {
					if resp.Done {
						mutex.Lock()
						uploadResp = resp
						mutex.Unlock()
					}
				}
			}
		}
	}

	wg.Add(5)

	for i := 0; i < 5; i++ {
		go uploadFunc(done, uploadChan)
	}

	f, _ := os.Open(entry.Path)
	defer f.Close()
	buf := make([]byte, 1024*1024)
	totalChunks := numChunks(entry.Finfo.Size())
	//var uploadResp *mcstoreapi.UploadChunkResponse
	for {
		n, err = f.Read(buf)
		if n != 0 {
			// send bytes
			req := &flow.Request{
				FlowChunkNumber:  int32(chunkNumber),
				FlowTotalChunks:  totalChunks,
				FlowChunkSize:    int32(n),
				FlowTotalSize:    entry.Finfo.Size(),
				FlowIdentifier:   requestID,
				FlowFileName:     entry.Finfo.Name(),
				FlowRelativePath: "",
				ProjectID:        u.project.ProjectID,
				DirectoryID:      dir.DirectoryID,
				Chunk:            buf[:n],
			}
			uploadChan <- req
			//			uploadResp, _ = u.sendFlowData(req)
			//			if uploadResp.Done {
			//				break
			//			}
			chunkNumber++
		}
		if err != nil {
			break
		}
	}

	close(done)
	wg.Wait()

	// ******************************* ADD THIS *********************
	// Need to wait on all go routines to finish before proceeding
	// **************************************************************

	if uploadResp == nil {
		app.Log.Errorf("uploadResp not done %#v\n", uploadResp)
		return
	}

	if err != nil && err != io.EOF {
		app.Log.Errorf("Unable to complete read on file for upload: %s", entry.Path)
	} else {
		// done, add or update the entry in the database.
		if file == nil {
			// create new entry
			newFile := File{
				FileID:     uploadResp.FileID,
				Name:       entry.Finfo.Name(),
				Checksum:   checksum,
				Size:       entry.Finfo.Size(),
				MTime:      entry.Finfo.ModTime(),
				LastUpload: time.Now(),
				Directory:  dir.ID,
			}
			u.db.InsertFile(&newFile)
		} else {
			// update existing entry
			file.MTime = entry.Finfo.ModTime()
			file.LastUpload = time.Now()
			if file.Checksum != checksum {
				// Existing file uploaded but we created a new version on the server.
				// We could get here if a previous upload did not complete.
				file.Checksum = checksum
				file.FileID = uploadResp.FileID
				file.Size = entry.Finfo.Size()
			}
			u.db.UpdateFile(file)
		}
	}
}

// getUploadResponse sends an upload request to the server and gets the response.
func (u *uploader) getUploadResponse(directoryID string, entry files.TreeEntry) (*mcstoreapi.CreateUploadResponse, string) {
	checksum, _ := file.HashStr(md5.New(), entry.Path)
	chunkSize := int32(1024 * 1024)
	uploadReq := mcstoreapi.CreateUploadRequest{
		ProjectID:   u.project.ProjectID,
		DirectoryID: directoryID,
		FileName:    entry.Finfo.Name(),
		FileSize:    entry.Finfo.Size(),
		ChunkSize:   chunkSize,
		FileMTime:   entry.Finfo.ModTime().Format(time.RFC1123),
		Checksum:    checksum,
	}
	resp, _ := u.createUploadRequest(uploadReq)
	return resp, checksum
}

// createUploadRequestWithRetry will make the server API CreateUploadRequest call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) createUploadRequest(uploadReq mcstoreapi.CreateUploadRequest) (*mcstoreapi.CreateUploadResponse, error) {
	var (
		resp *mcstoreapi.CreateUploadResponse
		err  error
	)
	if resp, err = u.serverAPI.CreateUpload(uploadReq); err != nil {
		return nil, err
	}

	return resp, nil
}

// numChunks determines the number of chunks that will be sent to the server.
func numChunks(size int64) int32 {
	d := float64(size) / float64(1024*1024)
	n := int(math.Ceil(d))
	return int32(n)
}

// sendFlowDataWithRetry will make the server API SendFlowData call. If it fails
// it will retry (dependent on retry settings).
func (u *uploader) sendFlowData(req *flow.Request) (*mcstoreapi.UploadChunkResponse, error) {
	var (
		resp *mcstoreapi.UploadChunkResponse
		err  error
	)
	if resp, err = u.serverAPI.SendFlowData(req); err != nil {
		return nil, err
	}
	return resp, nil
}
<|endoftext|>


<commit_before>package hipbot

import (
	"regexp"
	"strings"
	"time"

	"github.com/mattn/go-xmpp"
)

// Msg represents a message send or received
type Msg struct {
	Remote string
	Type   string
	Text   string
}

// Bot is the main object used to interact with Hipchat
type Bot struct {
	c        *xmpp.Client
	fullName string
}

// NewBot creates a new Bot instance. Please note that Jabber ID and
// nickname must match the ones in the Hipchat acoount settings
func NewBot(userJabberID, nickname, password string) (*Bot, error) {
	b := &Bot{}
	b.fullName = nickname

	options := xmpp.Options{
		Host:          "chat.hipchat.com:5222",
		User:          userJabberID,
		Password:      password,
		NoTLS:         true,
		StartTLS:      true,
		Debug:         false,
		Session:       false,
		Status:        "",
		StatusMessage: "",
		Resource:      "bot",
	}
	var err error
	b.c, err = options.NewClient()
	return b, err
}

func (b *Bot) KeepAlive() {
	go func() {
		for {
			time.Sleep(90 * time.Second)
			b.c.Send(xmpp.Chat{})
		}
	}()
}

// JoinRoom tries to make the bot join a specific room. Room ID must
// match the one reported in the hipchat website
func (b *Bot) JoinRoom(roomJabberID string) {
	b.c.JoinMUC(roomJabberID + "/" + b.fullName)
}

// SendDirect sends a message directly to one user
func (b *Bot) SendDirect(userJabberID, message string) {
	b.c.Send(xmpp.Chat{Type: "chat", Remote: userJabberID, Text: message})
}

// SendRoom sends a message to a room
func (b *Bot) SendRoom(roomJabberID, message string) {
	b.c.Send(xmpp.Chat{Type: "groupchat", Remote: roomJabberID, Text: message})
}

// Answer sends a message back to a room if the original message was a chatgroup,
// or directly to a user otherwise
func (b *Bot) Answer(m Msg, text string) {
	remote := m.Remote
	if m.Type == "groupchat" {
		remote = strings.Split(remote, "/")[0]
	}
	b.c.Send(xmpp.Chat{Type: m.Type, Remote: remote, Text: text})
}

// Listener is the interface used to handle incoming messages
type Listener interface {
	HandleMsg(b *Bot, m Msg)
}

// Listen waits forever for incoming messages and relays them to
// the passed-in listener
func (b *Bot) Listen(l Listener) error {
	for {
		m, err := b.c.Recv()
		if err != nil {
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			l.HandleMsg(b, Msg{Remote: v.Remote, Type: v.Type, Text: v.Text})
		case xmpp.Presence:
			// do nothing
		}
	}
}

// HandleFunc is the type of functions
type HandleFunc func(b *Bot, m Msg)

// Mux works pretty similarly to gorilla-mux and similar constructs.
// You can register pairs of regex-function, so each time an incoming
// message matches a regex, the relevant function will be called
type Mux struct {
	matches map[*regexp.Regexp]HandleFunc
}

// NewMux creates a new Mux instance
func NewMux() *Mux {
	result := &Mux{}
	result.matches = make(map[*regexp.Regexp]HandleFunc)
	return result
}

// HandleMsg receives the incoming messages
func (m *Mux) HandleMsg(c *Bot, msg Msg) {
	for regex, callback := range m.matches {
		if regex.MatchString(msg.Text) {
			callback(c, msg)
		}
	}
}

// AddHandler registers a function to be called when an
// incoming message matches the regex
func (m *Mux) AddHandler(regex string, f HandleFunc) {
	r := regexp.MustCompile(regex)
	m.matches[r] = f
}
<commit_msg>Made keepalive goroutine stop if connection is lost<commit_after>package hipbot

import (
	"regexp"
	"strings"
	"time"

	xmpp "github.com/mattn/go-xmpp"
)

// Msg represents a message send or received
type Msg struct {
	Remote string
	Type   string
	Text   string
}

// Bot is the main object used to interact with Hipchat
type Bot struct {
	c             *xmpp.Client
	fullName      string
	stopKeepAlive chan struct{}
}

// NewBot creates a new Bot instance. Please note that Jabber ID and
// nickname must match the ones in the Hipchat acoount settings
func NewBot(userJabberID, nickname, password string) (*Bot, error) {
	b := &Bot{}
	b.fullName = nickname

	options := xmpp.Options{
		Host:          "chat.hipchat.com:5222",
		User:          userJabberID,
		Password:      password,
		NoTLS:         true,
		StartTLS:      true,
		Debug:         false,
		Session:       false,
		Status:        "",
		StatusMessage: "",
		Resource:      "bot",
	}
	var err error
	b.c, err = options.NewClient()
	return b, err
}

// KeepAlive launches a goroutine that keeps the connection from closing
func (b *Bot) KeepAlive() {
	go func() {
		ticker := time.NewTicker(90 * time.Second)
		b.stopKeepAlive = make(chan struct{})

		for {
			select {
			case <-ticker.C:
				b.c.Send(xmpp.Chat{})
			case <-b.stopKeepAlive:
				ticker.Stop()
				return
			}
		}
	}()
}

// JoinRoom tries to make the bot join a specific room. Room ID must
// match the one reported in the hipchat website
func (b *Bot) JoinRoom(roomJabberID string) {
	b.c.JoinMUC(roomJabberID + "/" + b.fullName)
}

// SendDirect sends a message directly to one user
func (b *Bot) SendDirect(userJabberID, message string) {
	b.c.Send(xmpp.Chat{Type: "chat", Remote: userJabberID, Text: message})
}

// SendRoom sends a message to a room
func (b *Bot) SendRoom(roomJabberID, message string) {
	b.c.Send(xmpp.Chat{Type: "groupchat", Remote: roomJabberID, Text: message})
}

// Answer sends a message back to a room if the original message was a chatgroup,
// or directly to a user otherwise
func (b *Bot) Answer(m Msg, text string) {
	remote := m.Remote
	if m.Type == "groupchat" {
		remote = strings.Split(remote, "/")[0]
	}
	b.c.Send(xmpp.Chat{Type: m.Type, Remote: remote, Text: text})
}

// Listener is the interface used to handle incoming messages
type Listener interface {
	HandleMsg(b *Bot, m Msg)
}

// Listen waits forever for incoming messages and relays them to
// the passed-in listener
func (b *Bot) Listen(l Listener) error {
	for {
		m, err := b.c.Recv()
		if err != nil {
			if b.stopKeepAlive != nil {
				b.stopKeepAlive <- struct{}{}
			}
			return err
		}

		switch v := m.(type) {
		case xmpp.Chat:
			l.HandleMsg(b, Msg{Remote: v.Remote, Type: v.Type, Text: v.Text})
		case xmpp.Presence:
			// do nothing
		}
	}
}

// HandleFunc is the type of functions
type HandleFunc func(b *Bot, m Msg)

// Mux works pretty similarly to gorilla-mux and similar constructs.
// You can register pairs of regex-function, so each time an incoming
// message matches a regex, the relevant function will be called
type Mux struct {
	matches map[*regexp.Regexp]HandleFunc
}

// NewMux creates a new Mux instance
func NewMux() *Mux {
	result := &Mux{}
	result.matches = make(map[*regexp.Regexp]HandleFunc)
	return result
}

// HandleMsg receives the incoming messages
func (m *Mux) HandleMsg(c *Bot, msg Msg) {
	for regex, callback := range m.matches {
		if regex.MatchString(msg.Text) {
			callback(c, msg)
		}
	}
}

// AddHandler registers a function to be called when an
// incoming message matches the regex
func (m *Mux) AddHandler(regex string, f HandleFunc) {
	r := regexp.MustCompile(regex)
	m.matches[r] = f
}
<|endoftext|>