<commit_before>// Provides functions to query web interfaces.
package query

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Islandstone/yr"
)

const (
	geoURI    = "http://api.geonames.org/search?username=%s&q=%s&maxRows=1&type=json&orderby=relevance"
	yrURI     = "http://www.yr.no/place/%s/%s/%s/forecast.xml"
	yrNorURI  = "http://www.yr.no/place/%s/%s/%s/%s/forecast.xml"
	geoErrMsg = "Unable to find %s"
)

var placesLookup = map[string][]string{
	"oslo":     {"Norway", "Oslo", "Oslo", "Oslo"},
	"sandvika": {"Norway", "Akershus", "Bærum", "Sandvika"},
}

// GoogleData is used to parse the response from Google.
type GoogleData struct {
	ResponseData   *ResponseData
	ResponseStatus float64
}

// ResponseData is a substruct of GoogleData.
type ResponseData struct {
	Results []*Result
	Cursor  *Cursor
}

// Result is a substruct of GoogleData.
type Result struct {
	GsearchResultClass string
	UnescapedUrl       string
	Url                string
	VisibleUrl         string
	CacheUrl           string
	Title              string
	TitleNoFormatting  string
	Content            string
}

// Cursor is a substruct of GoogleData.
type Cursor struct {
	ResultCount    string
	MoreResultsUrl string
}

// WolframData is used to parse the response from WolframAlpha.
type WolframData struct {
	XMLName     xml.Name `xml:"queryresult"`
	Success     bool     `xml:"success,attr"`
	ParseTiming float64  `xml:"parsetiming,attr"`
	Numpods     int      `xml:"numpods,attr"`
	Pods        []*Pod   `xml:"pod"`
	DidYouMeans []string `xml:"didyoumeans>didyoumean"`
}

// Pod is a substruct of WolframData.
type Pod struct {
	Title      string   `xml:"title,attr"`
	Id         string   `xml:"id,attr"`
	Primary    bool     `xml:"primary,attr"`
	Numsubpods int      `xml:"numsubpods,attr"`
	PlainTexts []string `xml:"subpod>plaintext"`
}

// Config is the configuration for this thing.
type Config struct {
	WolframId    string `toml:"wolfram_id"`
	GeonamesId   string `toml:"geonames_id"`
	GoogleApiKey string `toml:"google_api_key"`
}

// NewConfig loads the config file.
func NewConfig(file string) *Config {
	var conf Config
	_, err := toml.DecodeFile(file, &conf)
	if err != nil {
		return nil
	}
	return &conf
}

var (
	rgxTitle = regexp.MustCompile(`<meta name="title" content="(.*?)"`)
	rgxUrl   = regexp.MustCompile(
		`(http|https):\/\/(www\.)?(youtube.com|youtu.be)\/\S+`)
	rgxDur = regexp.MustCompile(
		`<meta itemprop="duration" content="PT([0-9]+M[0-9]+S)"`)
	rgxTags = regexp.MustCompile(`<[^>]*>`)
)

const (
	googleURI  = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s"
	shortenURI = "https://www.googleapis.com/urlshortener/v1/url?fields=id,longUrl&key=%s"
	wolframURI = "http://api.wolframalpha.com/v2/query?format=plaintext&input=%s&appid=%s"
)

// Google performs a query and returns a formatted result.
func Google(query string, conf *Config) (output string, err error) {
	var resp *http.Response
	resp, err = http.Get(fmt.Sprintf(googleURI, url.QueryEscape(query)))
	if err != nil {
		return
	}

	defer resp.Body.Close()
	var jsonObj GoogleData
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&jsonObj)
	if err != nil {
		return "", err
	}

	if jsonObj.ResponseData == nil || int(jsonObj.ResponseStatus) != 200 || len(jsonObj.ResponseData.Results) == 0 {
		return fmt.Sprintf("\x02Google: No results found."), err
	}

	result := jsonObj.ResponseData.Results[0]
	url := rgxTags.ReplaceAllString(result.UnescapedUrl, "")
	var short string

	if len(conf.GoogleApiKey) > 0 {
		short, _ = GetShortUrl(jsonObj.ResponseData.Cursor.MoreResultsUrl, conf)
		if len(short) > 0 {
			short = fmt.Sprintf(" [%s]", short)
		}
	}

	output = fmt.Sprintf(
		"\x02Google (\x02%s results\x02)%s:\x02 %s - %s",
		short,
		jsonObj.ResponseData.Cursor.ResultCount,
		url,
		html.UnescapeString(rgxTags.ReplaceAllString(result.Content, "")),
	)

	return output, nil
}

type URLShortenResponse struct {
	ID      string `json:"id"`
	LongURL string `json:"longUrl"`
}

type URLShortenQuery struct {
	LongURL string `json:"longUrl"`
}

// GetShortUrl takes a long url and returns a shorter url from the Google API
func GetShortUrl(longURL string, conf *Config) (short string, err error) {
	var resp *http.Response
	var body []byte

	urlQuery := URLShortenQuery{LongURL: longURL}
	if body, err = json.Marshal(urlQuery); err != nil {
		return "", fmt.Errorf("failed to marshal url query: %v", err)
	}

	resp, err = http.Post(
		fmt.Sprintf(shortenURI, conf.GoogleApiKey),
		"application/json",
		bytes.NewReader(body),
	)

	if err != nil {
		return
	}
	defer resp.Body.Close()

	var jsonObj URLShortenResponse
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&jsonObj)
	if err != nil {
		return "", err
	}

	return jsonObj.ID, nil
}

// Wolfram performs a query and returns a formatted result.
func Wolfram(query string, conf *Config) (output string, err error) {
	var resp *http.Response
	requestUri := fmt.Sprintf(wolframURI, url.QueryEscape(query), conf.WolframId)
	resp, err = http.Get(requestUri)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	var xmlObj WolframData
	decoder := xml.NewDecoder(resp.Body)
	err = decoder.Decode(&xmlObj)
	if err != nil {
		return
	}

	// Handle cases of no results.
	if !xmlObj.Success {
		if len(xmlObj.DidYouMeans) > 0 {
			output = fmt.Sprintf("\x02Wolfram (\x02%.2fms\x02):\x02 Did you mean: %s",
				xmlObj.ParseTiming,
				xmlObj.DidYouMeans[0],
			)
		} else {
			output = fmt.Sprintf("\x02Wolfram (\x02%.2fms\x02):\x02 No results found.",
				xmlObj.ParseTiming)
		}
		return
	}

	// If there was no primary response fallback to link.
	if len(xmlObj.Pods) < 2 || len(xmlObj.Pods[1].PlainTexts[0]) == 0 {
		output = fmt.Sprintf(
			"\x02Wolfram (\x02%.2fms\x02):\x02 %s \x02=>\x02 http://www.wolframalpha.com/input/?i=%s",
			xmlObj.ParseTiming,
			xmlObj.Pods[0].PlainTexts[0],
			url.QueryEscape(query),
		)
		return
	}

	output = fmt.Sprintf(
		"\x02Wolfram (\x02%.2fms\x02):\x02 %s \x02=>\x02 %s",
		xmlObj.ParseTiming,
		xmlObj.Pods[0].PlainTexts[0],
		xmlObj.Pods[1].PlainTexts[0],
	)

	return
}

// YouTube will check to see if a message contains a YouTube uri, if so it will
// format a string with the title in it.
func YouTube(msg string) (output string, err error) {
	link := rgxUrl.FindString(msg)
	if len(link) == 0 {
		return
	}

	var resp *http.Response
	var body []byte

	resp, err = http.Get(link)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	var buf = bytes.NewBufferString("\x02YouTube")
	if duration := rgxDur.FindSubmatch(body); duration != nil {
		dur, e := time.ParseDuration(string(bytes.ToLower(duration[1])))
		if e == nil {
			buf.WriteString(" (\x02")
			buf.WriteString(dur.String())
			buf.WriteString("\x02)")
		}
	}

	if title := rgxTitle.FindSubmatch(body); title != nil {
		buf.WriteString(":\x02 ")
		buf.WriteString(html.UnescapeString(string(title[1])))
		output = buf.String()
	}

	return
}

type geonameplace struct {
	CountryName string
	AdminName1  string
	Name        string
}

type geonamesdata struct {
	Geonames []geonameplace
}

type geoErr struct {
	query string
}

func (g geoErr) Error() string {
	return fmt.Sprintf(geoErrMsg, g.query)
}

func getLocation(query string, conf *Config) (country, state, city string, err error) {
	resp, err := http.Get(fmt.Sprintf(geoURI, conf.GeonamesId, url.QueryEscape(query)))

	if err != nil {
		return
	}

	defer resp.Body.Close()

	r := json.NewDecoder(resp.Body)

	var data geonamesdata
	err = r.Decode(&data)

	if err != nil {
		return
	}

	if len(data.Geonames) == 0 {
		err = geoErr{query}
		return
	}

	country = data.Geonames[0].CountryName
	state = data.Geonames[0].AdminName1
	city = data.Geonames[0].Name

	return
}

func Weather(query string, conf *Config) (output string, err error) {
	var data *yr.WeatherData
	var URL, city, country, state string

	if subURI, ok := placesLookup[strings.ToLower(query)]; ok {
		country = subURI[0]
		state = subURI[1]
		county := subURI[2]
		city = subURI[3]

		URL = fmt.Sprintf(yrNorURI, country, state, county, city)
	} else {
		country, state, city, err = getLocation(query, conf)

		if err != nil {
			if e, ok := err.(geoErr); ok {
				return fmt.Sprintf("\x02Weather (\x02YR.no\x02):\x02 %v", e), nil
			}
			return "", err
		}

		URL = fmt.Sprintf(yrURI, country, state, city)
	}

	data, err = yr.LoadFromURL(URL)

	if err != nil {
		return
	}

	output = fmt.Sprintf(
		"\x02Weather (\x02YR.no\x02):\x02 %s, %s \x02=>\x02 %s, %d \u00B0C",
		city,
		country,
		data.Current().Symbol.Name,
		data.Current().Temperature.Value,
	)

	return
}
<commit_msg>Fixed printf arguments<commit_after>// Provides functions to query web interfaces.
package query

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html"
	"io/ioutil"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Islandstone/yr"
)

const (
	geoURI    = "http://api.geonames.org/search?username=%s&q=%s&maxRows=1&type=json&orderby=relevance"
	yrURI     = "http://www.yr.no/place/%s/%s/%s/forecast.xml"
	yrNorURI  = "http://www.yr.no/place/%s/%s/%s/%s/forecast.xml"
	geoErrMsg = "Unable to find %s"
)

var placesLookup = map[string][]string{
	"oslo":     {"Norway", "Oslo", "Oslo", "Oslo"},
	"sandvika": {"Norway", "Akershus", "Bærum", "Sandvika"},
}

// GoogleData is used to parse the response from Google.
type GoogleData struct {
	ResponseData   *ResponseData
	ResponseStatus float64
}

// ResponseData is a substruct of GoogleData.
type ResponseData struct {
	Results []*Result
	Cursor  *Cursor
}

// Result is a substruct of GoogleData.
type Result struct {
	GsearchResultClass string
	UnescapedUrl       string
	Url                string
	VisibleUrl         string
	CacheUrl           string
	Title              string
	TitleNoFormatting  string
	Content            string
}

// Cursor is a substruct of GoogleData.
type Cursor struct {
	ResultCount    string
	MoreResultsUrl string
}

// WolframData is used to parse the response from WolframAlpha.
type WolframData struct {
	XMLName     xml.Name `xml:"queryresult"`
	Success     bool     `xml:"success,attr"`
	ParseTiming float64  `xml:"parsetiming,attr"`
	Numpods     int      `xml:"numpods,attr"`
	Pods        []*Pod   `xml:"pod"`
	DidYouMeans []string `xml:"didyoumeans>didyoumean"`
}

// Pod is a substruct of WolframData.
type Pod struct {
	Title      string   `xml:"title,attr"`
	Id         string   `xml:"id,attr"`
	Primary    bool     `xml:"primary,attr"`
	Numsubpods int      `xml:"numsubpods,attr"`
	PlainTexts []string `xml:"subpod>plaintext"`
}

// Config is the configuration for this thing.
type Config struct {
	WolframId    string `toml:"wolfram_id"`
	GeonamesId   string `toml:"geonames_id"`
	GoogleApiKey string `toml:"google_api_key"`
}

// NewConfig loads the config file.
func NewConfig(file string) *Config {
	var conf Config
	_, err := toml.DecodeFile(file, &conf)
	if err != nil {
		return nil
	}
	return &conf
}

var (
	rgxTitle = regexp.MustCompile(`<meta name="title" content="(.*?)"`)
	rgxUrl   = regexp.MustCompile(
		`(http|https):\/\/(www\.)?(youtube.com|youtu.be)\/\S+`)
	rgxDur = regexp.MustCompile(
		`<meta itemprop="duration" content="PT([0-9]+M[0-9]+S)"`)
	rgxTags = regexp.MustCompile(`<[^>]*>`)
)

const (
	googleURI  = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s"
	shortenURI = "https://www.googleapis.com/urlshortener/v1/url?fields=id,longUrl&key=%s"
	wolframURI = "http://api.wolframalpha.com/v2/query?format=plaintext&input=%s&appid=%s"
)

// Google performs a query and returns a formatted result.
func Google(query string, conf *Config) (output string, err error) {
	var resp *http.Response
	resp, err = http.Get(fmt.Sprintf(googleURI, url.QueryEscape(query)))
	if err != nil {
		return
	}

	defer resp.Body.Close()
	var jsonObj GoogleData
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&jsonObj)
	if err != nil {
		return "", err
	}

	if jsonObj.ResponseData == nil || int(jsonObj.ResponseStatus) != 200 || len(jsonObj.ResponseData.Results) == 0 {
		return fmt.Sprintf("\x02Google: No results found."), err
	}

	result := jsonObj.ResponseData.Results[0]
	url := rgxTags.ReplaceAllString(result.UnescapedUrl, "")
	var short string

	if len(conf.GoogleApiKey) > 0 {
		short, _ = GetShortUrl(jsonObj.ResponseData.Cursor.MoreResultsUrl, conf)
		if len(short) > 0 {
			short = fmt.Sprintf(" [%s]", short)
		}
	}

	output = fmt.Sprintf(
		"\x02Google (\x02%s results\x02)%s:\x02 %s - %s",
		jsonObj.ResponseData.Cursor.ResultCount,
		short,
		url,
		html.UnescapeString(rgxTags.ReplaceAllString(result.Content, "")),
	)

	return output, nil
}

type URLShortenResponse struct {
	ID      string `json:"id"`
	LongURL string `json:"longUrl"`
}

type URLShortenQuery struct {
	LongURL string `json:"longUrl"`
}

// GetShortUrl takes a long url and returns a shorter url from the Google API
func GetShortUrl(longURL string, conf *Config) (short string, err error) {
	var resp *http.Response
	var body []byte

	urlQuery := URLShortenQuery{LongURL: longURL}
	if body, err = json.Marshal(urlQuery); err != nil {
		return "", fmt.Errorf("failed to marshal url query: %v", err)
	}

	resp, err = http.Post(
		fmt.Sprintf(shortenURI, conf.GoogleApiKey),
		"application/json",
		bytes.NewReader(body),
	)

	if err != nil {
		return
	}
	defer resp.Body.Close()

	var jsonObj URLShortenResponse
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&jsonObj)
	if err != nil {
		return "", err
	}

	return jsonObj.ID, nil
}

// Wolfram performs a query and returns a formatted result.
func Wolfram(query string, conf *Config) (output string, err error) {
	var resp *http.Response
	requestUri := fmt.Sprintf(wolframURI, url.QueryEscape(query), conf.WolframId)
	resp, err = http.Get(requestUri)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	var xmlObj WolframData
	decoder := xml.NewDecoder(resp.Body)
	err = decoder.Decode(&xmlObj)
	if err != nil {
		return
	}

	// Handle cases of no results.
	if !xmlObj.Success {
		if len(xmlObj.DidYouMeans) > 0 {
			output = fmt.Sprintf("\x02Wolfram (\x02%.2fms\x02):\x02 Did you mean: %s",
				xmlObj.ParseTiming,
				xmlObj.DidYouMeans[0],
			)
		} else {
			output = fmt.Sprintf("\x02Wolfram (\x02%.2fms\x02):\x02 No results found.",
				xmlObj.ParseTiming)
		}
		return
	}

	// If there was no primary response fallback to link.
	if len(xmlObj.Pods) < 2 || len(xmlObj.Pods[1].PlainTexts[0]) == 0 {
		output = fmt.Sprintf(
			"\x02Wolfram (\x02%.2fms\x02):\x02 %s \x02=>\x02 http://www.wolframalpha.com/input/?i=%s",
			xmlObj.ParseTiming,
			xmlObj.Pods[0].PlainTexts[0],
			url.QueryEscape(query),
		)
		return
	}

	output = fmt.Sprintf(
		"\x02Wolfram (\x02%.2fms\x02):\x02 %s \x02=>\x02 %s",
		xmlObj.ParseTiming,
		xmlObj.Pods[0].PlainTexts[0],
		xmlObj.Pods[1].PlainTexts[0],
	)

	return
}

// YouTube will check to see if a message contains a YouTube uri, if so it will
// format a string with the title in it.
func YouTube(msg string) (output string, err error) {
	link := rgxUrl.FindString(msg)
	if len(link) == 0 {
		return
	}

	var resp *http.Response
	var body []byte

	resp, err = http.Get(link)
	if err != nil {
		return
	}

	defer resp.Body.Close()
	body, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	var buf = bytes.NewBufferString("\x02YouTube")
	if duration := rgxDur.FindSubmatch(body); duration != nil {
		dur, e := time.ParseDuration(string(bytes.ToLower(duration[1])))
		if e == nil {
			buf.WriteString(" (\x02")
			buf.WriteString(dur.String())
			buf.WriteString("\x02)")
		}
	}

	if title := rgxTitle.FindSubmatch(body); title != nil {
		buf.WriteString(":\x02 ")
		buf.WriteString(html.UnescapeString(string(title[1])))
		output = buf.String()
	}

	return
}

type geonameplace struct {
	CountryName string
	AdminName1  string
	Name        string
}

type geonamesdata struct {
	Geonames []geonameplace
}

type geoErr struct {
	query string
}

func (g geoErr) Error() string {
	return fmt.Sprintf(geoErrMsg, g.query)
}

func getLocation(query string, conf *Config) (country, state, city string, err error) {
	resp, err := http.Get(fmt.Sprintf(geoURI, conf.GeonamesId, url.QueryEscape(query)))

	if err != nil {
		return
	}

	defer resp.Body.Close()

	r := json.NewDecoder(resp.Body)

	var data geonamesdata
	err = r.Decode(&data)

	if err != nil {
		return
	}

	if len(data.Geonames) == 0 {
		err = geoErr{query}
		return
	}

	country = data.Geonames[0].CountryName
	state = data.Geonames[0].AdminName1
	city = data.Geonames[0].Name

	return
}

func Weather(query string, conf *Config) (output string, err error) {
	var data *yr.WeatherData
	var URL, city, country, state string

	if subURI, ok := placesLookup[strings.ToLower(query)]; ok {
		country = subURI[0]
		state = subURI[1]
		county := subURI[2]
		city = subURI[3]

		URL = fmt.Sprintf(yrNorURI, country, state, county, city)
	} else {
		country, state, city, err = getLocation(query, conf)

		if err != nil {
			if e, ok := err.(geoErr); ok {
				return fmt.Sprintf("\x02Weather (\x02YR.no\x02):\x02 %v", e), nil
			}
			return "", err
		}

		URL = fmt.Sprintf(yrURI, country, state, city)
	}

	data, err = yr.LoadFromURL(URL)

	if err != nil {
		return
	}

	output = fmt.Sprintf(
		"\x02Weather (\x02YR.no\x02):\x02 %s, %s \x02=>\x02 %s, %d \u00B0C",
		city,
		country,
		data.Current().Symbol.Name,
		data.Current().Temperature.Value,
	)

	return
}
<|endoftext|>


<commit_before>package dht

import (
	"sync"

	inet "github.com/jbenet/go-ipfs/net"
	peer "github.com/jbenet/go-ipfs/peer"
	queue "github.com/jbenet/go-ipfs/peer/queue"
	"github.com/jbenet/go-ipfs/routing"
	kb "github.com/jbenet/go-ipfs/routing/kbucket"
	u "github.com/jbenet/go-ipfs/util"
	todoctr "github.com/jbenet/go-ipfs/util/todocounter"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
)

var maxQueryConcurrency = AlphaValue

type dhtQuery struct {
	// the key we're querying for
	key u.Key

	// dialer used to ensure we're connected to peers
	dialer inet.Dialer

	// the function to execute per peer
	qfunc queryFunc

	// the concurrency parameter
	concurrency int
}

type dhtQueryResult struct {
	value         []byte          // GetValue
	peer          peer.PeerInfo   // FindPeer
	providerPeers []peer.PeerInfo // GetProviders
	closerPeers   []peer.PeerInfo // *
	success       bool
}

// constructs query
func newQuery(k u.Key, d inet.Dialer, f queryFunc) *dhtQuery {
	return &dhtQuery{
		key:         k,
		dialer:      d,
		qfunc:       f,
		concurrency: maxQueryConcurrency,
	}
}

// QueryFunc is a function that runs a particular query with a given peer.
// It returns either:
// - the value
// - a list of peers potentially better able to serve the query
// - an error
type queryFunc func(context.Context, peer.ID) (*dhtQueryResult, error)

// Run runs the query at hand. pass in a list of peers to use first.
func (q *dhtQuery) Run(ctx context.Context, peers []peer.ID) (*dhtQueryResult, error) {
	runner := newQueryRunner(ctx, q)
	return runner.Run(peers)
}

type dhtQueryRunner struct {

	// the query to run
	query *dhtQuery

	// peersToQuery is a list of peers remaining to query
	peersToQuery *queue.ChanQueue

	// peersSeen are all the peers queried. used to prevent querying same peer 2x
	peersSeen peer.Set

	// rateLimit is a channel used to rate limit our processing (semaphore)
	rateLimit chan struct{}

	// peersRemaining is a counter of peers remaining (toQuery + processing)
	peersRemaining todoctr.Counter

	// context group
	cg ctxgroup.ContextGroup

	// result
	result *dhtQueryResult

	// result errors
	errs []error

	// lock for concurrent access to fields
	sync.RWMutex
}

func newQueryRunner(ctx context.Context, q *dhtQuery) *dhtQueryRunner {
	return &dhtQueryRunner{
		query:          q,
		peersToQuery:   queue.NewChanQueue(ctx, queue.NewXORDistancePQ(q.key)),
		peersRemaining: todoctr.NewSyncCounter(),
		peersSeen:      peer.Set{},
		rateLimit:      make(chan struct{}, q.concurrency),
		cg:             ctxgroup.WithContext(ctx),
	}
}

func (r *dhtQueryRunner) Run(peers []peer.ID) (*dhtQueryResult, error) {
	log.Debugf("Run query with %d peers.", len(peers))
	if len(peers) == 0 {
		log.Warning("Running query with no peers!")
		return nil, nil
	}

	// setup concurrency rate limiting
	for i := 0; i < r.query.concurrency; i++ {
		r.rateLimit <- struct{}{}
	}

	// add all the peers we got first.
	for _, p := range peers {
		r.addPeerToQuery(r.cg.Context(), p, "") // don't have access to self here...
	}

	// go do this thing.
	// do it as a child func to make sure Run exits
	// ONLY AFTER spawn workers has exited.
	r.cg.AddChildFunc(r.spawnWorkers)

	// so workers are working.

	// wait until they're done.
	err := routing.ErrNotFound

	select {
	case <-r.peersRemaining.Done():
		r.cg.Close()
		r.RLock()
		defer r.RUnlock()

		if len(r.errs) > 0 {
			err = r.errs[0]
		}

	case <-r.cg.Closed():
		r.RLock()
		defer r.RUnlock()
		err = r.cg.Context().Err() // collect the error.
	}

	if r.result != nil && r.result.success {
		return r.result, nil
	}

	return nil, err
}

func (r *dhtQueryRunner) addPeerToQuery(ctx context.Context, next peer.ID, benchmark peer.ID) {
	// if new peer is ourselves...
	if next == r.query.dialer.LocalPeer() {
		return
	}

	// if new peer further away than whom we got it from, don't bother (loops)
	// TODO----------- this benchmark should be replaced by a heap:
	// we should be doing the s/kademlia "continue to search"
	// (i.e. put all of them in a heap sorted by dht distance and then just
	// pull from the the top until  a) you exhaust all peers you get,
	// b) you succeed, c) your context expires.
	if benchmark != "" && kb.Closer(benchmark, next, r.query.key) {
		return
	}

	// if already seen, no need.
	r.Lock()
	_, found := r.peersSeen[next]
	if found {
		r.Unlock()
		return
	}
	r.peersSeen[next] = struct{}{}
	r.Unlock()

	log.Debugf("adding peer to query: %v", next)

	// do this after unlocking to prevent possible deadlocks.
	r.peersRemaining.Increment(1)
	select {
	case r.peersToQuery.EnqChan <- next:
	case <-ctx.Done():
	}
}

func (r *dhtQueryRunner) spawnWorkers(parent ctxgroup.ContextGroup) {
	for {

		select {
		case <-r.peersRemaining.Done():
			return

		case <-r.cg.Closing():
			return

		case p, more := <-r.peersToQuery.DeqChan:
			if !more {
				return // channel closed.
			}
			log.Debugf("spawning worker for: %v", p)

			// do it as a child func to make sure Run exits
			// ONLY AFTER spawn workers has exited.
			parent.AddChildFunc(func(cg ctxgroup.ContextGroup) {
				r.queryPeer(cg, p)
			})
		}
	}
}

func (r *dhtQueryRunner) queryPeer(cg ctxgroup.ContextGroup, p peer.ID) {
	log.Debugf("spawned worker for: %v", p)

	// make sure we rate limit concurrency.
	select {
	case <-r.rateLimit:
	case <-cg.Closing():
		r.peersRemaining.Decrement(1)
		return
	}

	// ok let's do this!
	log.Debugf("running worker for: %v", p)

	// make sure we do this when we exit
	defer func() {
		// signal we're done proccessing peer p
		log.Debugf("completing worker for: %v", p)
		r.peersRemaining.Decrement(1)
		r.rateLimit <- struct{}{}
	}()

	// make sure we're connected to the peer.
	err := r.query.dialer.DialPeer(cg.Context(), p)
	if err != nil {
		log.Debugf("ERROR worker for: %v -- err connecting: %v", p, err)
		r.Lock()
		r.errs = append(r.errs, err)
		r.Unlock()
		return
	}

	// finally, run the query against this peer
	res, err := r.query.qfunc(cg.Context(), p)

	if err != nil {
		log.Debugf("ERROR worker for: %v %v", p, err)
		r.Lock()
		r.errs = append(r.errs, err)
		r.Unlock()

	} else if res.success {
		log.Debugf("SUCCESS worker for: %v", p, res)
		r.Lock()
		r.result = res
		r.Unlock()
		go r.cg.Close() // signal to everyone that we're done.
		// must be async, as we're one of the children, and Close blocks.

	} else if len(res.closerPeers) > 0 {
		log.Debugf("PEERS CLOSER -- worker for: %v (%d closer peers)", p, len(res.closerPeers))
		for _, next := range res.closerPeers {
			// add their addresses to the dialer's peerstore
			conns := r.query.dialer.ConnsToPeer(next.ID)
			if len(conns) == 0 {
				log.Infof("PEERS CLOSER -- worker for %v FOUND NEW PEER: %s %s", p, next.ID, next.Addrs)
			}

			r.query.dialer.Peerstore().AddAddresses(next.ID, next.Addrs)
			r.addPeerToQuery(cg.Context(), next.ID, p)
			log.Debugf("PEERS CLOSER -- worker for: %v added %v (%v)", p, next.ID, next.Addrs)
		}
	} else {
		log.Debugf("QUERY worker for: %v - not found, and no closer peers.", p)
	}
}
<commit_msg>dht/query: log when dialing a closerpeer<commit_after>package dht

import (
	"sync"

	inet "github.com/jbenet/go-ipfs/net"
	peer "github.com/jbenet/go-ipfs/peer"
	queue "github.com/jbenet/go-ipfs/peer/queue"
	"github.com/jbenet/go-ipfs/routing"
	kb "github.com/jbenet/go-ipfs/routing/kbucket"
	u "github.com/jbenet/go-ipfs/util"
	todoctr "github.com/jbenet/go-ipfs/util/todocounter"

	context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
	ctxgroup "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-ctxgroup"
)

var maxQueryConcurrency = AlphaValue

type dhtQuery struct {
	// the key we're querying for
	key u.Key

	// dialer used to ensure we're connected to peers
	dialer inet.Dialer

	// the function to execute per peer
	qfunc queryFunc

	// the concurrency parameter
	concurrency int
}

type dhtQueryResult struct {
	value         []byte          // GetValue
	peer          peer.PeerInfo   // FindPeer
	providerPeers []peer.PeerInfo // GetProviders
	closerPeers   []peer.PeerInfo // *
	success       bool
}

// constructs query
func newQuery(k u.Key, d inet.Dialer, f queryFunc) *dhtQuery {
	return &dhtQuery{
		key:         k,
		dialer:      d,
		qfunc:       f,
		concurrency: maxQueryConcurrency,
	}
}

// QueryFunc is a function that runs a particular query with a given peer.
// It returns either:
// - the value
// - a list of peers potentially better able to serve the query
// - an error
type queryFunc func(context.Context, peer.ID) (*dhtQueryResult, error)

// Run runs the query at hand. pass in a list of peers to use first.
func (q *dhtQuery) Run(ctx context.Context, peers []peer.ID) (*dhtQueryResult, error) {
	runner := newQueryRunner(ctx, q)
	return runner.Run(peers)
}

type dhtQueryRunner struct {

	// the query to run
	query *dhtQuery

	// peersToQuery is a list of peers remaining to query
	peersToQuery *queue.ChanQueue

	// peersSeen are all the peers queried. used to prevent querying same peer 2x
	peersSeen peer.Set

	// rateLimit is a channel used to rate limit our processing (semaphore)
	rateLimit chan struct{}

	// peersRemaining is a counter of peers remaining (toQuery + processing)
	peersRemaining todoctr.Counter

	// context group
	cg ctxgroup.ContextGroup

	// result
	result *dhtQueryResult

	// result errors
	errs []error

	// lock for concurrent access to fields
	sync.RWMutex
}

func newQueryRunner(ctx context.Context, q *dhtQuery) *dhtQueryRunner {
	return &dhtQueryRunner{
		query:          q,
		peersToQuery:   queue.NewChanQueue(ctx, queue.NewXORDistancePQ(q.key)),
		peersRemaining: todoctr.NewSyncCounter(),
		peersSeen:      peer.Set{},
		rateLimit:      make(chan struct{}, q.concurrency),
		cg:             ctxgroup.WithContext(ctx),
	}
}

func (r *dhtQueryRunner) Run(peers []peer.ID) (*dhtQueryResult, error) {
	log.Debugf("Run query with %d peers.", len(peers))
	if len(peers) == 0 {
		log.Warning("Running query with no peers!")
		return nil, nil
	}

	// setup concurrency rate limiting
	for i := 0; i < r.query.concurrency; i++ {
		r.rateLimit <- struct{}{}
	}

	// add all the peers we got first.
	for _, p := range peers {
		r.addPeerToQuery(r.cg.Context(), p, "") // don't have access to self here...
	}

	// go do this thing.
	// do it as a child func to make sure Run exits
	// ONLY AFTER spawn workers has exited.
	r.cg.AddChildFunc(r.spawnWorkers)

	// so workers are working.

	// wait until they're done.
	err := routing.ErrNotFound

	select {
	case <-r.peersRemaining.Done():
		r.cg.Close()
		r.RLock()
		defer r.RUnlock()

		if len(r.errs) > 0 {
			err = r.errs[0]
		}

	case <-r.cg.Closed():
		r.RLock()
		defer r.RUnlock()
		err = r.cg.Context().Err() // collect the error.
	}

	if r.result != nil && r.result.success {
		return r.result, nil
	}

	return nil, err
}

func (r *dhtQueryRunner) addPeerToQuery(ctx context.Context, next peer.ID, benchmark peer.ID) {
	// if new peer is ourselves...
	if next == r.query.dialer.LocalPeer() {
		return
	}

	// if new peer further away than whom we got it from, don't bother (loops)
	// TODO----------- this benchmark should be replaced by a heap:
	// we should be doing the s/kademlia "continue to search"
	// (i.e. put all of them in a heap sorted by dht distance and then just
	// pull from the the top until  a) you exhaust all peers you get,
	// b) you succeed, c) your context expires.
	if benchmark != "" && kb.Closer(benchmark, next, r.query.key) {
		return
	}

	// if already seen, no need.
	r.Lock()
	_, found := r.peersSeen[next]
	if found {
		r.Unlock()
		return
	}
	r.peersSeen[next] = struct{}{}
	r.Unlock()

	log.Debugf("adding peer to query: %v", next)

	// do this after unlocking to prevent possible deadlocks.
	r.peersRemaining.Increment(1)
	select {
	case r.peersToQuery.EnqChan <- next:
	case <-ctx.Done():
	}
}

func (r *dhtQueryRunner) spawnWorkers(parent ctxgroup.ContextGroup) {
	for {

		select {
		case <-r.peersRemaining.Done():
			return

		case <-r.cg.Closing():
			return

		case p, more := <-r.peersToQuery.DeqChan:
			if !more {
				return // channel closed.
			}
			log.Debugf("spawning worker for: %v", p)

			// do it as a child func to make sure Run exits
			// ONLY AFTER spawn workers has exited.
			parent.AddChildFunc(func(cg ctxgroup.ContextGroup) {
				r.queryPeer(cg, p)
			})
		}
	}
}

func (r *dhtQueryRunner) queryPeer(cg ctxgroup.ContextGroup, p peer.ID) {
	log.Debugf("spawned worker for: %v", p)

	// make sure we rate limit concurrency.
	select {
	case <-r.rateLimit:
	case <-cg.Closing():
		r.peersRemaining.Decrement(1)
		return
	}

	// ok let's do this!
	log.Debugf("running worker for: %v", p)

	// make sure we do this when we exit
	defer func() {
		// signal we're done proccessing peer p
		log.Debugf("completing worker for: %v", p)
		r.peersRemaining.Decrement(1)
		r.rateLimit <- struct{}{}
	}()

	// make sure we're connected to the peer.
	if conns := r.query.dialer.ConnsToPeer(p); len(conns) == 0 {
		log.Infof("worker for: %v -- not connected. dial start", p)

		if err := r.query.dialer.DialPeer(cg.Context(), p); err != nil {
			log.Debugf("ERROR worker for: %v -- err connecting: %v", p, err)
			r.Lock()
			r.errs = append(r.errs, err)
			r.Unlock()
			return
		}

		log.Infof("worker for: %v -- not connected. dial success!", p)
	}

	// finally, run the query against this peer
	res, err := r.query.qfunc(cg.Context(), p)

	if err != nil {
		log.Debugf("ERROR worker for: %v %v", p, err)
		r.Lock()
		r.errs = append(r.errs, err)
		r.Unlock()

	} else if res.success {
		log.Debugf("SUCCESS worker for: %v", p, res)
		r.Lock()
		r.result = res
		r.Unlock()
		go r.cg.Close() // signal to everyone that we're done.
		// must be async, as we're one of the children, and Close blocks.

	} else if len(res.closerPeers) > 0 {
		log.Debugf("PEERS CLOSER -- worker for: %v (%d closer peers)", p, len(res.closerPeers))
		for _, next := range res.closerPeers {
			// add their addresses to the dialer's peerstore
			conns := r.query.dialer.ConnsToPeer(next.ID)
			if len(conns) == 0 {
				log.Infof("PEERS CLOSER -- worker for %v FOUND NEW PEER: %s %s", p, next.ID, next.Addrs)
			}

			r.query.dialer.Peerstore().AddAddresses(next.ID, next.Addrs)
			r.addPeerToQuery(cg.Context(), next.ID, p)
			log.Debugf("PEERS CLOSER -- worker for: %v added %v (%v)", p, next.ID, next.Addrs)
		}
	} else {
		log.Debugf("QUERY worker for: %v - not found, and no closer peers.", p)
	}
}
<|endoftext|>


<commit_before>package ares

import (
	"fmt"
	"log"

	"github.com/nlopes/slack"
)

type Ares struct {
	SlackAppToken string
	SlackBotToken string
	SlackAppID    string
	ImgurClientID string
	BotUserID     string
	// Bot won't be added or re-added to these channels
	ManagedChannels []string
}

func (a *Ares) initBot() {
	api := slack.New(a.SlackAppToken)
	a.getBotUserID()

	channels, err := api.GetChannels(true)

	if err != nil {
		log.Fatal("Failed to get public channels list: ", err.Error())
	}

	for _, channel := range channels {
		a.addBotChannel(channel.ID, a.BotUserID)
	}

}

func (a *Ares) getBotUserID() {
	api := slack.New(a.SlackAppToken)
	users, err := api.GetUsers()

	if err != nil {
		log.Fatal("Failed to fetch slack users info", err.Error())
	}

	for _, user := range users {

		if user.Profile.ApiAppID == a.SlackAppID {
			a.BotUserID = user.ID
			return
		}
	}
	log.Fatal("Unable to find bot user on the Slack")
}

func (a *Ares) deleteFile(fileId string) {
	api := slack.New(a.SlackAppToken)

	if err := api.DeleteFile(fileId); err != nil {
		fmt.Println("Deletion of the file failed:", err.Error())
	}
}

func (a *Ares) addBotChannel(channelID, user string) {
	api := slack.New(a.SlackAppToken)
	if channel, err := api.InviteUserToChannel(channelID, user); err != nil {
		log.Println(fmt.Sprintf("Failed to add bot to %s: %s", channel, err.Error()))
	}
}

func (a *Ares) addBotGroup(group, user string) {
	api := slack.New(a.SlackAppToken)
	grp, resp, err := api.InviteUserToGroup(group, user)

	log.Println(grp, resp, err)
}

func (a *Ares) notifyUser(user, deleteHash string) {
	api := slack.New(a.SlackBotToken)
	params := slack.PostMessageParameters{}
	attachment := slack.Attachment{
		Color:   "danger",
		Pretext: "Hey there, image uploads to this slack is disabled. Please use external image upload services next time. This incident has been reported :squirrel:",
		Text:    "I have uploaded the image to Imgur for now.",
		Fields: []slack.AttachmentField{
			{
				Title: "If you wish to delete the image uploaded, click on the following link",
				Value: fmt.Sprintf("https://imgur.com/delete/%s", deleteHash),
			},
		},
	}
	params.Attachments = []slack.Attachment{attachment}
	_, _, err := api.PostMessage(user, "", params)
	if err != nil {
		log.Println("Failed to send DM to user", err.Error())
	}
}

func (a *Ares) sendImgToSlack(channel, user, url string) {
	api := slack.New(a.SlackBotToken)
	params := slack.PostMessageParameters{}
	attachment := slack.Attachment{
		Color:    "#D3D3D3",
		Text:     fmt.Sprintf("image originally uploaded by <@%s>", user),
		ImageURL: url,
	}
	params.Attachments = []slack.Attachment{attachment}
	_, _, err := api.PostMessage(channel, "", params)
	if err != nil {
		log.Println("Failed to send DM to user", err.Error())
	}
}

func (a *Ares) handleFile(file *slack.File, channel string) {
	resp := uploadToImgur(file.URLPrivateDownload, a.SlackAppToken, a.ImgurClientID)

	if resp.Status != 200 {
		log.Println("Failed to download/upload")
		return
	}

	a.notifyUser(file.User, resp.Data.Deletehash)
	a.sendImgToSlack(channel, file.User, resp.Data.Link)
	a.deleteFile(file.ID)
}

func isImageFile(fileType string) bool {
	for _, fType := range []string{"jpg", "jpeg", "png", "gif"} {
		if fType == fileType {
			return true
		}
	}
	return false
}

func (a *Ares) Run() {
	api := slack.New(a.SlackBotToken)

	a.initBot()

	rtm := api.NewRTM()
	go rtm.ManageConnection()

	for msg := range rtm.IncomingEvents {
		switch ev := msg.Data.(type) {

		case *slack.MessageEvent:
			if ev.SubType == "file_share" {
				if isImageFile(ev.File.Filetype) {
					a.handleFile(ev.File, ev.Channel)
				}
			}

		case *slack.RTMError:
			fmt.Printf("Error: %s\n", ev.Error())

		case *slack.InvalidAuthEvent:
			fmt.Printf("Invalid credentials")
			return

		default:
			// Ignore other events..
			// fmt.Printf("Unexpected: %v\n", msg.Data)
		}
	}
}
<commit_msg>Add bot to private channels<commit_after>package ares

import (
	"fmt"
	"log"

	"github.com/nlopes/slack"
)

type Ares struct {
	SlackAppToken string
	SlackBotToken string
	SlackAppID    string
	ImgurClientID string
	BotUserID     string
	// Bot won't be added or re-added to these channels
	ManagedChannels []string
}

func (a *Ares) initBot() {
	api := slack.New(a.SlackAppToken)
	a.getBotUserID()

	channels, err := api.GetChannels(true)

	if err != nil {
		log.Fatal("Failed to get public channels list: ", err.Error())
	}

	for _, channel := range channels {
		a.addBotChannel(channel.ID, a.BotUserID)
	}

	groups, err := api.GetGroups(true)

	if err != nil {
		log.Fatal("Failed to get private channels list: ", err.Error())
	}

	for _, group := range groups {
		a.addBotGroup(group.ID, a.BotUserID)
	}
}

func (a *Ares) getBotUserID() {
	api := slack.New(a.SlackAppToken)
	users, err := api.GetUsers()

	if err != nil {
		log.Fatal("Failed to fetch slack users info", err.Error())
	}

	for _, user := range users {

		if user.Profile.ApiAppID == a.SlackAppID {
			a.BotUserID = user.ID
			return
		}
	}
	log.Fatal("Unable to find bot user on the Slack")
}

func (a *Ares) deleteFile(fileId string) {
	api := slack.New(a.SlackAppToken)

	if err := api.DeleteFile(fileId); err != nil {
		fmt.Println("Deletion of the file failed:", err.Error())
	}
}

func (a *Ares) addBotChannel(channelID, user string) {
	api := slack.New(a.SlackAppToken)
	if _, err := api.InviteUserToChannel(channelID, user); err != nil {
		if err.Error() != "already_in_channel" {
			log.Println(fmt.Sprintf("Failed to add bot to %s: %s", channelID, err.Error()))
		}
	}
}

func (a *Ares) addBotGroup(group, user string) {
	api := slack.New(a.SlackAppToken)
	if _, _, err := api.InviteUserToGroup(group, user); err != nil {
		log.Println(fmt.Sprintf("Failed to add bot to %s: %s", group, err.Error()))
	}
}

func (a *Ares) notifyUser(user, deleteHash string) {
	api := slack.New(a.SlackBotToken)
	params := slack.PostMessageParameters{}
	attachment := slack.Attachment{
		Color:   "danger",
		Pretext: "Hey there, image uploads to this slack is disabled. Please use external image upload services next time. This incident has been reported :squirrel:",
		Text:    "I have uploaded the image to Imgur for now.",
		Fields: []slack.AttachmentField{
			{
				Title: "If you wish to delete the image uploaded, click on the following link",
				Value: fmt.Sprintf("https://imgur.com/delete/%s", deleteHash),
			},
		},
	}
	params.Attachments = []slack.Attachment{attachment}
	_, _, err := api.PostMessage(user, "", params)
	if err != nil {
		log.Println("Failed to send DM to user", err.Error())
	}
}

func (a *Ares) sendImgToSlack(channel, user, url string) {
	api := slack.New(a.SlackBotToken)
	params := slack.PostMessageParameters{}
	attachment := slack.Attachment{
		Color:    "#D3D3D3",
		Text:     fmt.Sprintf("image originally uploaded by <@%s>", user),
		ImageURL: url,
	}
	params.Attachments = []slack.Attachment{attachment}
	_, _, err := api.PostMessage(channel, "", params)
	if err != nil {
		log.Println("Failed to send DM to user", err.Error())
	}
}

func (a *Ares) handleFile(file *slack.File, channel string) {
	resp := uploadToImgur(file.URLPrivateDownload, a.SlackAppToken, a.ImgurClientID)

	if resp.Status != 200 {
		log.Println("Failed to download/upload")
		return
	}

	a.notifyUser(file.User, resp.Data.Deletehash)
	a.sendImgToSlack(channel, file.User, resp.Data.Link)
	a.deleteFile(file.ID)
}

func isImageFile(fileType string) bool {
	for _, fType := range []string{"jpg", "jpeg", "png", "gif"} {
		if fType == fileType {
			return true
		}
	}
	return false
}

func (a *Ares) Run() {
	api := slack.New(a.SlackBotToken)

	a.initBot()
	log.Println("Bot initialized. Starting moderation duty.")

	rtm := api.NewRTM()
	go rtm.ManageConnection()

	for msg := range rtm.IncomingEvents {
		switch ev := msg.Data.(type) {

		case *slack.MessageEvent:
			if ev.SubType == "file_share" {
				if isImageFile(ev.File.Filetype) {
					a.handleFile(ev.File, ev.Channel)
				}
			}

		case *slack.RTMError:
			fmt.Printf("Error: %s\n", ev.Error())

		case *slack.InvalidAuthEvent:
			fmt.Printf("Invalid credentials")
			return

		default:
			// Ignore other events..
			// fmt.Printf("Unexpected: %v\n", msg.Data)
		}
	}
}
<|endoftext|>


<commit_before>// Package queue provides a queue for running tasks concurrently up to a certain limit.
package queue

import (
	"runtime"
	"sync"
)

type queue struct {
	Handler          func(interface{})
	ConcurrencyLimit int
	push             chan interface{}
	pop              chan struct{}
	suspend          chan bool
	suspended        bool
	stop             chan struct{}
	stopped          bool
	buffer           []interface{}
	count            int
	wg               sync.WaitGroup
}
// Queue is the queue
// Queue also has the members Handler and ConcurrencyLimit which can be set at anytime
type Queue struct {
	*queue
}

// Handler is a function that takes any value, and is called every time a task is processed from the queue
type Handler func(interface{})

// NewQueue must be called to initialize a new queue.
// The first argument is a Handler
// The second argument is an int which specifies how many operation can run in parallel in the queue, zero means unlimited.
func NewQueue(handler Handler, concurrencyLimit int) *Queue {

	q := &Queue{
		&queue{
			Handler:          handler,
			ConcurrencyLimit: concurrencyLimit,
			push:             make(chan interface{}),
			pop:              make(chan struct{}),
			suspend:          make(chan bool),
			stop:             make(chan struct{}),
		},
	}

	go q.run()
	runtime.SetFinalizer(q, (*Queue).Stop)
	return q
}

// Push pushes a new value to the end of the queue
func (q *Queue) Push(val interface{}) {
	q.push <- val
}
// Stop stops the queue from executing anymore tasks, and waits for the currently executing tasks to finish.
// The queue can not be started again once this is called
func (q *Queue) Stop() {

	q.stop <- struct{}{}
	runtime.SetFinalizer(q, nil)
}
// Wait calls wait on a waitgroup, that waits for all items of the queue to finish execution
func (q *Queue) Wait() {

	q.wg.Wait()
}

// Count returns the number of currently executing tasks and the number of tasks waiting to be executed
func (q *Queue) Count() (_, _ int) {

	return q.count, len(q.buffer)
}

func (q *queue) run() {

	defer func() {
		q.wg.Add(-len(q.buffer))
		q.buffer = nil
	}()
	for {

		select {

		case val := <-q.push:
			q.buffer = append(q.buffer, val)
			q.wg.Add(1)
		case <-q.pop:
			q.count--
		case suspend := <-q.suspend:
			if suspend != q.suspended {

				if suspend {
					q.wg.Add(1)
				} else {
					q.wg.Done()
				}
				q.suspended = suspend
			}
		case <-q.stop:
			q.stopped = true
		}

		if q.stopped && q.count == 0 {

			return
		}

		for (q.count < q.ConcurrencyLimit || q.ConcurrencyLimit == 0) && len(q.buffer) > 0 && !(q.suspended || q.stopped) {

			val := q.buffer[0]
			q.buffer = q.buffer[1:]
			q.count++
			go func() {
				defer func() {
					q.pop <- struct{}{}
					q.wg.Done()
				}()
				q.Handler(val)

			}()
		}

	}

}
<commit_msg>changed Count() to Len()<commit_after>// Package queue provides a queue for running tasks concurrently up to a certain limit.
package queue

import (
	"runtime"
	"sync"
)

type queue struct {
	Handler          func(interface{})
	ConcurrencyLimit int
	push             chan interface{}
	pop              chan interface{}
	suspend          chan bool
	suspended        bool
	stop             chan struct{}
	stopped          bool
	buffer           []interface{}
	count            int
	wg               sync.WaitGroup
}

// Queue is the queue
// Queue also has the members Handler and ConcurrencyLimit which can be set at anytime
type Queue struct {
	*queue
}

// Handler is a function that takes any value, and is called every time a task is processed from the queue
type Handler func(interface{})

// NewQueue must be called to initialize a new queue.
// The first argument is a Handler
// The second argument is an int which specifies how many operation can run in parallel in the queue, zero means unlimited.
func NewQueue(handler Handler, concurrencyLimit int) *Queue {

	q := &Queue{
		&queue{
			Handler:          handler,
			ConcurrencyLimit: concurrencyLimit,
			push:             make(chan interface{}),
			pop:              make(chan struct{}),
			suspend:          make(chan bool),
			stop:             make(chan struct{}),
		},
	}

	go q.run()
	runtime.SetFinalizer(q, (*Queue).Stop)
	return q
}

// Push pushes a new value to the end of the queue
func (q *Queue) Push(val interface{}) {
	q.push <- val
}

// Stop stops the queue from executing anymore tasks, and waits for the currently executing tasks to finish.
// The queue can not be started again once this is called
func (q *Queue) Stop() {

	q.stop <- struct{}{}
	runtime.SetFinalizer(q, nil)
}

// Wait calls wait on a waitgroup, that waits for all items of the queue to finish execution
func (q *Queue) Wait() {

	q.wg.Wait()
}

// Count returns the number of currently executing tasks and the number of tasks waiting to be executed
func (q *Queue) Len() (_, _ int) {

	return q.count, len(q.buffer)
}

func (q *queue) run() {

	defer func() {
		q.wg.Add(-len(q.buffer))
		q.buffer = nil
	}()
	for {

		select {

		case val := <-q.push:
			q.buffer = append(q.buffer, val)
			q.wg.Add(1)
		case <-q.pop:
			q.count--
		case suspend := <-q.suspend:
			if suspend != q.suspended {

				if suspend {
					q.wg.Add(1)
				} else {
					q.wg.Done()
				}
				q.suspended = suspend
			}
		case <-q.stop:
			q.stopped = true
		}

		if q.stopped && q.count == 0 {

			return
		}

		for (q.count < q.ConcurrencyLimit || q.ConcurrencyLimit == 0) && len(q.buffer) > 0 && !(q.suspended || q.stopped) {

			val := q.buffer[0]
			q.buffer = q.buffer[1:]
			q.count++
			go func() {
				defer func() {
					q.pop <- struct{}{}
					q.wg.Done()
				}()
				q.Handler(val)

			}()
		}

	}

}
<|endoftext|>


<commit_before>package main_test

import (
	. "github.com/bluemixgaragelondon/cf-blue-green-deploy"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"strings"
)

var _ = Describe("Args", func() {
	Context("With an appname only", func() {
		args := NewArgs(bgdArgs("appname"))

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("does not set the smoke test file", func() {
			Expect(args.SmokeTestPath).To(BeZero())
		})

		It("does not set a manifest", func() {
			Expect(args.ManifestPath).To(BeZero())
		})
	})

	Context("With a smoke test and an appname", func() {
		args := NewArgs(bgdArgs("appname --smoke-test script/smoke-test"))

		It("sets the smoke test file", func() {
			Expect(args.SmokeTestPath).To(Equal("script/smoke-test"))
		})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("does not set a manifest", func() {
			Expect(args.ManifestPath).To(BeZero())
		})
	})

	Context("With an appname smoke test and a manifest", func() {
		args := NewArgs(bgdArgs("appname --smoke-test smokey -f manifest.yml"))

		It("sets the smoke test file", func() {
			Expect(args.SmokeTestPath).To(Equal("smokey"))
		})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("sets a manifest", func() {
			Expect(args.ManifestPath).To(Equal("manifest.yml"))
		})
	})

	Context("When a global cf flag is set with an app name", func() {
		args := NewArgs([]string{"cf", "-v", "blue-green-deploy", "app"})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("app"))
		})
	})

	Context("When the bgd abbreviation is used", func() {
		args := NewArgs([]string{"cf", "bgd", "app"})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("app"))
		})
	})
})

func bgdArgs(argString string) []string {
	args := strings.Split(argString, " ")
	return append([]string{"cf", "blue-green-deploy"}, args...)
}
<commit_msg>Make test slightly stronger<commit_after>package main_test

import (
	. "github.com/bluemixgaragelondon/cf-blue-green-deploy"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"strings"
)

var _ = Describe("Args", func() {
	Context("With an appname only", func() {
		args := NewArgs(bgdArgs("appname"))

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("does not set the smoke test file", func() {
			Expect(args.SmokeTestPath).To(BeZero())
		})

		It("does not set a manifest", func() {
			Expect(args.ManifestPath).To(BeZero())
		})
	})

	Context("With a smoke test and an appname", func() {
		args := NewArgs(bgdArgs("appname --smoke-test script/smoke-test"))

		It("sets the smoke test file", func() {
			Expect(args.SmokeTestPath).To(Equal("script/smoke-test"))
		})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("does not set a manifest", func() {
			Expect(args.ManifestPath).To(BeZero())
		})
	})

	Context("With an appname smoke test and a manifest", func() {
		args := NewArgs(bgdArgs("appname --smoke-test smokey -f custommanifest.yml"))

		It("sets the smoke test file", func() {
			Expect(args.SmokeTestPath).To(Equal("smokey"))
		})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("appname"))
		})

		It("sets a manifest", func() {
			Expect(args.ManifestPath).To(Equal("custommanifest.yml"))
		})
	})

	Context("When a global cf flag is set with an app name", func() {
		args := NewArgs([]string{"cf", "-v", "blue-green-deploy", "app"})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("app"))
		})
	})

	Context("When the bgd abbreviation is used", func() {
		args := NewArgs([]string{"cf", "bgd", "app"})

		It("sets the app name", func() {
			Expect(args.AppName).To(Equal("app"))
		})
	})
})

func bgdArgs(argString string) []string {
	args := strings.Split(argString, " ")
	return append([]string{"cf", "blue-green-deploy"}, args...)
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/jdkato/prose/tag"
	"github.com/jdkato/regexp"
	"github.com/xrash/smetrics"
)

// ExeDir is our starting location.
var ExeDir string

func IsLetter(s string) bool {
	for _, r := range s {
		if !unicode.IsLetter(r) {
			return false
		}
	}
	return true
}

// TextToWords convert raw text into a slice of words.
func TextToWords(text string) []string {
	words := []string{}
	for _, s := range SentenceTokenizer.Tokenize(text) {
		words = append(words, strings.Fields(s)...)
	}
	return words
}

// InRange determines if the range r contains the integer n.
func InRange(n int, r []int) bool {
	return len(r) == 2 && (r[0] <= n && n <= r[1])
}

// SlicesEqual determines if the slices a and b are equal.
func SlicesEqual(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

// CheckPOS determines if a match (as found by an extension point) also matches
// the expected part-of-speech in text.
func CheckPOS(loc []int, expected, text string) bool {
	var word string

	// Initilize our tagger, if needed.
	if Tagger == nil {
		Tagger = tag.NewPerceptronTagger()
	}

	pos := 1
	observed := []string{}
	for _, tok := range Tagger.Tag(TextToWords(text)) {
		if InRange(pos, loc) {
			if len(tok.Text) > 1 {
				word = strings.ToLower(strings.TrimRight(tok.Text, ",.!?:;"))
			} else {
				word = tok.Text
			}
			observed = append(observed, (word + "/" + tok.Tag))
		}
		pos += len(tok.Text) + 1
	}

	match, _ := regexp.MatchString(expected, strings.Join(observed, " "))
	return !match
}

// Stat checks if we have anything waiting in stdin.
func Stat() bool {
	stat, err := os.Stdin.Stat()
	if err != nil || (stat.Mode()&os.ModeCharDevice) != 0 {
		return false
	}
	return true
}

// Which checks for the existence of any command in `cmds`.
func Which(cmds []string) string {
	for _, cmd := range cmds {
		path, err := exec.LookPath(cmd)
		if err == nil {
			return path
		}
	}
	return ""
}

// FormatMessage inserts `subs` into `msg`.
func FormatMessage(msg string, subs ...string) string {
	n := strings.Count(msg, "%s")
	max := len(subs)
	found := []string{}
	for i := 0; i < n && i < max; i++ {
		found = append(found, strings.TrimSpace(subs[i]))
	}
	return fmt.Sprintf(msg, StringsToInterface(found)...)
}

// Substitute replaces the substring `sub` with a string of asterisks.
func Substitute(src, sub string, char rune) (string, bool) {
	idx := strings.Index(src, sub)
	if idx < 0 {
		return src, false
	}
	repl := strings.Map(func(r rune) rune {
		if r != '\n' {
			return char
		}
		return r
	}, sub)
	return src[:idx] + repl + src[idx+len(sub):], true
}

// StringsToInterface converts a slice of strings to an interface.
func StringsToInterface(strings []string) []interface{} {
	intf := make([]interface{}, len(strings))
	for i, v := range strings {
		intf[i] = v
	}
	return intf
}

// DumpConfig returns Vale's configuration in JSON format.
func DumpConfig(config *Config) string {
	b, _ := json.MarshalIndent(config, "", "  ")
	return string(b)
}

// JaroWinkler searches `ctx` line-by-line for a JaroWinkler distance score
// greater than a particular threshold.
func JaroWinkler(ctx, sub string) (int, string) {
	threshold := 0.75
	if strings.Contains(sub, "`*") && len(strings.Fields(sub)) < 7 {
		threshold = 0.65 // lower the threshold for short text with code spans
	}
	for _, line := range strings.Split(ctx, "\n") {
		for _, text := range SentenceTokenizer.Tokenize(line) {
			if smetrics.JaroWinkler(sub, text, 0.7, 4) > threshold {
				return strings.Index(ctx, text) + 1, text
			}
		}
	}
	return -1, sub
}

// initialPosition calculates the position of a match (given by the location in
// the reference document, `loc`) in the source document (`ctx`).
func initialPosition(ctx, sub string, loc []int) (int, string) {
	idx := strings.Index(ctx, sub)
	if idx < 0 {
		// Fall back to the JaroWinkler distance. This should only happen if
		// we're in a scope that contains inline markup (e.g., a sentence with
		// code spans).
		return JaroWinkler(ctx, sub)
	}
	pat := `(?:^|\b|_)` + regexp.QuoteMeta(sub) + `(?:\b|_|$)`
	txt := strings.TrimSpace(ctx[Max(idx-1, 0):Min(idx+len(sub)+1, len(ctx))])
	if match, err := regexp.MatchString(pat, txt); err != nil || !match {
		// If there's more than one, we take the first bounded option.
		// For example, given that we're looking for "very", "every" => nested
		// and " very " => bounded.
		sloc := regexp.MustCompile(pat).FindStringIndex(ctx)
		if len(sloc) > 0 {
			idx = sloc[0]
			if strings.HasPrefix(ctx[idx:], "_") {
				idx++ // We don't want to include the underscore boundary.
			}
		}
	}
	return utf8.RuneCountInString(ctx[:idx]) + 1, sub
}

// sanitizer replaces a set of unicode characters with ASCII equivalents.
var sanitizer = strings.NewReplacer(
	"\u201c", `"`,
	"\u201d", `"`,
	"\u2018", "'",
	"\u2019", "'",
	"’", "'",
	"\r\n", "\n",
	"\r", "\n")

// PrepText prepares text for our check functions.
func PrepText(txt string) string {
	return sanitizer.Replace(txt)
}

// Indent adds padding to every line of `text`.
func Indent(text, indent string) string {
	if text[len(text)-1:] == "\n" {
		result := ""
		for _, j := range strings.Split(text[:len(text)-1], "\n") {
			result += indent + j + "\n"
		}
		return result
	}
	result := ""
	for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
		result += indent + j + "\n"
	}
	return result[:len(result)-1]
}

// FormatFromExt takes a file extension and returns its [normExt, format]
// list, if supported.
func FormatFromExt(path string) (string, string) {
	ext := filepath.Ext(path)
	for r, f := range FormatByExtension {
		m, _ := regexp.MatchString(r, ext)
		if m {
			return f[0], f[1]
		}
	}
	return "unknown", "unknown"
}

// IsDir determines if the path given by `filename` is a directory.
func IsDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

// FileExists determines if the path given by `filename` exists.
func FileExists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

// Min returns the min of `a` and `b`.
func Min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// Max returns the max of `a` and `b`.
func Max(a, b int) int {
	if a < b {
		return b
	}
	return a
}

// StringInSlice determines if `slice` contains the string `a`.
func StringInSlice(a string, slice []string) bool {
	for _, b := range slice {
		if a == b {
			return true
		}
	}
	return false
}

// AllStringsInSlice determines if `slice` contains the `strings`.
func AllStringsInSlice(strings []string, slice []string) bool {
	for _, s := range strings {
		if !StringInSlice(s, slice) {
			return false
		}
	}
	return true
}

// HasAnyPrefix determines if `text` has any prefix contained in `slice`.
func HasAnyPrefix(text string, slice []string) bool {
	for _, s := range slice {
		if strings.HasPrefix(text, s) {
			return true
		}
	}
	return false
}

// ContainsAny determines if `text` contains any string in `slice`.
func ContainsAny(text string, slice []string) bool {
	for _, s := range slice {
		if strings.Contains(text, s) {
			return true
		}
	}
	return false
}

// CheckError prints any errors to stdout. A return value of true => no error.
func CheckError(err error) bool {
	if err != nil {
		_, werr := fmt.Fprintln(os.Stderr, err.Error())
		if werr != nil {
			panic(werr)
		}
	}
	return err == nil
}

// LooksLikeStdin determines if s appears to be a string.
func LooksLikeStdin(s string) bool {
	return !(FileExists(s) || IsDir(s))
}

// CheckAndClose closes `file` and prints any errors to stdout.
// A return value of true => no error.
func CheckAndClose(file *os.File) bool {
	err := file.Close()
	return CheckError(err)
}

// SplitLines splits on CRLF, CR not followed by LF, and LF.
func SplitLines(data []byte, atEOF bool) (adv int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexAny(data, "\r\n"); i >= 0 {
		if data[i] == '\n' {
			return i + 1, data[0:i], nil
		}
		adv = i + 1
		if len(data) > i+1 && data[i+1] == '\n' {
			adv++
		}
		return adv, data[0:i], nil
	}
	if atEOF {
		return len(data), data, nil
	}
	return 0, nil, nil
}

// DeterminePath decides if `keyPath` is relative or absolute.
func DeterminePath(configPath string, keyPath string) string {
	sep := string(filepath.Separator)
	abs, _ := filepath.Abs(keyPath)
	rel := strings.TrimRight(keyPath, sep)
	if abs != rel || !strings.Contains(keyPath, sep) {
		// The path was relative
		return filepath.Join(configPath, keyPath)
	}
	return abs
}
<commit_msg>style: add comment<commit_after>package core

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"unicode"
	"unicode/utf8"

	"github.com/jdkato/prose/tag"
	"github.com/jdkato/regexp"
	"github.com/xrash/smetrics"
)

// ExeDir is our starting location.
var ExeDir string

// IsLetter returns `true` if s contains all letter characters and false if not.
func IsLetter(s string) bool {
	for _, r := range s {
		if !unicode.IsLetter(r) {
			return false
		}
	}
	return true
}

// TextToWords convert raw text into a slice of words.
func TextToWords(text string) []string {
	words := []string{}
	for _, s := range SentenceTokenizer.Tokenize(text) {
		words = append(words, strings.Fields(s)...)
	}
	return words
}

// InRange determines if the range r contains the integer n.
func InRange(n int, r []int) bool {
	return len(r) == 2 && (r[0] <= n && n <= r[1])
}

// SlicesEqual determines if the slices a and b are equal.
func SlicesEqual(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}

// CheckPOS determines if a match (as found by an extension point) also matches
// the expected part-of-speech in text.
func CheckPOS(loc []int, expected, text string) bool {
	var word string

	// Initilize our tagger, if needed.
	if Tagger == nil {
		Tagger = tag.NewPerceptronTagger()
	}

	pos := 1
	observed := []string{}
	for _, tok := range Tagger.Tag(TextToWords(text)) {
		if InRange(pos, loc) {
			if len(tok.Text) > 1 {
				word = strings.ToLower(strings.TrimRight(tok.Text, ",.!?:;"))
			} else {
				word = tok.Text
			}
			observed = append(observed, (word + "/" + tok.Tag))
		}
		pos += len(tok.Text) + 1
	}

	match, _ := regexp.MatchString(expected, strings.Join(observed, " "))
	return !match
}

// Stat checks if we have anything waiting in stdin.
func Stat() bool {
	stat, err := os.Stdin.Stat()
	if err != nil || (stat.Mode()&os.ModeCharDevice) != 0 {
		return false
	}
	return true
}

// Which checks for the existence of any command in `cmds`.
func Which(cmds []string) string {
	for _, cmd := range cmds {
		path, err := exec.LookPath(cmd)
		if err == nil {
			return path
		}
	}
	return ""
}

// FormatMessage inserts `subs` into `msg`.
func FormatMessage(msg string, subs ...string) string {
	n := strings.Count(msg, "%s")
	max := len(subs)
	found := []string{}
	for i := 0; i < n && i < max; i++ {
		found = append(found, strings.TrimSpace(subs[i]))
	}
	return fmt.Sprintf(msg, StringsToInterface(found)...)
}

// Substitute replaces the substring `sub` with a string of asterisks.
func Substitute(src, sub string, char rune) (string, bool) {
	idx := strings.Index(src, sub)
	if idx < 0 {
		return src, false
	}
	repl := strings.Map(func(r rune) rune {
		if r != '\n' {
			return char
		}
		return r
	}, sub)
	return src[:idx] + repl + src[idx+len(sub):], true
}

// StringsToInterface converts a slice of strings to an interface.
func StringsToInterface(strings []string) []interface{} {
	intf := make([]interface{}, len(strings))
	for i, v := range strings {
		intf[i] = v
	}
	return intf
}

// DumpConfig returns Vale's configuration in JSON format.
func DumpConfig(config *Config) string {
	b, _ := json.MarshalIndent(config, "", "  ")
	return string(b)
}

// JaroWinkler searches `ctx` line-by-line for a JaroWinkler distance score
// greater than a particular threshold.
func JaroWinkler(ctx, sub string) (int, string) {
	threshold := 0.75
	if strings.Contains(sub, "`*") && len(strings.Fields(sub)) < 7 {
		threshold = 0.65 // lower the threshold for short text with code spans
	}
	for _, line := range strings.Split(ctx, "\n") {
		for _, text := range SentenceTokenizer.Tokenize(line) {
			if smetrics.JaroWinkler(sub, text, 0.7, 4) > threshold {
				return strings.Index(ctx, text) + 1, text
			}
		}
	}
	return -1, sub
}

// initialPosition calculates the position of a match (given by the location in
// the reference document, `loc`) in the source document (`ctx`).
func initialPosition(ctx, sub string, loc []int) (int, string) {
	idx := strings.Index(ctx, sub)
	if idx < 0 {
		// Fall back to the JaroWinkler distance. This should only happen if
		// we're in a scope that contains inline markup (e.g., a sentence with
		// code spans).
		return JaroWinkler(ctx, sub)
	}
	pat := `(?:^|\b|_)` + regexp.QuoteMeta(sub) + `(?:\b|_|$)`
	txt := strings.TrimSpace(ctx[Max(idx-1, 0):Min(idx+len(sub)+1, len(ctx))])
	if match, err := regexp.MatchString(pat, txt); err != nil || !match {
		// If there's more than one, we take the first bounded option.
		// For example, given that we're looking for "very", "every" => nested
		// and " very " => bounded.
		sloc := regexp.MustCompile(pat).FindStringIndex(ctx)
		if len(sloc) > 0 {
			idx = sloc[0]
			if strings.HasPrefix(ctx[idx:], "_") {
				idx++ // We don't want to include the underscore boundary.
			}
		}
	}
	return utf8.RuneCountInString(ctx[:idx]) + 1, sub
}

// sanitizer replaces a set of unicode characters with ASCII equivalents.
var sanitizer = strings.NewReplacer(
	"\u201c", `"`,
	"\u201d", `"`,
	"\u2018", "'",
	"\u2019", "'",
	"’", "'",
	"\r\n", "\n",
	"\r", "\n")

// PrepText prepares text for our check functions.
func PrepText(txt string) string {
	return sanitizer.Replace(txt)
}

// Indent adds padding to every line of `text`.
func Indent(text, indent string) string {
	if text[len(text)-1:] == "\n" {
		result := ""
		for _, j := range strings.Split(text[:len(text)-1], "\n") {
			result += indent + j + "\n"
		}
		return result
	}
	result := ""
	for _, j := range strings.Split(strings.TrimRight(text, "\n"), "\n") {
		result += indent + j + "\n"
	}
	return result[:len(result)-1]
}

// FormatFromExt takes a file extension and returns its [normExt, format]
// list, if supported.
func FormatFromExt(path string) (string, string) {
	ext := filepath.Ext(path)
	for r, f := range FormatByExtension {
		m, _ := regexp.MatchString(r, ext)
		if m {
			return f[0], f[1]
		}
	}
	return "unknown", "unknown"
}

// IsDir determines if the path given by `filename` is a directory.
func IsDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

// FileExists determines if the path given by `filename` exists.
func FileExists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

// Min returns the min of `a` and `b`.
func Min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// Max returns the max of `a` and `b`.
func Max(a, b int) int {
	if a < b {
		return b
	}
	return a
}

// StringInSlice determines if `slice` contains the string `a`.
func StringInSlice(a string, slice []string) bool {
	for _, b := range slice {
		if a == b {
			return true
		}
	}
	return false
}

// AllStringsInSlice determines if `slice` contains the `strings`.
func AllStringsInSlice(strings []string, slice []string) bool {
	for _, s := range strings {
		if !StringInSlice(s, slice) {
			return false
		}
	}
	return true
}

// HasAnyPrefix determines if `text` has any prefix contained in `slice`.
func HasAnyPrefix(text string, slice []string) bool {
	for _, s := range slice {
		if strings.HasPrefix(text, s) {
			return true
		}
	}
	return false
}

// ContainsAny determines if `text` contains any string in `slice`.
func ContainsAny(text string, slice []string) bool {
	for _, s := range slice {
		if strings.Contains(text, s) {
			return true
		}
	}
	return false
}

// CheckError prints any errors to stdout. A return value of true => no error.
func CheckError(err error) bool {
	if err != nil {
		_, werr := fmt.Fprintln(os.Stderr, err.Error())
		if werr != nil {
			panic(werr)
		}
	}
	return err == nil
}

// LooksLikeStdin determines if s appears to be a string.
func LooksLikeStdin(s string) bool {
	return !(FileExists(s) || IsDir(s))
}

// CheckAndClose closes `file` and prints any errors to stdout.
// A return value of true => no error.
func CheckAndClose(file *os.File) bool {
	err := file.Close()
	return CheckError(err)
}

// SplitLines splits on CRLF, CR not followed by LF, and LF.
func SplitLines(data []byte, atEOF bool) (adv int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}
	if i := bytes.IndexAny(data, "\r\n"); i >= 0 {
		if data[i] == '\n' {
			return i + 1, data[0:i], nil
		}
		adv = i + 1
		if len(data) > i+1 && data[i+1] == '\n' {
			adv++
		}
		return adv, data[0:i], nil
	}
	if atEOF {
		return len(data), data, nil
	}
	return 0, nil, nil
}

// DeterminePath decides if `keyPath` is relative or absolute.
func DeterminePath(configPath string, keyPath string) string {
	sep := string(filepath.Separator)
	abs, _ := filepath.Abs(keyPath)
	rel := strings.TrimRight(keyPath, sep)
	if abs != rel || !strings.Contains(keyPath, sep) {
		// The path was relative
		return filepath.Join(configPath, keyPath)
	}
	return abs
}
<|endoftext|>


<commit_before>package google

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	gauth "golang.org/x/oauth2/google"
	"google.golang.org/api/dns/v1"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/StackExchange/dnscontrol/providers/diff"
	"github.com/pkg/errors"
)

var features = providers.DocumentationNotes{
	providers.DocCreateDomains:       providers.Can(),
	providers.DocDualHost:            providers.Can(),
	providers.DocOfficiallySupported: providers.Can(),
	providers.CanUsePTR:              providers.Can(),
	providers.CanUseSRV:              providers.Can(),
	providers.CanUseCAA:              providers.Can(),
}

func init() {
	providers.RegisterDomainServiceProviderType("GCLOUD", New, features)
}

type gcloud struct {
	client  *dns.Service
	project string
	zones   map[string]*dns.ManagedZone
}

// New creates a new gcloud provider
func New(cfg map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
	raw, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}
	config, err := gauth.JWTConfigFromJSON(raw, "https://www.googleapis.com/auth/ndev.clouddns.readwrite")
	if err != nil {
		return nil, err
	}
	ctx := context.Background()
	hc := config.Client(ctx)
	dcli, err := dns.New(hc)
	if err != nil {
		return nil, err
	}
	return &gcloud{
		client:  dcli,
		project: cfg["project_id"],
	}, nil
}

type errNoExist struct {
	domain string
}

func (e errNoExist) Error() string {
	return fmt.Sprintf("Domain %s not found in gcloud account", e.domain)
}

func (g *gcloud) getZone(domain string) (*dns.ManagedZone, error) {
	if g.zones == nil {
		g.zones = map[string]*dns.ManagedZone{}
		pageToken := ""
		for {
			resp, err := g.client.ManagedZones.List(g.project).PageToken(pageToken).Do()
			if err != nil {
				return nil, err
			}
			for _, z := range resp.ManagedZones {
				g.zones[z.DnsName] = z
			}
			if pageToken = resp.NextPageToken; pageToken == "" {
				break
			}
		}
	}
	if g.zones[domain+"."] == nil {
		return nil, errNoExist{domain}
	}
	return g.zones[domain+"."], nil
}

func (g *gcloud) GetNameservers(domain string) ([]*models.Nameserver, error) {
	zone, err := g.getZone(domain)
	if err != nil {
		return nil, err
	}
	return models.StringsToNameservers(zone.NameServers), nil
}

type key struct {
	Type string
	Name string
}

func keyFor(r *dns.ResourceRecordSet) key {
	return key{Type: r.Type, Name: r.Name}
}
func keyForRec(r *models.RecordConfig) key {
	return key{Type: r.Type, Name: r.NameFQDN + "."}
}

func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	if err := dc.Punycode(); err != nil {
		return nil, err
	}
	rrs, zoneName, err := g.getRecords(dc.Name)
	if err != nil {
		return nil, err
	}
	// convert to dnscontrol RecordConfig format
	existingRecords := []*models.RecordConfig{}
	oldRRs := map[key]*dns.ResourceRecordSet{}
	for _, set := range rrs {
		oldRRs[keyFor(set)] = set
		for _, rec := range set.Rrdatas {
			existingRecords = append(existingRecords, nativeToRecord(set, rec, dc.Name))
		}
	}

	// Normalize
	models.PostProcessRecords(existingRecords)

	// first collect keys that have changed
	differ := diff.New(dc)
	_, create, delete, modify := differ.IncrementalDiff(existingRecords)
	changedKeys := map[key]bool{}
	desc := ""
	for _, c := range create {
		desc += fmt.Sprintln(c)
		changedKeys[keyForRec(c.Desired)] = true
	}
	for _, d := range delete {
		desc += fmt.Sprintln(d)
		changedKeys[keyForRec(d.Existing)] = true
	}
	for _, m := range modify {
		desc += fmt.Sprintln(m)
		changedKeys[keyForRec(m.Existing)] = true
	}
	if len(changedKeys) == 0 {
		return nil, nil
	}
	chg := &dns.Change{Kind: "dns#change"}
	for ck := range changedKeys {
		// remove old version (if present)
		if old, ok := oldRRs[ck]; ok {
			chg.Deletions = append(chg.Deletions, old)
		}
		// collect records to replace with
		newRRs := &dns.ResourceRecordSet{
			Name: ck.Name,
			Type: ck.Type,
			Kind: "dns#resourceRecordSet",
		}
		for _, r := range dc.Records {
			if keyForRec(r) == ck {
				newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined())
				newRRs.Ttl = int64(r.TTL)
			}
		}
		if len(newRRs.Rrdatas) > 0 {
			chg.Additions = append(chg.Additions, newRRs)
		}
	}

	runChange := func() error {
		_, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
		return err
	}
	return []*models.Correction{{
		Msg: desc,
		F:   runChange,
	}}, nil
}

func nativeToRecord(set *dns.ResourceRecordSet, rec, origin string) *models.RecordConfig {
	r := &models.RecordConfig{}
	r.SetLabelFromFQDN(set.Name, origin)
	r.TTL = uint32(set.Ttl)
	if err := r.PopulateFromString(set.Type, rec, origin); err != nil {
		panic(errors.Wrap(err, "unparsable record received from GCLOUD"))
	}
	return r
}

func (g *gcloud) getRecords(domain string) ([]*dns.ResourceRecordSet, string, error) {
	zone, err := g.getZone(domain)
	if err != nil {
		return nil, "", err
	}
	pageToken := ""
	sets := []*dns.ResourceRecordSet{}
	for {
		call := g.client.ResourceRecordSets.List(g.project, zone.Name)
		if pageToken != "" {
			call = call.PageToken(pageToken)
		}
		resp, err := call.Do()
		if err != nil {
			return nil, "", err
		}
		for _, rrs := range resp.Rrsets {
			if rrs.Type == "SOA" {
				continue
			}
			sets = append(sets, rrs)
		}
		if pageToken = resp.NextPageToken; pageToken == "" {
			break
		}
	}
	return sets, zone.Name, nil
}

func (g *gcloud) EnsureDomainExists(domain string) error {
	z, err := g.getZone(domain)
	if err != nil {
		if _, ok := err.(errNoExist); !ok {
			return err
		}
	}
	if z != nil {
		return nil
	}
	fmt.Printf("Adding zone for %s to gcloud account\n", domain)
	mz := &dns.ManagedZone{
		DnsName:     domain + ".",
		Name:        strings.Replace(domain, ".", "-", -1),
		Description: "zone added by dnscontrol",
	}
	g.zones = nil // reset cache
	_, err = g.client.ManagedZones.Create(g.project, mz).Do()
	return err
}
<commit_msg>GCLOUD: Differentiate two modules name dns (#328)<commit_after>package google

import (
	"context"
	"encoding/json"
	"fmt"
	"strings"

	gauth "golang.org/x/oauth2/google"
	gdns "google.golang.org/api/dns/v1"

	"github.com/StackExchange/dnscontrol/models"
	"github.com/StackExchange/dnscontrol/providers"
	"github.com/StackExchange/dnscontrol/providers/diff"
	"github.com/pkg/errors"
)

var features = providers.DocumentationNotes{
	providers.DocCreateDomains:       providers.Can(),
	providers.DocDualHost:            providers.Can(),
	providers.DocOfficiallySupported: providers.Can(),
	providers.CanUsePTR:              providers.Can(),
	providers.CanUseSRV:              providers.Can(),
	providers.CanUseCAA:              providers.Can(),
}

func init() {
	providers.RegisterDomainServiceProviderType("GCLOUD", New, features)
}

type gcloud struct {
	client  *gdns.Service
	project string
	zones   map[string]*gdns.ManagedZone
}

// New creates a new gcloud provider
func New(cfg map[string]string, _ json.RawMessage) (providers.DNSServiceProvider, error) {
	raw, err := json.Marshal(cfg)
	if err != nil {
		return nil, err
	}
	config, err := gauth.JWTConfigFromJSON(raw, "https://www.googleapis.com/auth/ndev.clouddns.readwrite")
	if err != nil {
		return nil, err
	}
	ctx := context.Background()
	hc := config.Client(ctx)
	dcli, err := gdns.New(hc)
	if err != nil {
		return nil, err
	}
	return &gcloud{
		client:  dcli,
		project: cfg["project_id"],
	}, nil
}

type errNoExist struct {
	domain string
}

func (e errNoExist) Error() string {
	return fmt.Sprintf("Domain %s not found in gcloud account", e.domain)
}

func (g *gcloud) getZone(domain string) (*gdns.ManagedZone, error) {
	if g.zones == nil {
		g.zones = map[string]*gdns.ManagedZone{}
		pageToken := ""
		for {
			resp, err := g.client.ManagedZones.List(g.project).PageToken(pageToken).Do()
			if err != nil {
				return nil, err
			}
			for _, z := range resp.ManagedZones {
				g.zones[z.DnsName] = z
			}
			if pageToken = resp.NextPageToken; pageToken == "" {
				break
			}
		}
	}
	if g.zones[domain+"."] == nil {
		return nil, errNoExist{domain}
	}
	return g.zones[domain+"."], nil
}

func (g *gcloud) GetNameservers(domain string) ([]*models.Nameserver, error) {
	zone, err := g.getZone(domain)
	if err != nil {
		return nil, err
	}
	return models.StringsToNameservers(zone.NameServers), nil
}

type key struct {
	Type string
	Name string
}

func keyFor(r *gdns.ResourceRecordSet) key {
	return key{Type: r.Type, Name: r.Name}
}
func keyForRec(r *models.RecordConfig) key {
	return key{Type: r.Type, Name: r.NameFQDN + "."}
}

func (g *gcloud) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
	if err := dc.Punycode(); err != nil {
		return nil, err
	}
	rrs, zoneName, err := g.getRecords(dc.Name)
	if err != nil {
		return nil, err
	}
	// convert to dnscontrol RecordConfig format
	existingRecords := []*models.RecordConfig{}
	oldRRs := map[key]*gdns.ResourceRecordSet{}
	for _, set := range rrs {
		oldRRs[keyFor(set)] = set
		for _, rec := range set.Rrdatas {
			existingRecords = append(existingRecords, nativeToRecord(set, rec, dc.Name))
		}
	}

	// Normalize
	models.PostProcessRecords(existingRecords)

	// first collect keys that have changed
	differ := diff.New(dc)
	_, create, delete, modify := differ.IncrementalDiff(existingRecords)
	changedKeys := map[key]bool{}
	desc := ""
	for _, c := range create {
		desc += fmt.Sprintln(c)
		changedKeys[keyForRec(c.Desired)] = true
	}
	for _, d := range delete {
		desc += fmt.Sprintln(d)
		changedKeys[keyForRec(d.Existing)] = true
	}
	for _, m := range modify {
		desc += fmt.Sprintln(m)
		changedKeys[keyForRec(m.Existing)] = true
	}
	if len(changedKeys) == 0 {
		return nil, nil
	}
	chg := &gdns.Change{Kind: "dns#change"}
	for ck := range changedKeys {
		// remove old version (if present)
		if old, ok := oldRRs[ck]; ok {
			chg.Deletions = append(chg.Deletions, old)
		}
		// collect records to replace with
		newRRs := &gdns.ResourceRecordSet{
			Name: ck.Name,
			Type: ck.Type,
			Kind: "dns#resourceRecordSet",
		}
		for _, r := range dc.Records {
			if keyForRec(r) == ck {
				newRRs.Rrdatas = append(newRRs.Rrdatas, r.GetTargetCombined())
				newRRs.Ttl = int64(r.TTL)
			}
		}
		if len(newRRs.Rrdatas) > 0 {
			chg.Additions = append(chg.Additions, newRRs)
		}
	}

	runChange := func() error {
		_, err := g.client.Changes.Create(g.project, zoneName, chg).Do()
		return err
	}
	return []*models.Correction{{
		Msg: desc,
		F:   runChange,
	}}, nil
}

func nativeToRecord(set *gdns.ResourceRecordSet, rec, origin string) *models.RecordConfig {
	r := &models.RecordConfig{}
	r.SetLabelFromFQDN(set.Name, origin)
	r.TTL = uint32(set.Ttl)
	if err := r.PopulateFromString(set.Type, rec, origin); err != nil {
		panic(errors.Wrap(err, "unparsable record received from GCLOUD"))
	}
	return r
}

func (g *gcloud) getRecords(domain string) ([]*gdns.ResourceRecordSet, string, error) {
	zone, err := g.getZone(domain)
	if err != nil {
		return nil, "", err
	}
	pageToken := ""
	sets := []*gdns.ResourceRecordSet{}
	for {
		call := g.client.ResourceRecordSets.List(g.project, zone.Name)
		if pageToken != "" {
			call = call.PageToken(pageToken)
		}
		resp, err := call.Do()
		if err != nil {
			return nil, "", err
		}
		for _, rrs := range resp.Rrsets {
			if rrs.Type == "SOA" {
				continue
			}
			sets = append(sets, rrs)
		}
		if pageToken = resp.NextPageToken; pageToken == "" {
			break
		}
	}
	return sets, zone.Name, nil
}

func (g *gcloud) EnsureDomainExists(domain string) error {
	z, err := g.getZone(domain)
	if err != nil {
		if _, ok := err.(errNoExist); !ok {
			return err
		}
	}
	if z != nil {
		return nil
	}
	fmt.Printf("Adding zone for %s to gcloud account\n", domain)
	mz := &gdns.ManagedZone{
		DnsName:     domain + ".",
		Name:        strings.Replace(domain, ".", "-", -1),
		Description: "zone added by dnscontrol",
	}
	g.zones = nil // reset cache
	_, err = g.client.ManagedZones.Create(g.project, mz).Do()
	return err
}
<|endoftext|>


<commit_before>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package inpututil provides utility functions of input like keyboard or mouse.
//
// Note: This package is experimental and API might be changed.
package inpututil

import (
	"sort"
	"sync"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/hooks"
)

type inputState struct {
	keyDurations     map[ebiten.Key]int
	prevKeyDurations map[ebiten.Key]int

	mouseButtonDurations     map[ebiten.MouseButton]int
	prevMouseButtonDurations map[ebiten.MouseButton]int

	gamepadIDs     map[int]struct{}
	prevGamepadIDs map[int]struct{}

	gamepadButtonDurations     map[int]map[ebiten.GamepadButton]int
	prevGamepadButtonDurations map[int]map[ebiten.GamepadButton]int

	touchDurations     map[int]int
	prevTouchDurations map[int]int

	m sync.RWMutex
}

var theInputState = &inputState{
	keyDurations:     map[ebiten.Key]int{},
	prevKeyDurations: map[ebiten.Key]int{},

	mouseButtonDurations:     map[ebiten.MouseButton]int{},
	prevMouseButtonDurations: map[ebiten.MouseButton]int{},

	gamepadIDs:     map[int]struct{}{},
	prevGamepadIDs: map[int]struct{}{},

	gamepadButtonDurations:     map[int]map[ebiten.GamepadButton]int{},
	prevGamepadButtonDurations: map[int]map[ebiten.GamepadButton]int{},

	touchDurations:     map[int]int{},
	prevTouchDurations: map[int]int{},
}

func init() {
	hooks.AppendHookOnBeforeUpdate(func() error {
		theInputState.update()
		return nil
	})
}

func (i *inputState) update() {
	i.m.Lock()
	defer i.m.Unlock()

	// Keyboard
	for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
		i.prevKeyDurations[k] = i.keyDurations[k]
		if ebiten.IsKeyPressed(k) {
			i.keyDurations[k]++
		} else {
			i.keyDurations[k] = 0
		}
	}

	// Mouse
	for _, b := range []ebiten.MouseButton{
		ebiten.MouseButtonLeft,
		ebiten.MouseButtonRight,
		ebiten.MouseButtonMiddle,
	} {
		i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
		if ebiten.IsMouseButtonPressed(b) {
			i.mouseButtonDurations[b]++
		} else {
			i.mouseButtonDurations[b] = 0
		}
	}

	// Gamepads

	// Copy the gamepad IDs.
	i.prevGamepadIDs = map[int]struct{}{}
	for id := range i.gamepadIDs {
		i.prevGamepadIDs[id] = struct{}{}
	}

	// Copy the gamepad button durations.
	i.prevGamepadButtonDurations = map[int]map[ebiten.GamepadButton]int{}
	for id, ds := range i.gamepadButtonDurations {
		i.prevGamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
		for b, d := range ds {
			i.prevGamepadButtonDurations[id][b] = d
		}
	}

	i.gamepadIDs = map[int]struct{}{}
	for _, id := range ebiten.GamepadIDs() {
		i.gamepadIDs[id] = struct{}{}
		if _, ok := i.gamepadButtonDurations[id]; !ok {
			i.gamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
		}
		n := ebiten.GamepadButtonNum(id)
		for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
			if ebiten.IsGamepadButtonPressed(id, b) {
				i.gamepadButtonDurations[id][b]++
			} else {
				i.gamepadButtonDurations[id][b] = 0
			}
		}
	}
	idsToDelete := []int{}
	for id := range i.gamepadButtonDurations {
		if _, ok := i.gamepadIDs[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.gamepadButtonDurations, id)
	}

	// Touches
	ids := map[int]struct{}{}

	// Copy the touch durations.
	i.prevTouchDurations = map[int]int{}
	for id := range i.touchDurations {
		i.prevTouchDurations[id] = i.touchDurations[id]
	}

	for _, id := range ebiten.TouchIDs() {
		ids[id] = struct{}{}
		i.touchDurations[id]++
	}
	idsToDelete = []int{}
	for id := range i.touchDurations {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.touchDurations, id)
	}
}

// IsKeyJustPressed returns a boolean value indicating
// whether the given key is pressed just in the current frame.
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
	return KeyPressDuration(key) == 1
}

// IsKeyJustReleased returns a boolean value indicating
// whether the given key is released just in the current frame.
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
	theInputState.m.RLock()
	r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
	theInputState.m.RUnlock()
	return r
}

// KeyPressDuration returns how long the key is pressed in frames.
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
	theInputState.m.RLock()
	s := theInputState.keyDurations[key]
	theInputState.m.RUnlock()
	return s
}

// IsMouseButtonJustPressed returns a boolean value indicating
// whether the given mouse button is pressed just in the current frame.
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
	return MouseButtonPressDuration(button) == 1
}

// IsMouseButtonJustReleased returns a boolean value indicating
// whether the given mouse button is released just in the current frame.
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
	theInputState.m.RLock()
	r := theInputState.mouseButtonDurations[button] == 0 &&
		theInputState.prevMouseButtonDurations[button] > 0
	theInputState.m.RUnlock()
	return r
}

// MouseButtonPressDuration returns how long the mouse button is pressed in frames.
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
	theInputState.m.RLock()
	s := theInputState.mouseButtonDurations[button]
	theInputState.m.RUnlock()
	return s
}

// JustConnectedGamepadIDs returns gamepad IDs that are connected just in the current frame.
//
// JustConnectedGamepadIDs might return nil when there is no connected gamepad.
//
// JustConnectedGamepadIDs is concurrent safe.
func JustConnectedGamepadIDs() []int {
	var ids []int
	theInputState.m.RLock()
	for id := range theInputState.gamepadIDs {
		if _, ok := theInputState.prevGamepadIDs[id]; !ok {
			ids = append(ids, id)
		}
	}
	theInputState.m.RUnlock()
	sort.Ints(ids)
	return ids
}

// IsGamepadJustDisconnected returns a boolean value indicating
// whether the gamepad of the given id is released just in the current frame.
//
// IsGamepadJustDisconnected is concurrent safe.
func IsGamepadJustDisconnected(id int) bool {
	theInputState.m.RLock()
	_, prev := theInputState.prevGamepadIDs[id]
	_, current := theInputState.gamepadIDs[id]
	theInputState.m.RUnlock()
	return prev && !current
}

// IsGamepadButtonJustPressed returns a boolean value indicating
// whether the given gamepad button of the gamepad id is pressed just in the current frame.
//
// IsGamepadButtonJustPressed is concurrent safe.
func IsGamepadButtonJustPressed(id int, button ebiten.GamepadButton) bool {
	return GamepadButtonPressDuration(id, button) == 1
}

// IsGamepadButtonJustReleased returns a boolean value indicating
// whether the given gamepad button of the gamepad id is released just in the current frame.
//
// IsGamepadButtonJustReleased is concurrent safe.
func IsGamepadButtonJustReleased(id int, button ebiten.GamepadButton) bool {
	theInputState.m.RLock()
	prev := 0
	if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
		prev = theInputState.prevGamepadButtonDurations[id][button]
	}
	current := 0
	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
		current = theInputState.gamepadButtonDurations[id][button]
	}
	theInputState.m.RUnlock()
	return current == 0 && prev > 0
}

// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
//
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id int, button ebiten.GamepadButton) int {
	theInputState.m.RLock()
	s := 0
	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
		s = theInputState.gamepadButtonDurations[id][button]
	}
	theInputState.m.RUnlock()
	return s
}

// JustPressedTouchIDs returns touch IDs that are created just in the current frame.
//
// JustPressedTouchIDs might return nil when there is not touch.
//
// JustPressedTouchIDs is concurrent safe.
func JustPressedTouchIDs() []int {
	var ids []int
	theInputState.m.RLock()
	for id, s := range theInputState.touchDurations {
		if s == 1 {
			ids = append(ids, id)
		}
	}
	theInputState.m.RUnlock()
	sort.Ints(ids)
	return ids
}

// IsTouchJustReleased returns a boolean value indicating
// whether the given touch is released just in the current frame.
//
// IsTouchJustReleased is concurrent safe.
func IsTouchJustReleased(id int) bool {
	theInputState.m.RLock()
	r := theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
	theInputState.m.RUnlock()
	return r
}

// TouchPressDuration returns how long the touch remains in frames.
//
// TouchPressDuration is concurrent safe.
func TouchPressDuration(id int) int {
	theInputState.m.RLock()
	s := theInputState.touchDurations[id]
	theInputState.m.RUnlock()
	return s
}
<commit_msg>inpututil: Package inpututil is no longer experimental<commit_after>// Copyright 2018 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package inpututil provides utility functions of input like keyboard or mouse.
package inpututil

import (
	"sort"
	"sync"

	"github.com/hajimehoshi/ebiten"
	"github.com/hajimehoshi/ebiten/internal/hooks"
)

type inputState struct {
	keyDurations     map[ebiten.Key]int
	prevKeyDurations map[ebiten.Key]int

	mouseButtonDurations     map[ebiten.MouseButton]int
	prevMouseButtonDurations map[ebiten.MouseButton]int

	gamepadIDs     map[int]struct{}
	prevGamepadIDs map[int]struct{}

	gamepadButtonDurations     map[int]map[ebiten.GamepadButton]int
	prevGamepadButtonDurations map[int]map[ebiten.GamepadButton]int

	touchDurations     map[int]int
	prevTouchDurations map[int]int

	m sync.RWMutex
}

var theInputState = &inputState{
	keyDurations:     map[ebiten.Key]int{},
	prevKeyDurations: map[ebiten.Key]int{},

	mouseButtonDurations:     map[ebiten.MouseButton]int{},
	prevMouseButtonDurations: map[ebiten.MouseButton]int{},

	gamepadIDs:     map[int]struct{}{},
	prevGamepadIDs: map[int]struct{}{},

	gamepadButtonDurations:     map[int]map[ebiten.GamepadButton]int{},
	prevGamepadButtonDurations: map[int]map[ebiten.GamepadButton]int{},

	touchDurations:     map[int]int{},
	prevTouchDurations: map[int]int{},
}

func init() {
	hooks.AppendHookOnBeforeUpdate(func() error {
		theInputState.update()
		return nil
	})
}

func (i *inputState) update() {
	i.m.Lock()
	defer i.m.Unlock()

	// Keyboard
	for k := ebiten.Key(0); k <= ebiten.KeyMax; k++ {
		i.prevKeyDurations[k] = i.keyDurations[k]
		if ebiten.IsKeyPressed(k) {
			i.keyDurations[k]++
		} else {
			i.keyDurations[k] = 0
		}
	}

	// Mouse
	for _, b := range []ebiten.MouseButton{
		ebiten.MouseButtonLeft,
		ebiten.MouseButtonRight,
		ebiten.MouseButtonMiddle,
	} {
		i.prevMouseButtonDurations[b] = i.mouseButtonDurations[b]
		if ebiten.IsMouseButtonPressed(b) {
			i.mouseButtonDurations[b]++
		} else {
			i.mouseButtonDurations[b] = 0
		}
	}

	// Gamepads

	// Copy the gamepad IDs.
	i.prevGamepadIDs = map[int]struct{}{}
	for id := range i.gamepadIDs {
		i.prevGamepadIDs[id] = struct{}{}
	}

	// Copy the gamepad button durations.
	i.prevGamepadButtonDurations = map[int]map[ebiten.GamepadButton]int{}
	for id, ds := range i.gamepadButtonDurations {
		i.prevGamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
		for b, d := range ds {
			i.prevGamepadButtonDurations[id][b] = d
		}
	}

	i.gamepadIDs = map[int]struct{}{}
	for _, id := range ebiten.GamepadIDs() {
		i.gamepadIDs[id] = struct{}{}
		if _, ok := i.gamepadButtonDurations[id]; !ok {
			i.gamepadButtonDurations[id] = map[ebiten.GamepadButton]int{}
		}
		n := ebiten.GamepadButtonNum(id)
		for b := ebiten.GamepadButton(0); b < ebiten.GamepadButton(n); b++ {
			if ebiten.IsGamepadButtonPressed(id, b) {
				i.gamepadButtonDurations[id][b]++
			} else {
				i.gamepadButtonDurations[id][b] = 0
			}
		}
	}
	idsToDelete := []int{}
	for id := range i.gamepadButtonDurations {
		if _, ok := i.gamepadIDs[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.gamepadButtonDurations, id)
	}

	// Touches
	ids := map[int]struct{}{}

	// Copy the touch durations.
	i.prevTouchDurations = map[int]int{}
	for id := range i.touchDurations {
		i.prevTouchDurations[id] = i.touchDurations[id]
	}

	for _, id := range ebiten.TouchIDs() {
		ids[id] = struct{}{}
		i.touchDurations[id]++
	}
	idsToDelete = []int{}
	for id := range i.touchDurations {
		if _, ok := ids[id]; !ok {
			idsToDelete = append(idsToDelete, id)
		}
	}
	for _, id := range idsToDelete {
		delete(i.touchDurations, id)
	}
}

// IsKeyJustPressed returns a boolean value indicating
// whether the given key is pressed just in the current frame.
//
// IsKeyJustPressed is concurrent safe.
func IsKeyJustPressed(key ebiten.Key) bool {
	return KeyPressDuration(key) == 1
}

// IsKeyJustReleased returns a boolean value indicating
// whether the given key is released just in the current frame.
//
// IsKeyJustReleased is concurrent safe.
func IsKeyJustReleased(key ebiten.Key) bool {
	theInputState.m.RLock()
	r := theInputState.keyDurations[key] == 0 && theInputState.prevKeyDurations[key] > 0
	theInputState.m.RUnlock()
	return r
}

// KeyPressDuration returns how long the key is pressed in frames.
//
// KeyPressDuration is concurrent safe.
func KeyPressDuration(key ebiten.Key) int {
	theInputState.m.RLock()
	s := theInputState.keyDurations[key]
	theInputState.m.RUnlock()
	return s
}

// IsMouseButtonJustPressed returns a boolean value indicating
// whether the given mouse button is pressed just in the current frame.
//
// IsMouseButtonJustPressed is concurrent safe.
func IsMouseButtonJustPressed(button ebiten.MouseButton) bool {
	return MouseButtonPressDuration(button) == 1
}

// IsMouseButtonJustReleased returns a boolean value indicating
// whether the given mouse button is released just in the current frame.
//
// IsMouseButtonJustReleased is concurrent safe.
func IsMouseButtonJustReleased(button ebiten.MouseButton) bool {
	theInputState.m.RLock()
	r := theInputState.mouseButtonDurations[button] == 0 &&
		theInputState.prevMouseButtonDurations[button] > 0
	theInputState.m.RUnlock()
	return r
}

// MouseButtonPressDuration returns how long the mouse button is pressed in frames.
//
// MouseButtonPressDuration is concurrent safe.
func MouseButtonPressDuration(button ebiten.MouseButton) int {
	theInputState.m.RLock()
	s := theInputState.mouseButtonDurations[button]
	theInputState.m.RUnlock()
	return s
}

// JustConnectedGamepadIDs returns gamepad IDs that are connected just in the current frame.
//
// JustConnectedGamepadIDs might return nil when there is no connected gamepad.
//
// JustConnectedGamepadIDs is concurrent safe.
func JustConnectedGamepadIDs() []int {
	var ids []int
	theInputState.m.RLock()
	for id := range theInputState.gamepadIDs {
		if _, ok := theInputState.prevGamepadIDs[id]; !ok {
			ids = append(ids, id)
		}
	}
	theInputState.m.RUnlock()
	sort.Ints(ids)
	return ids
}

// IsGamepadJustDisconnected returns a boolean value indicating
// whether the gamepad of the given id is released just in the current frame.
//
// IsGamepadJustDisconnected is concurrent safe.
func IsGamepadJustDisconnected(id int) bool {
	theInputState.m.RLock()
	_, prev := theInputState.prevGamepadIDs[id]
	_, current := theInputState.gamepadIDs[id]
	theInputState.m.RUnlock()
	return prev && !current
}

// IsGamepadButtonJustPressed returns a boolean value indicating
// whether the given gamepad button of the gamepad id is pressed just in the current frame.
//
// IsGamepadButtonJustPressed is concurrent safe.
func IsGamepadButtonJustPressed(id int, button ebiten.GamepadButton) bool {
	return GamepadButtonPressDuration(id, button) == 1
}

// IsGamepadButtonJustReleased returns a boolean value indicating
// whether the given gamepad button of the gamepad id is released just in the current frame.
//
// IsGamepadButtonJustReleased is concurrent safe.
func IsGamepadButtonJustReleased(id int, button ebiten.GamepadButton) bool {
	theInputState.m.RLock()
	prev := 0
	if _, ok := theInputState.prevGamepadButtonDurations[id]; ok {
		prev = theInputState.prevGamepadButtonDurations[id][button]
	}
	current := 0
	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
		current = theInputState.gamepadButtonDurations[id][button]
	}
	theInputState.m.RUnlock()
	return current == 0 && prev > 0
}

// GamepadButtonPressDuration returns how long the gamepad button of the gamepad id is pressed in frames.
//
// GamepadButtonPressDuration is concurrent safe.
func GamepadButtonPressDuration(id int, button ebiten.GamepadButton) int {
	theInputState.m.RLock()
	s := 0
	if _, ok := theInputState.gamepadButtonDurations[id]; ok {
		s = theInputState.gamepadButtonDurations[id][button]
	}
	theInputState.m.RUnlock()
	return s
}

// JustPressedTouchIDs returns touch IDs that are created just in the current frame.
//
// JustPressedTouchIDs might return nil when there is not touch.
//
// JustPressedTouchIDs is concurrent safe.
func JustPressedTouchIDs() []int {
	var ids []int
	theInputState.m.RLock()
	for id, s := range theInputState.touchDurations {
		if s == 1 {
			ids = append(ids, id)
		}
	}
	theInputState.m.RUnlock()
	sort.Ints(ids)
	return ids
}

// IsTouchJustReleased returns a boolean value indicating
// whether the given touch is released just in the current frame.
//
// IsTouchJustReleased is concurrent safe.
func IsTouchJustReleased(id int) bool {
	theInputState.m.RLock()
	r := theInputState.touchDurations[id] == 0 && theInputState.prevTouchDurations[id] > 0
	theInputState.m.RUnlock()
	return r
}

// TouchPressDuration returns how long the touch remains in frames.
//
// TouchPressDuration is concurrent safe.
func TouchPressDuration(id int) int {
	theInputState.m.RLock()
	s := theInputState.touchDurations[id]
	theInputState.m.RUnlock()
	return s
}
<|endoftext|>