<commit_before>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package sessions provides the JSON handlers to handle connections to gonawin app.
package sessions

import (
	"errors"
	golog "log"
	"net/http"
	"net/url"

	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
	"appengine/user"

	oauth "github.com/garyburd/go-oauth/oauth"

	"github.com/taironas/gonawin/helpers"
	authhlp "github.com/taironas/gonawin/helpers/auth"
	"github.com/taironas/gonawin/helpers/log"
	"github.com/taironas/gonawin/helpers/memcache"
	templateshlp "github.com/taironas/gonawin/helpers/templates"

	gwconfig "github.com/taironas/gonawin/config"
	mdl "github.com/taironas/gonawin/models"
)

var (
	config                 *gwconfig.GwConfig
	twitterConfig          oauth.Client
	twitterCallbackURL     string
	googleVerifyTokenURL   string
	facebookVerifyTokenURL string
)

func init() {
	// read config file.
	var err error
	if config, err = gwconfig.ReadConfig(""); err != nil {
		golog.Printf("Error: unable to read config file; %v", err)
	}
	// Set up a configuration for twitter.
	twitterConfig = oauth.Client{
		Credentials:                   oauth.Credentials{Token: config.Twitter.Token, Secret: config.Twitter.Secret},
		TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token",
		ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize",
		TokenRequestURI:               "https://api.twitter.com/oauth/access_token",
	}
	twitterCallbackURL = "/j/auth/twitter/callback"
	googleVerifyTokenURL = "https://www.google.com/accounts/AuthSubTokenInfo?bearer_token"
	facebookVerifyTokenURL = "https://graph.facebook.com/me?access_token"
}

// Authenticate handler, use it to authenticate a user.
// It returns the JSON data of the requested user.
func Authenticate(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)

	userInfo := authhlp.UserInfo{ID: r.FormValue("id"), Email: r.FormValue("email"), Name: r.FormValue("name")}

	var verifyURL string
	if r.FormValue("provider") == "google" {
		verifyURL = googleVerifyTokenURL
	} else if r.FormValue("provider") == "facebook" {
		verifyURL = facebookVerifyTokenURL
	}

	if !authhlp.CheckUserValidity(r, verifyURL, r.FormValue("access_token")) {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsAccessTokenNotValid)}
	}

	var user *mdl.User
	var err error
	if user, err = mdl.SigninUser(c, "Email", userInfo.Email, userInfo.Name, userInfo.Name); err != nil {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// TwitterAuth handler, use it to authenticate via twitter.
func TwitterAuth(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Twitter Auth handler:"

	credentials, err := twitterConfig.RequestTemporaryCredentials(urlfetch.Client(c), "http://"+r.Host+twitterCallbackURL, nil)
	if err != nil {
		c.Errorf("JsonTwitterAuth, error = %v", err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetTempCredentials)}
	}

	if err = memcache.Set(c, "secret", credentials.Secret); err != nil {
		// store secret in datastore
		secretId, _, err := datastore.AllocateIDs(c, "Secret", nil, 1)
		if err != nil {
			log.Errorf(c, "%s Cannot allocate ID for secret. %v", desc, err)
		}

		key := datastore.NewKey(c, "Secret", "", secretId, nil)

		_, err = datastore.Put(c, key, credentials.Secret)
		if err != nil {
			log.Errorf(c, "%s Cannot put secret in Datastore. %v", desc, err)
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotSetSecretValue)}
		}
	}

	// return OAuth token
	oAuthToken := struct {
		OAuthToken string
	}{
		credentials.Token,
	}

	return templateshlp.RenderJson(w, c, oAuthToken)
}

// TwitterAuthCallback handler, use it to make a callback for Twitter Authentication.
func TwitterAuthCallback(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	http.Redirect(w, r, "http://"+r.Host+"/#/auth/twitter/callback?oauth_token="+r.FormValue("oauth_token")+"&oauth_verifier="+r.FormValue("oauth_verifier"), http.StatusFound)
	return nil
}

// TwitterUser handler, use it to get the Twitter user data.
func TwitterUser(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Twitter User handler:"

	var user *mdl.User

	// get the request token
	requestToken := r.FormValue("oauth_token")

	// update credentials with request token and 'secret cookie value'
	var cred oauth.Credentials
	cred.Token = requestToken
	if secret, err := memcache.Get(c, "secret"); secret != nil {
		cred.Secret = string(secret.([]byte))
	} else {
		log.Errorf(c, "%s cannot get secret value from memcache: %v", desc, err)
		// try to get secret from datastore
		q := datastore.NewQuery("Secret")
		var secrets []string
		if keys, err := q.GetAll(c, &secrets); err == nil && len(secrets) > 0 {
			// delete secret from datastore
			if err = datastore.Delete(c, keys[0]); err != nil {
				log.Errorf(c, "%s Error when trying to delete 'secret' key in Datastore: %v", desc, err)
			}

		} else if err != nil || len(secrets) == 0 {
			log.Errorf(c, "%s cannot get secret value from Datastore: %v", desc, err)
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetSecretValue)}
		}
	}

	if err := memcache.Delete(c, "secret"); err != nil {
		log.Errorf(c, "%s Error when trying to delete memcached 'secret' key: %v", desc, err)
	}

	token, values, err := twitterConfig.RequestToken(urlfetch.Client(c), &cred, r.FormValue("oauth_verifier"))
	if err != nil {
		log.Errorf(c, "%s Error when trying to delete memcached 'secret' key: %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetRequestToken)}
	}

	// get user info
	urlValues := url.Values{}
	urlValues.Set("user_id", values.Get("user_id"))
	resp, err := twitterConfig.Get(urlfetch.Client(c), token, "https://api.twitter.com/1.1/users/show.json", urlValues)
	if err != nil {
		log.Errorf(c, "%s Cannot get user info from twitter. %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetUserInfo)}
	}

	userInfo, err := authhlp.FetchTwitterUserInfo(resp)
	if err != nil {
		log.Errorf(c, "%s Cannot get user info by fetching twitter response. %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetUserInfo)}
	}

	if user, err = mdl.SigninUser(c, "Username", "", userInfo.ScreenName, userInfo.Name); err != nil {
		log.Errorf(c, "%s Unable to signin user %s. %v", desc, userInfo.Name, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// GoogleAccountsLoginURL handler, use it to get Google accounts login URL.
func GoogleAccountsLoginURL(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts Login URL Handler:"

	var url string
	var err error
	url, err = user.LoginURL(c, "/j/auth/google/callback/")
	if err != nil {
		log.Errorf(c, "%s error when getting Google accounts login URL", desc)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetGoogleLoginUrl)}
	}

	loginData := struct {
		Url string
	}{
		url,
	}

	return templateshlp.RenderJson(w, c, loginData)
}

// GoogleAuthCallback handler, use it to make a callback for Google Authentication.
func GoogleAuthCallback(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts Auth Callback Handler:"

	u := user.Current(c)
	if u == nil {
		log.Errorf(c, "%s user cannot be nil", desc)
		return &helpers.InternalServerError{Err: errors.New("user cannot be nil")}
	}

	http.Redirect(w, r, "http://"+r.Host+"/#/auth/google/callback?auth_token="+u.ID, http.StatusFound)
	return nil
}

// GoogleUser handler, use it to get Google accounts user.
func GoogleUser(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts User Handler:"

	u := user.Current(c)
	if u == nil {
		log.Errorf(c, "%s user cannot be nil", desc)
		return &helpers.InternalServerError{Err: errors.New("user cannot be nil")}
	}

	if u.ID != r.FormValue("auth_token") {
		log.Errorf(c, "%s Auth token doesn't match user ID", desc)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsAccessTokenNotValid)}
	}

	userInfo := authhlp.GetUserGoogleInfo(u)

	var user *mdl.User
	var err error
	if user, err = mdl.SigninUser(c, "Email", userInfo.Email, userInfo.Name, userInfo.Name); err != nil {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// GoogleDeleteCookie handler, use it to delete cookie created by Google account.
func GoogleDeleteCookie(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)

	cookieName := "ACSID"
	if appengine.IsDevAppServer() {
		cookieName = "dev_appserver_login"
	}
	cookie := http.Cookie{Name: cookieName, Path: "/", MaxAge: -1}
	http.SetCookie(w, &cookie)

	return templateshlp.RenderJson(w, c, "Google user has been logged out")
}

// AuthServiceIds handler, use it to get the identifiers of Gonawin
func AuthServiceIds(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}
	c := appengine.NewContext(r)

	data := struct {
		GooglePlusClientID string
		FacebookAppID      string
	}{
		config.GooglePlus.ClientID,
		config.Facebook.AppID,
	}
	return templateshlp.RenderJson(w, c, data)
}
<commit_msg>go lint controllers/sessions/sessions.go<commit_after>/*
 * Copyright (c) 2014 Santiago Arias | Remy Jourde
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

// Package sessions provides the JSON handlers to handle connections to gonawin app.
package sessions

import (
	"errors"
	golog "log"
	"net/http"
	"net/url"

	"appengine"
	"appengine/datastore"
	"appengine/urlfetch"
	"appengine/user"

	oauth "github.com/garyburd/go-oauth/oauth"

	"github.com/taironas/gonawin/helpers"
	authhlp "github.com/taironas/gonawin/helpers/auth"
	"github.com/taironas/gonawin/helpers/log"
	"github.com/taironas/gonawin/helpers/memcache"
	templateshlp "github.com/taironas/gonawin/helpers/templates"

	gwconfig "github.com/taironas/gonawin/config"
	mdl "github.com/taironas/gonawin/models"
)

var (
	config                 *gwconfig.GwConfig
	twitterConfig          oauth.Client
	twitterCallbackURL     string
	googleVerifyTokenURL   string
	facebookVerifyTokenURL string
)

func init() {
	// read config file.
	var err error
	if config, err = gwconfig.ReadConfig(""); err != nil {
		golog.Printf("Error: unable to read config file; %v", err)
	}
	// Set up a configuration for twitter.
	twitterConfig = oauth.Client{
		Credentials:                   oauth.Credentials{Token: config.Twitter.Token, Secret: config.Twitter.Secret},
		TemporaryCredentialRequestURI: "https://api.twitter.com/oauth/request_token",
		ResourceOwnerAuthorizationURI: "https://api.twitter.com/oauth/authorize",
		TokenRequestURI:               "https://api.twitter.com/oauth/access_token",
	}
	twitterCallbackURL = "/j/auth/twitter/callback"
	googleVerifyTokenURL = "https://www.google.com/accounts/AuthSubTokenInfo?bearer_token"
	facebookVerifyTokenURL = "https://graph.facebook.com/me?access_token"
}

// Authenticate handler, use it to authenticate a user.
// It returns the JSON data of the requested user.
func Authenticate(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)

	userInfo := authhlp.UserInfo{ID: r.FormValue("id"), Email: r.FormValue("email"), Name: r.FormValue("name")}

	var verifyURL string
	if r.FormValue("provider") == "google" {
		verifyURL = googleVerifyTokenURL
	} else if r.FormValue("provider") == "facebook" {
		verifyURL = facebookVerifyTokenURL
	}

	if !authhlp.CheckUserValidity(r, verifyURL, r.FormValue("access_token")) {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsAccessTokenNotValid)}
	}

	var user *mdl.User
	var err error
	if user, err = mdl.SigninUser(c, "Email", userInfo.Email, userInfo.Name, userInfo.Name); err != nil {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// TwitterAuth handler, use it to authenticate via twitter.
func TwitterAuth(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Twitter Auth handler:"

	credentials, err := twitterConfig.RequestTemporaryCredentials(urlfetch.Client(c), "http://"+r.Host+twitterCallbackURL, nil)
	if err != nil {
		c.Errorf("JsonTwitterAuth, error = %v", err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetTempCredentials)}
	}

	if err = memcache.Set(c, "secret", credentials.Secret); err != nil {
		// store secret in datastore
		secretID, _, err := datastore.AllocateIDs(c, "Secret", nil, 1)
		if err != nil {
			log.Errorf(c, "%s Cannot allocate ID for secret. %v", desc, err)
		}

		key := datastore.NewKey(c, "Secret", "", secretID, nil)

		_, err = datastore.Put(c, key, credentials.Secret)
		if err != nil {
			log.Errorf(c, "%s Cannot put secret in Datastore. %v", desc, err)
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotSetSecretValue)}
		}
	}

	// return OAuth token
	oAuthToken := struct {
		OAuthToken string
	}{
		credentials.Token,
	}

	return templateshlp.RenderJson(w, c, oAuthToken)
}

// TwitterAuthCallback handler, use it to make a callback for Twitter Authentication.
func TwitterAuthCallback(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	http.Redirect(w, r, "http://"+r.Host+"/#/auth/twitter/callback?oauth_token="+r.FormValue("oauth_token")+"&oauth_verifier="+r.FormValue("oauth_verifier"), http.StatusFound)
	return nil
}

// TwitterUser handler, use it to get the Twitter user data.
func TwitterUser(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Twitter User handler:"

	var user *mdl.User

	// get the request token
	requestToken := r.FormValue("oauth_token")

	// update credentials with request token and 'secret cookie value'
	var cred oauth.Credentials
	cred.Token = requestToken
	if secret, err := memcache.Get(c, "secret"); secret != nil {
		cred.Secret = string(secret.([]byte))
	} else {
		log.Errorf(c, "%s cannot get secret value from memcache: %v", desc, err)
		// try to get secret from datastore
		q := datastore.NewQuery("Secret")
		var secrets []string
		if keys, err := q.GetAll(c, &secrets); err == nil && len(secrets) > 0 {
			// delete secret from datastore
			if err = datastore.Delete(c, keys[0]); err != nil {
				log.Errorf(c, "%s Error when trying to delete 'secret' key in Datastore: %v", desc, err)
			}

		} else if err != nil || len(secrets) == 0 {
			log.Errorf(c, "%s cannot get secret value from Datastore: %v", desc, err)
			return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetSecretValue)}
		}
	}

	if err := memcache.Delete(c, "secret"); err != nil {
		log.Errorf(c, "%s Error when trying to delete memcached 'secret' key: %v", desc, err)
	}

	token, values, err := twitterConfig.RequestToken(urlfetch.Client(c), &cred, r.FormValue("oauth_verifier"))
	if err != nil {
		log.Errorf(c, "%s Error when trying to delete memcached 'secret' key: %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetRequestToken)}
	}

	// get user info
	urlValues := url.Values{}
	urlValues.Set("user_id", values.Get("user_id"))
	resp, err := twitterConfig.Get(urlfetch.Client(c), token, "https://api.twitter.com/1.1/users/show.json", urlValues)
	if err != nil {
		log.Errorf(c, "%s Cannot get user info from twitter. %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetUserInfo)}
	}

	userInfo, err := authhlp.FetchTwitterUserInfo(resp)
	if err != nil {
		log.Errorf(c, "%s Cannot get user info by fetching twitter response. %v", desc, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetUserInfo)}
	}

	if user, err = mdl.SigninUser(c, "Username", "", userInfo.ScreenName, userInfo.Name); err != nil {
		log.Errorf(c, "%s Unable to signin user %s. %v", desc, userInfo.Name, err)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// GoogleAccountsLoginURL handler, use it to get Google accounts login URL.
func GoogleAccountsLoginURL(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts Login URL Handler:"

	var url string
	var err error
	url, err = user.LoginURL(c, "/j/auth/google/callback/")
	if err != nil {
		log.Errorf(c, "%s error when getting Google accounts login URL", desc)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsCannotGetGoogleLoginUrl)}
	}

	loginData := struct {
		URL string
	}{
		url,
	}

	return templateshlp.RenderJson(w, c, loginData)
}

// GoogleAuthCallback handler, use it to make a callback for Google Authentication.
func GoogleAuthCallback(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts Auth Callback Handler:"

	u := user.Current(c)
	if u == nil {
		log.Errorf(c, "%s user cannot be nil", desc)
		return &helpers.InternalServerError{Err: errors.New("user cannot be nil")}
	}

	http.Redirect(w, r, "http://"+r.Host+"/#/auth/google/callback?auth_token="+u.ID, http.StatusFound)
	return nil
}

// GoogleUser handler, use it to get Google accounts user.
func GoogleUser(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)
	desc := "Google Accounts User Handler:"

	u := user.Current(c)
	if u == nil {
		log.Errorf(c, "%s user cannot be nil", desc)
		return &helpers.InternalServerError{Err: errors.New("user cannot be nil")}
	}

	if u.ID != r.FormValue("auth_token") {
		log.Errorf(c, "%s Auth token doesn't match user ID", desc)
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsAccessTokenNotValid)}
	}

	userInfo := authhlp.GetUserGoogleInfo(u)

	var user *mdl.User
	var err error
	if user, err = mdl.SigninUser(c, "Email", userInfo.Email, userInfo.Name, userInfo.Name); err != nil {
		return &helpers.InternalServerError{Err: errors.New(helpers.ErrorCodeSessionsUnableToSignin)}
	}

	imageURL := helpers.UserImageURL(user.Username, user.Id)

	userData := struct {
		User     *mdl.User
		ImageURL string
	}{
		user,
		imageURL,
	}

	return templateshlp.RenderJson(w, c, userData)
}

// GoogleDeleteCookie handler, use it to delete cookie created by Google account.
func GoogleDeleteCookie(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}

	c := appengine.NewContext(r)

	cookieName := "ACSID"
	if appengine.IsDevAppServer() {
		cookieName = "dev_appserver_login"
	}
	cookie := http.Cookie{Name: cookieName, Path: "/", MaxAge: -1}
	http.SetCookie(w, &cookie)

	return templateshlp.RenderJson(w, c, "Google user has been logged out")
}

// AuthServiceIds handler, use it to get the identifiers of Gonawin
func AuthServiceIds(w http.ResponseWriter, r *http.Request) error {
	if r.Method != "GET" {
		return &helpers.BadRequest{Err: errors.New(helpers.ErrorCodeNotSupported)}
	}
	c := appengine.NewContext(r)

	data := struct {
		GooglePlusClientID string
		FacebookAppID      string
	}{
		config.GooglePlus.ClientID,
		config.Facebook.AppID,
	}
	return templateshlp.RenderJson(w, c, data)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	openrtb "gopkg.in/bsm/openrtb.v2"
)

//OutputPerSeconds = number of seconds between stat output
var OutputPerSeconds = 10

//Wins = counter for # of wins in the last OutputPerSeconds
var Wins = 0

//Events = counter for # of events in the last OutputPerSeconds
var Events = 0

//Bids = counter for # of bids in the last OutputPerSeconds
var Bids = 0

var outputChannel = make(chan bool)

// Creative ...
type Creative struct {
	Format         string           `json:"format"`
	ID             int              `json:"id"`
	UUID           string           `json:"uuid,omitempty"`
	Name           string           `json:"name"`
	ProviderConfig *json.RawMessage `json:"providerConfig"`
}

// AgentConfig This is the agent configuration that will be sent to RTBKIT's ACS
type AgentConfig struct {
	// We use `RawMessage` for Augmentations and BidcControl, because we
	// don't need it, we just cache it.
	Account            []string         `json:"account"`
	Augmentations      *json.RawMessage `json:"augmentations"`
	BidControl         *json.RawMessage `json:"bidControl"`
	BidProbability     float64          `json:"bidProbability"`
	Creatives          []Creative       `json:"creatives"`
	ErrorFormat        string           `json:"errorFormat"`
	External           bool             `json:"external"`
	ExternalId         int              `json:"externalId"`
	LossFormat         string           `json:"lossFormat"`
	MinTimeAvailableMs float64          `json:"minTimeAvailableMs"`
	ProviderConfig     *json.RawMessage `json:"providerConfig"`
	WinFormat          string           `json:"winFormat"`
	BidderInterface    string           `json:"bidderInterface"`
}

// Agent This represents a RTBKIT Agent
type Agent struct {
	Name   string      `json:"name"`
	Config AgentConfig `json:"config"`

	// This is the price the agent will pay per impression. "Fixed price bidder".
	Price float64 `json:"price"`

	// For pacing the budgeting
	Period  int `json:"period"`
	Balance int `json:"balance"`

	// private state of each agent
	registered bool      // did we register the configuration in the ACS?
	pacer      chan bool // go routine updating balance in the banker
	bidID      int       // unique id for response
}

type creativesKey struct {
	// This is used to make a mapping between an impression and the
	// external-id of an agent to the creatives that can be sent to the
	// exchange for that impression.
	ImpID string
	ExtID int
}

// RegisterAgent in the ACS sending a HTTP request to the service on `acsIp`:`acsPort`
func (agent *Agent) RegisterAgent(httpClient *http.Client, acsIP string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIP, acsPort, agent.Name)
	body, _ := json.Marshal(agent.Config)
	reader := bytes.NewReader(body)
	req, _ := http.NewRequest("POST", url, reader)
	req.Header.Add("Accept", "application/json")
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("ACS registration failed with %s\n", err)
		return
	}
	agent.registered = true
	res.Body.Close()
}

// UnregisterAgent Removes the agent configuration from the ACS
func (agent *Agent) UnregisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	req, _ := http.NewRequest("DELETE", url, bytes.NewBufferString(""))
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("Unregister failed with %s\n", err)
		return
	}
	agent.registered = false
	res.Body.Close()
}

// StartPacer Starts a go routine which periodically updates the balance on the agents account.
func (agent *Agent) StartPacer(
	httpClient *http.Client, bankerIP string, bankerPort int) {

	accounts := agent.Config.Account

	url := fmt.Sprintf("http://%s:%d/v1/accounts/%s/balance",
		bankerIP, bankerPort, strings.Join(accounts, ":"))
	body := fmt.Sprintf("{\"USD/1M\": %d}", agent.Balance)
	ticker := time.NewTicker(time.Duration(agent.Period) * time.Millisecond)
	agent.pacer = make(chan bool)

	go func() {
		for {
			select {
			case <-ticker.C:
				// make this a new go routine?
				go func() {
					log.Println("Pacing...")
					req, _ := http.NewRequest("POST", url, strings.NewReader(body))
					req.Header.Add("Accept", "application/json")
					res, err := httpClient.Do(req)
					if err != nil {
						log.Printf("Balance failed with %s\n", err)
						return
					}
					res.Body.Close()
				}()
			case <-agent.pacer:
				ticker.Stop()
				return
			}
		}
	}()
}

// StartStatOutput is responsible for displaying the number of wins and events per timer tick
func StartStatOutput() {
	tickerChannel := time.NewTicker(time.Second * time.Duration(OutputPerSeconds)).C

	go func() {
		for {
			select {
			case <-tickerChannel:
				printStats()
			case <-outputChannel:
				return
			}
		}
	}()
}

// BidWin ...
func BidWin() {
	Wins++
}

// BidEvent ...
func BidEvent() {
	Events++
}

// BidIncoming ...
func BidIncoming() {
	Bids++
}

func printStats() {
	tempWins := Wins
	Wins = 0
	winsPerSecond := tempWins / OutputPerSeconds
	tempEvents := Events
	Events = 0
	eventsPerSecond := tempEvents / OutputPerSeconds
	tempBids := Bids
	Bids = 0
	bidsPerSecond := tempBids / OutputPerSeconds
	log.Println("***********************")
	log.Printf("Bids: %d (%d/second)", tempBids, bidsPerSecond)
	log.Printf("Wins: %d (%d/second)", tempWins, winsPerSecond)
	log.Printf("Events: %d (%d/second)", tempEvents, eventsPerSecond)
}

// StopPacer Stops the go routine updating the bank balance.
func (agent *Agent) StopPacer() {
	close(outputChannel)
	close(agent.pacer)
}

// DoBid Adds to the bid response the bid by the agent. The Bid is added to
// the only seat of the response. It picks a random creative from
// the list of creatives from the `Agent.Config.Creative` and places it
// in the bid.
func (agent *Agent) DoBid(
	req *openrtb.BidRequest, res *openrtb.BidResponse, ids map[creativesKey]interface{}) (*openrtb.BidResponse, bool) {

	for _, imp := range req.Imp {
		key := creativesKey{ImpID: imp.ID, ExtID: agent.Config.ExternalId}
		if ids[key] == nil {
			continue
		}

		creativeList := ids[key].([]interface{})
		// pick a random creative
		n := rand.Intn(len(creativeList))

		// Extract a usable creative ID from the JSON parse
		crid := strconv.Itoa(int(creativeList[n].(float64)))

		bidID := strconv.Itoa(agent.bidID)

		price := float64(imp.BidFloor * 1.25)

		ext := map[string]interface{}{"priority": 1.0, "external-id": agent.Config.ExternalId}
		jsonExt, _ := json.Marshal(ext)
		bid := openrtb.Bid{ID: bidID, ImpID: imp.ID, CreativeID: crid, Price: price * rand.Float64(), Ext: jsonExt}
		agent.bidID++
		res.SeatBid[0].Bid = append(res.SeatBid[0].Bid, bid)
	}
	return res, len(res.SeatBid[0].Bid) > 0
}

func externalIdsFromRequest(req *openrtb.BidRequest) map[creativesKey]interface{} {
	// This function makes a mappping with a range of type (Impression Id, External Id)
	// to a slice of "creative indexes" (See the agent configuration "creative").
	// We use this auxiliary function in `DoBid` to match the `BidRequest` to the
	// creatives of the agent and create a response.
	ids := make(map[creativesKey]interface{})

	for _, imp := range req.Imp {
		var extJSON map[string]interface{}
		_ = json.Unmarshal(imp.Ext, &extJSON)

		for _, extID := range extJSON["external-ids"].([]interface{}) {
			extID = int(extID.(float64))
			key := creativesKey{ImpID: imp.ID, ExtID: extID.(int)}
			creatives := (extJSON["creative-ids"].(map[string]interface{}))[strconv.Itoa(extID.(int))]
			ids[key] = creatives.(interface{})
		}
	}
	return ids
}

func emptyResponseWithOneSeat(req *openrtb.BidRequest) *openrtb.BidResponse {
	// This function adds a Seat to the Response.
	// Seat: A buyer entity that uses a Bidder to obtain impressions on its behalf.
	seat := openrtb.SeatBid{Bid: make([]openrtb.Bid, 0)}
	seatbid := []openrtb.SeatBid{seat}
	res := &openrtb.BidResponse{ID: req.ID, SeatBid: seatbid}
	return res
}

// LoadAgent Parse a JSON file and return an Agent.
func LoadAgent(filepath string) (Agent, error) {
	var agent Agent
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return Agent{}, err
	}
	err = json.Unmarshal(data, &agent)
	if err != nil {
		return Agent{}, err
	}
	return agent, nil
}

func loadAgents(data []byte) ([]Agent, error) {
	type Agents []Agent
	var agents Agents

	err := json.Unmarshal(data, &agents)
	if err != nil {
		return nil, err
	}
	return agents, nil
}

// LoadAgentsFromFile Parse a JSON file and return a list of Agents.
func LoadAgentsFromFile(filepath string) ([]Agent, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	return loadAgents(data)
}

func loadAgentsFromString(agentsString string) ([]Agent, error) {
	return loadAgents([]byte(agentsString))
}

// FindCreativeIndexFromID takes a creative ID and an AgentConfig,
// returning a creative index usable by the RTBKit router
func FindCreativeIndexFromID(crid int, agent AgentConfig) (string, error) {
	for creativeIndex, creative := range agent.Creatives {
		if creative.ID == crid {
			return strconv.Itoa(creativeIndex), nil
		}
	}
	return "", errors.New("Unable to find matching creative")
}
<commit_msg>removing rand floor<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"strconv"
	"strings"
	"time"

	openrtb "gopkg.in/bsm/openrtb.v2"
)

//OutputPerSeconds = number of seconds between stat output
var OutputPerSeconds = 10

//Wins = counter for # of wins in the last OutputPerSeconds
var Wins = 0

//Events = counter for # of events in the last OutputPerSeconds
var Events = 0

//Bids = counter for # of bids in the last OutputPerSeconds
var Bids = 0

var outputChannel = make(chan bool)

// Creative ...
type Creative struct {
	Format         string           `json:"format"`
	ID             int              `json:"id"`
	UUID           string           `json:"uuid,omitempty"`
	Name           string           `json:"name"`
	ProviderConfig *json.RawMessage `json:"providerConfig"`
}

// AgentConfig This is the agent configuration that will be sent to RTBKIT's ACS
type AgentConfig struct {
	// We use `RawMessage` for Augmentations and BidcControl, because we
	// don't need it, we just cache it.
	Account            []string         `json:"account"`
	Augmentations      *json.RawMessage `json:"augmentations"`
	BidControl         *json.RawMessage `json:"bidControl"`
	BidProbability     float64          `json:"bidProbability"`
	Creatives          []Creative       `json:"creatives"`
	ErrorFormat        string           `json:"errorFormat"`
	External           bool             `json:"external"`
	ExternalId         int              `json:"externalId"`
	LossFormat         string           `json:"lossFormat"`
	MinTimeAvailableMs float64          `json:"minTimeAvailableMs"`
	ProviderConfig     *json.RawMessage `json:"providerConfig"`
	WinFormat          string           `json:"winFormat"`
	BidderInterface    string           `json:"bidderInterface"`
}

// Agent This represents a RTBKIT Agent
type Agent struct {
	Name   string      `json:"name"`
	Config AgentConfig `json:"config"`

	// This is the price the agent will pay per impression. "Fixed price bidder".
	Price float64 `json:"price"`

	// For pacing the budgeting
	Period  int `json:"period"`
	Balance int `json:"balance"`

	// private state of each agent
	registered bool      // did we register the configuration in the ACS?
	pacer      chan bool // go routine updating balance in the banker
	bidID      int       // unique id for response
}

type creativesKey struct {
	// This is used to make a mapping between an impression and the
	// external-id of an agent to the creatives that can be sent to the
	// exchange for that impression.
	ImpID string
	ExtID int
}

// RegisterAgent in the ACS sending a HTTP request to the service on `acsIp`:`acsPort`
func (agent *Agent) RegisterAgent(httpClient *http.Client, acsIP string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIP, acsPort, agent.Name)
	body, _ := json.Marshal(agent.Config)
	reader := bytes.NewReader(body)
	req, _ := http.NewRequest("POST", url, reader)
	req.Header.Add("Accept", "application/json")
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("ACS registration failed with %s\n", err)
		return
	}
	agent.registered = true
	res.Body.Close()
}

// UnregisterAgent Removes the agent configuration from the ACS
func (agent *Agent) UnregisterAgent(
	httpClient *http.Client, acsIp string, acsPort int) {
	url := fmt.Sprintf("http://%s:%d/v1/agents/%s/config", acsIp, acsPort, agent.Name)
	req, _ := http.NewRequest("DELETE", url, bytes.NewBufferString(""))
	res, err := httpClient.Do(req)
	if err != nil {
		log.Printf("Unregister failed with %s\n", err)
		return
	}
	agent.registered = false
	res.Body.Close()
}

// StartPacer Starts a go routine which periodically updates the balance on the agents account.
func (agent *Agent) StartPacer(
	httpClient *http.Client, bankerIP string, bankerPort int) {

	accounts := agent.Config.Account

	url := fmt.Sprintf("http://%s:%d/v1/accounts/%s/balance",
		bankerIP, bankerPort, strings.Join(accounts, ":"))
	body := fmt.Sprintf("{\"USD/1M\": %d}", agent.Balance)
	ticker := time.NewTicker(time.Duration(agent.Period) * time.Millisecond)
	agent.pacer = make(chan bool)

	go func() {
		for {
			select {
			case <-ticker.C:
				// make this a new go routine?
				go func() {
					log.Println("Pacing...")
					req, _ := http.NewRequest("POST", url, strings.NewReader(body))
					req.Header.Add("Accept", "application/json")
					res, err := httpClient.Do(req)
					if err != nil {
						log.Printf("Balance failed with %s\n", err)
						return
					}
					res.Body.Close()
				}()
			case <-agent.pacer:
				ticker.Stop()
				return
			}
		}
	}()
}

// StartStatOutput is responsible for displaying the number of wins and events per timer tick
func StartStatOutput() {
	tickerChannel := time.NewTicker(time.Second * time.Duration(OutputPerSeconds)).C

	go func() {
		for {
			select {
			case <-tickerChannel:
				printStats()
			case <-outputChannel:
				return
			}
		}
	}()
}

// BidWin ...
func BidWin() {
	Wins++
}

// BidEvent ...
func BidEvent() {
	Events++
}

// BidIncoming ...
func BidIncoming() {
	Bids++
}

func printStats() {
	tempWins := Wins
	Wins = 0
	winsPerSecond := tempWins / OutputPerSeconds
	tempEvents := Events
	Events = 0
	eventsPerSecond := tempEvents / OutputPerSeconds
	tempBids := Bids
	Bids = 0
	bidsPerSecond := tempBids / OutputPerSeconds
	log.Println("***********************")
	log.Printf("Bids: %d (%d/second)", tempBids, bidsPerSecond)
	log.Printf("Wins: %d (%d/second)", tempWins, winsPerSecond)
	log.Printf("Events: %d (%d/second)", tempEvents, eventsPerSecond)
}

// StopPacer Stops the go routine updating the bank balance.
func (agent *Agent) StopPacer() {
	close(outputChannel)
	close(agent.pacer)
}

// DoBid Adds to the bid response the bid by the agent. The Bid is added to
// the only seat of the response. It picks a random creative from
// the list of creatives from the `Agent.Config.Creative` and places it
// in the bid.
func (agent *Agent) DoBid(
	req *openrtb.BidRequest, res *openrtb.BidResponse, ids map[creativesKey]interface{}) (*openrtb.BidResponse, bool) {

	for _, imp := range req.Imp {
		key := creativesKey{ImpID: imp.ID, ExtID: agent.Config.ExternalId}
		if ids[key] == nil {
			continue
		}

		creativeList := ids[key].([]interface{})
		// pick a random creative
		n := rand.Intn(len(creativeList))

		// Extract a usable creative ID from the JSON parse
		crid := strconv.Itoa(int(creativeList[n].(float64)))

		bidID := strconv.Itoa(agent.bidID)

		price := float64(imp.BidFloor * 1.25)

		ext := map[string]interface{}{"priority": 1.0, "external-id": agent.Config.ExternalId}
		jsonExt, _ := json.Marshal(ext)
		bid := openrtb.Bid{ID: bidID, ImpID: imp.ID, CreativeID: crid, Price: price, Ext: jsonExt}
		agent.bidID++
		res.SeatBid[0].Bid = append(res.SeatBid[0].Bid, bid)
	}
	return res, len(res.SeatBid[0].Bid) > 0
}

func externalIdsFromRequest(req *openrtb.BidRequest) map[creativesKey]interface{} {
	// This function makes a mappping with a range of type (Impression Id, External Id)
	// to a slice of "creative indexes" (See the agent configuration "creative").
	// We use this auxiliary function in `DoBid` to match the `BidRequest` to the
	// creatives of the agent and create a response.
	ids := make(map[creativesKey]interface{})

	for _, imp := range req.Imp {
		var extJSON map[string]interface{}
		_ = json.Unmarshal(imp.Ext, &extJSON)

		for _, extID := range extJSON["external-ids"].([]interface{}) {
			extID = int(extID.(float64))
			key := creativesKey{ImpID: imp.ID, ExtID: extID.(int)}
			creatives := (extJSON["creative-ids"].(map[string]interface{}))[strconv.Itoa(extID.(int))]
			ids[key] = creatives.(interface{})
		}
	}
	return ids
}

func emptyResponseWithOneSeat(req *openrtb.BidRequest) *openrtb.BidResponse {
	// This function adds a Seat to the Response.
	// Seat: A buyer entity that uses a Bidder to obtain impressions on its behalf.
	seat := openrtb.SeatBid{Bid: make([]openrtb.Bid, 0)}
	seatbid := []openrtb.SeatBid{seat}
	res := &openrtb.BidResponse{ID: req.ID, SeatBid: seatbid}
	return res
}

// LoadAgent Parse a JSON file and return an Agent.
func LoadAgent(filepath string) (Agent, error) {
	var agent Agent
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return Agent{}, err
	}
	err = json.Unmarshal(data, &agent)
	if err != nil {
		return Agent{}, err
	}
	return agent, nil
}

func loadAgents(data []byte) ([]Agent, error) {
	type Agents []Agent
	var agents Agents

	err := json.Unmarshal(data, &agents)
	if err != nil {
		return nil, err
	}
	return agents, nil
}

// LoadAgentsFromFile Parse a JSON file and return a list of Agents.
func LoadAgentsFromFile(filepath string) ([]Agent, error) {
	data, err := ioutil.ReadFile(filepath)
	if err != nil {
		return nil, err
	}
	return loadAgents(data)
}

func loadAgentsFromString(agentsString string) ([]Agent, error) {
	return loadAgents([]byte(agentsString))
}

// FindCreativeIndexFromID takes a creative ID and an AgentConfig,
// returning a creative index usable by the RTBKit router
func FindCreativeIndexFromID(crid int, agent AgentConfig) (string, error) {
	for creativeIndex, creative := range agent.Creatives {
		if creative.ID == crid {
			return strconv.Itoa(creativeIndex), nil
		}
	}
	return "", errors.New("Unable to find matching creative")
}
<|endoftext|>


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

package modules

import (
	"bytes"
	"log"
	"strings"
	"text/template"

	"github.com/muesli/beehive/filters"
)

// An element in a Chain
type ChainElement struct {
	Action Action
	Filter Filter
}

// A user defined Chain
type Chain struct {
	Name        string
	Description string
	Event       *Event
	Elements    []ChainElement
}

// Execute chains for an event we received.
func execChains(event *Event) {
	for _, c := range chains {
		if c.Event.Name != event.Name || c.Event.Bee != event.Bee {
			continue
		}

		log.Println("Executing chain:", c.Name, "-", c.Description)
		for _, el := range c.Elements {
			m := make(map[string]interface{})
			for _, opt := range event.Options {
				m[opt.Name] = opt.Value
			}

			if el.Filter.Name != "" {
				filter := *filters.GetFilter(el.Filter.Name)
				passes := true

				log.Println("\tExecuting filter:", filter.Name(), "-", filter.Description())
				for _, opt := range el.Filter.Options {
					log.Println("\t\tOptions:", opt)
					origVal := m[opt.Name]
					cleanVal := opt.Value
					if opt.Trimmed {
						switch v := origVal.(type) {
						case string:
							origVal = strings.TrimSpace(v)
						}
						switch v := cleanVal.(type) {
						case string:
							cleanVal = strings.TrimSpace(v)
						}
					}
					if opt.CaseInsensitive {
						switch v := origVal.(type) {
						case string:
							origVal = strings.ToLower(v)
						}
						switch v := cleanVal.(type) {
						case string:
							cleanVal = strings.ToLower(v)
						}
					}

					if filter.Passes(origVal, cleanVal) == opt.Inverse {
						log.Println("\t\tDid not pass filter!")
						passes = false
						break
					}
				}

				if !passes {
					break
				}
				log.Println("\t\tPassed filter!")
			}
			if el.Action.Name != "" {
				action := Action{
					Bee:  el.Action.Bee,
					Name: el.Action.Name,
				}

				for _, opt := range el.Action.Options {
					var value bytes.Buffer
					tmpl, err := template.New(el.Action.Bee + "_" + el.Action.Name + "_" + opt.Name).Parse(opt.Value.(string))
					if err == nil {
						err = tmpl.Execute(&value, m)
					}
					if err != nil {
						panic(err)
					}

					ph := Placeholder{
						Name:  opt.Name,
						Type:  "string", //FIXME
						Value: value.String(),
					}
					action.Options = append(action.Options, ph)
				}

				log.Println("\tExecuting action:", action.Bee, "/", action.Name, "-", GetActionDescriptor(&action).Description)
				for _, v := range action.Options {
					log.Println("\t\tOptions:", v)
				}
				(*GetModule(action.Bee)).Action(action)
			}
		}
	}
}
<commit_msg>* Only strings can be templates.<commit_after>/*
 *    Copyright (C) 2014 Christian Muehlhaeuser
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as published
 *    by the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *    Authors:
 *      Christian Muehlhaeuser <muesli@gmail.com>
 */

package modules

import (
	"bytes"
	"log"
	"strings"
	"text/template"

	"github.com/muesli/beehive/filters"
)

// An element in a Chain
type ChainElement struct {
	Action Action
	Filter Filter
}

// A user defined Chain
type Chain struct {
	Name        string
	Description string
	Event       *Event
	Elements    []ChainElement
}

// Execute chains for an event we received.
func execChains(event *Event) {
	for _, c := range chains {
		if c.Event.Name != event.Name || c.Event.Bee != event.Bee {
			continue
		}

		log.Println("Executing chain:", c.Name, "-", c.Description)
		for _, el := range c.Elements {
			m := make(map[string]interface{})
			for _, opt := range event.Options {
				m[opt.Name] = opt.Value
			}

			if el.Filter.Name != "" {
				filter := *filters.GetFilter(el.Filter.Name)
				passes := true

				log.Println("\tExecuting filter:", filter.Name(), "-", filter.Description())
				for _, opt := range el.Filter.Options {
					log.Println("\t\tOptions:", opt)
					origVal := m[opt.Name]
					cleanVal := opt.Value
					if opt.Trimmed {
						switch v := origVal.(type) {
						case string:
							origVal = strings.TrimSpace(v)
						}
						switch v := cleanVal.(type) {
						case string:
							cleanVal = strings.TrimSpace(v)
						}
					}
					if opt.CaseInsensitive {
						switch v := origVal.(type) {
						case string:
							origVal = strings.ToLower(v)
						}
						switch v := cleanVal.(type) {
						case string:
							cleanVal = strings.ToLower(v)
						}
					}

					if filter.Passes(origVal, cleanVal) == opt.Inverse {
						log.Println("\t\tDid not pass filter!")
						passes = false
						break
					}
				}

				if !passes {
					break
				}
				log.Println("\t\tPassed filter!")
			}
			if el.Action.Name != "" {
				action := Action{
					Bee:  el.Action.Bee,
					Name: el.Action.Name,
				}

				for _, opt := range el.Action.Options {
					ph := Placeholder{
						Name:  opt.Name,
					}

					switch opt.Value.(type) {
						case string:
							var value bytes.Buffer
							tmpl, err := template.New(el.Action.Bee + "_" + el.Action.Name + "_" + opt.Name).Parse(opt.Value.(string))
							if err == nil {
								err = tmpl.Execute(&value, m)
							}
							if err != nil {
								panic(err)
							}

							ph.Type = "string" //FIXME
							ph.Value = value.String()

						default:
							ph.Type = opt.Type
							ph.Value = opt.Value
					}
					action.Options = append(action.Options, ph)
				}

				log.Println("\tExecuting action:", action.Bee, "/", action.Name, "-", GetActionDescriptor(&action).Description)
				for _, v := range action.Options {
					log.Println("\t\tOptions:", v)
				}
				(*GetModule(action.Bee)).Action(action)
			}
		}
	}
}
<|endoftext|>


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

package modules

import (
	"fmt"
	"path"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"github.com/gohugoio/hugo/common/hugo"

	"github.com/gohugoio/hugo/config"
	"github.com/gohugoio/hugo/hugofs/files"
	"github.com/gohugoio/hugo/langs"
	"github.com/mitchellh/mapstructure"
)

var DefaultModuleConfig = Config{

	// Default to direct, which means "git clone" and similar. We
	// will investigate proxy settings in more depth later.
	// See https://github.com/golang/go/issues/26334
	Proxy: "direct",

	// Comma separated glob list matching paths that should not use the
	// proxy configured above.
	NoProxy: "none",

	// Comma separated glob list matching paths that should be
	// treated as private.
	Private: "*.*",

	// A list of replacement directives mapping a module path to a directory
	// or a theme component in the themes folder.
	// Note that this will turn the component into a traditional theme component
	// that does not partake in vendoring etc.
	// The syntax is the similar to the replacement directives used in go.mod, e.g:
	//    github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2
	Replacements: nil,
}

// ApplyProjectConfigDefaults applies default/missing module configuration for
// the main project.
func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
	moda := mod.(*moduleAdapter)

	// Map legacy directory config into the new module.
	languages := cfg.Get("languagesSortedDefaultFirst").(langs.Languages)
	isMultiHost := languages.IsMultihost()

	// To bridge between old and new configuration format we need
	// a way to make sure all of the core components are configured on
	// the basic level.
	componentsConfigured := make(map[string]bool)
	for _, mnt := range moda.mounts {
		if !strings.HasPrefix(mnt.Target, files.JsConfigFolderMountPrefix) {
			componentsConfigured[mnt.Component()] = true
		}
	}

	type dirKeyComponent struct {
		key          string
		component    string
		multilingual bool
	}

	dirKeys := []dirKeyComponent{
		{"contentDir", files.ComponentFolderContent, true},
		{"dataDir", files.ComponentFolderData, false},
		{"layoutDir", files.ComponentFolderLayouts, false},
		{"i18nDir", files.ComponentFolderI18n, false},
		{"archetypeDir", files.ComponentFolderArchetypes, false},
		{"assetDir", files.ComponentFolderAssets, false},
		{"", files.ComponentFolderStatic, isMultiHost},
	}

	createMountsFor := func(d dirKeyComponent, cfg config.Provider) []Mount {
		var lang string
		if language, ok := cfg.(*langs.Language); ok {
			lang = language.Lang
		}

		// Static mounts are a little special.
		if d.component == files.ComponentFolderStatic {
			var mounts []Mount
			staticDirs := getStaticDirs(cfg)
			if len(staticDirs) > 0 {
				componentsConfigured[d.component] = true
			}

			for _, dir := range staticDirs {
				mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: d.component})
			}

			return mounts

		}

		if cfg.IsSet(d.key) {
			source := cfg.GetString(d.key)
			componentsConfigured[d.component] = true

			return []Mount{{
				// No lang set for layouts etc.
				Source: source,
				Target: d.component,
			}}
		}

		return nil
	}

	createMounts := func(d dirKeyComponent) []Mount {
		var mounts []Mount
		if d.multilingual {
			if d.component == files.ComponentFolderContent {
				seen := make(map[string]bool)
				hasContentDir := false
				for _, language := range languages {
					if language.ContentDir != "" {
						hasContentDir = true
						break
					}
				}

				if hasContentDir {
					for _, language := range languages {
						contentDir := language.ContentDir
						if contentDir == "" {
							contentDir = files.ComponentFolderContent
						}
						if contentDir == "" || seen[contentDir] {
							continue
						}
						seen[contentDir] = true
						mounts = append(mounts, Mount{Lang: language.Lang, Source: contentDir, Target: d.component})
					}
				}

				componentsConfigured[d.component] = len(seen) > 0

			} else {
				for _, language := range languages {
					mounts = append(mounts, createMountsFor(d, language)...)
				}
			}
		} else {
			mounts = append(mounts, createMountsFor(d, cfg)...)
		}

		return mounts
	}

	var mounts []Mount
	for _, dirKey := range dirKeys {
		if componentsConfigured[dirKey.component] {
			continue
		}

		mounts = append(mounts, createMounts(dirKey)...)

	}

	// Add default configuration
	for _, dirKey := range dirKeys {
		if componentsConfigured[dirKey.component] {
			continue
		}
		mounts = append(mounts, Mount{Source: dirKey.component, Target: dirKey.component})
	}

	// Prepend the mounts from configuration.
	mounts = append(moda.mounts, mounts...)

	moda.mounts = mounts

	return nil
}

// DecodeConfig creates a modules Config from a given Hugo configuration.
func DecodeConfig(cfg config.Provider) (Config, error) {
	return decodeConfig(cfg, nil)
}

func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Config, error) {
	c := DefaultModuleConfig
	c.replacementsMap = pathReplacements

	if cfg == nil {
		return c, nil
	}

	themeSet := cfg.IsSet("theme")
	moduleSet := cfg.IsSet("module")

	if moduleSet {
		m := cfg.GetStringMap("module")
		if err := mapstructure.WeakDecode(m, &c); err != nil {
			return c, err
		}

		if c.replacementsMap == nil {

			if len(c.Replacements) == 1 {
				c.Replacements = strings.Split(c.Replacements[0], ",")
			}

			for i, repl := range c.Replacements {
				c.Replacements[i] = strings.TrimSpace(repl)
			}

			c.replacementsMap = make(map[string]string)
			for _, repl := range c.Replacements {
				parts := strings.Split(repl, "->")
				if len(parts) != 2 {
					return c, errors.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl)
				}

				c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
			}
		}

		if c.replacementsMap != nil && c.Imports != nil {
			for i, imp := range c.Imports {
				if newImp, found := c.replacementsMap[imp.Path]; found {
					imp.Path = newImp
					imp.pathProjectReplaced = true
					c.Imports[i] = imp
				}
			}
		}

		for i, mnt := range c.Mounts {
			mnt.Source = filepath.Clean(mnt.Source)
			mnt.Target = filepath.Clean(mnt.Target)
			c.Mounts[i] = mnt
		}

	}

	if themeSet {
		imports := config.GetStringSlicePreserveString(cfg, "theme")
		for _, imp := range imports {
			c.Imports = append(c.Imports, Import{
				Path: imp,
			})
		}

	}

	return c, nil
}

// Config holds a module config.
type Config struct {
	Mounts  []Mount
	Imports []Import

	// Meta info about this module (license information etc.).
	Params map[string]interface{}

	// Will be validated against the running Hugo version.
	HugoVersion HugoVersion

	// A optional Glob pattern matching module paths to skip when vendoring, e.g.
	// "github.com/**".
	NoVendor string

	// When enabled, we will pick the vendored module closest to the module
	// using it.
	// The default behaviour is to pick the first.
	// Note that there can still be only one dependency of a given module path,
	// so once it is in use it cannot be redefined.
	VendorClosest bool

	Replacements    []string
	replacementsMap map[string]string

	// Configures GOPROXY.
	Proxy string
	// Configures GONOPROXY.
	NoProxy string
	// Configures GOPRIVATE.
	Private string
}

// hasModuleImport reports whether the project config have one or more
// modules imports, e.g. github.com/bep/myshortcodes.
func (c Config) hasModuleImport() bool {
	for _, imp := range c.Imports {
		if isProbablyModule(imp.Path) {
			return true
		}
	}
	return false
}

// HugoVersion holds Hugo binary version requirements for a module.
type HugoVersion struct {
	// The minimum Hugo version that this module works with.
	Min hugo.VersionString

	// The maxium Hugo version that this module works with.
	Max hugo.VersionString

	// Set if the extended version is needed.
	Extended bool
}

func (v HugoVersion) String() string {
	extended := ""
	if v.Extended {
		extended = " extended"
	}

	if v.Min != "" && v.Max != "" {
		return fmt.Sprintf("%s/%s%s", v.Min, v.Max, extended)
	}

	if v.Min != "" {
		return fmt.Sprintf("Min %s%s", v.Min, extended)
	}

	if v.Max != "" {
		return fmt.Sprintf("Max %s%s", v.Max, extended)
	}

	return extended
}

// IsValid reports whether this version is valid compared to the running
// Hugo binary.
func (v HugoVersion) IsValid() bool {
	current := hugo.CurrentVersion.Version()
	if v.Extended && !hugo.IsExtended {
		return false
	}

	isValid := true

	if v.Min != "" && current.Compare(v.Min) > 0 {
		isValid = false
	}

	if v.Max != "" && current.Compare(v.Max) < 0 {
		isValid = false
	}

	return isValid
}

type Import struct {
	Path                string // Module path
	pathProjectReplaced bool   // Set when Path is replaced in project config.
	IgnoreConfig        bool   // Ignore any config in config.toml (will still folow imports).
	IgnoreImports       bool   // Do not follow any configured imports.
	NoMounts            bool   // Do not mount any folder in this import.
	NoVendor            bool   // Never vendor this import (only allowed in main project).
	Disable             bool   // Turn off this module.
	Mounts              []Mount
}

type Mount struct {
	Source string // relative path in source repo, e.g. "scss"
	Target string // relative target path, e.g. "assets/bootstrap/scss"

	Lang string // any language code associated with this mount.

	// Include only files matching the given Glob patterns (string or slice).
	IncludeFiles interface{}

	// Exclude all files matching the given Glob patterns (string or slice).
	ExcludeFiles interface{}
}

// Used as key to remove duplicates.
func (m Mount) key() string {
	return path.Join(m.Lang, m.Source, m.Target)
}

func (m Mount) Component() string {
	return strings.Split(m.Target, fileSeparator)[0]
}

func (m Mount) ComponentAndName() (string, string) {
	k := strings.Index(m.Target, fileSeparator)
	if k == -1 {
		return m.Target, ""
	}
	return m.Target[:k], m.Target[k+1:]
}

func getStaticDirs(cfg config.Provider) []string {
	var staticDirs []string
	for i := -1; i <= 10; i++ {
		staticDirs = append(staticDirs, getStringOrStringSlice(cfg, "staticDir", i)...)
	}
	return staticDirs
}

func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
	if id >= 0 {
		key = fmt.Sprintf("%s%d", key, id)
	}

	return config.GetStringSlicePreserveString(cfg, key)
}
<commit_msg>don't use path.Join, because it cleans the final path<commit_after>// Copyright 2019 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package modules

import (
	"fmt"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"

	"github.com/gohugoio/hugo/common/hugo"

	"github.com/gohugoio/hugo/config"
	"github.com/gohugoio/hugo/hugofs/files"
	"github.com/gohugoio/hugo/langs"
	"github.com/mitchellh/mapstructure"
)

var DefaultModuleConfig = Config{

	// Default to direct, which means "git clone" and similar. We
	// will investigate proxy settings in more depth later.
	// See https://github.com/golang/go/issues/26334
	Proxy: "direct",

	// Comma separated glob list matching paths that should not use the
	// proxy configured above.
	NoProxy: "none",

	// Comma separated glob list matching paths that should be
	// treated as private.
	Private: "*.*",

	// A list of replacement directives mapping a module path to a directory
	// or a theme component in the themes folder.
	// Note that this will turn the component into a traditional theme component
	// that does not partake in vendoring etc.
	// The syntax is the similar to the replacement directives used in go.mod, e.g:
	//    github.com/mod1 -> ../mod1,github.com/mod2 -> ../mod2
	Replacements: nil,
}

// ApplyProjectConfigDefaults applies default/missing module configuration for
// the main project.
func ApplyProjectConfigDefaults(cfg config.Provider, mod Module) error {
	moda := mod.(*moduleAdapter)

	// Map legacy directory config into the new module.
	languages := cfg.Get("languagesSortedDefaultFirst").(langs.Languages)
	isMultiHost := languages.IsMultihost()

	// To bridge between old and new configuration format we need
	// a way to make sure all of the core components are configured on
	// the basic level.
	componentsConfigured := make(map[string]bool)
	for _, mnt := range moda.mounts {
		if !strings.HasPrefix(mnt.Target, files.JsConfigFolderMountPrefix) {
			componentsConfigured[mnt.Component()] = true
		}
	}

	type dirKeyComponent struct {
		key          string
		component    string
		multilingual bool
	}

	dirKeys := []dirKeyComponent{
		{"contentDir", files.ComponentFolderContent, true},
		{"dataDir", files.ComponentFolderData, false},
		{"layoutDir", files.ComponentFolderLayouts, false},
		{"i18nDir", files.ComponentFolderI18n, false},
		{"archetypeDir", files.ComponentFolderArchetypes, false},
		{"assetDir", files.ComponentFolderAssets, false},
		{"", files.ComponentFolderStatic, isMultiHost},
	}

	createMountsFor := func(d dirKeyComponent, cfg config.Provider) []Mount {
		var lang string
		if language, ok := cfg.(*langs.Language); ok {
			lang = language.Lang
		}

		// Static mounts are a little special.
		if d.component == files.ComponentFolderStatic {
			var mounts []Mount
			staticDirs := getStaticDirs(cfg)
			if len(staticDirs) > 0 {
				componentsConfigured[d.component] = true
			}

			for _, dir := range staticDirs {
				mounts = append(mounts, Mount{Lang: lang, Source: dir, Target: d.component})
			}

			return mounts

		}

		if cfg.IsSet(d.key) {
			source := cfg.GetString(d.key)
			componentsConfigured[d.component] = true

			return []Mount{{
				// No lang set for layouts etc.
				Source: source,
				Target: d.component,
			}}
		}

		return nil
	}

	createMounts := func(d dirKeyComponent) []Mount {
		var mounts []Mount
		if d.multilingual {
			if d.component == files.ComponentFolderContent {
				seen := make(map[string]bool)
				hasContentDir := false
				for _, language := range languages {
					if language.ContentDir != "" {
						hasContentDir = true
						break
					}
				}

				if hasContentDir {
					for _, language := range languages {
						contentDir := language.ContentDir
						if contentDir == "" {
							contentDir = files.ComponentFolderContent
						}
						if contentDir == "" || seen[contentDir] {
							continue
						}
						seen[contentDir] = true
						mounts = append(mounts, Mount{Lang: language.Lang, Source: contentDir, Target: d.component})
					}
				}

				componentsConfigured[d.component] = len(seen) > 0

			} else {
				for _, language := range languages {
					mounts = append(mounts, createMountsFor(d, language)...)
				}
			}
		} else {
			mounts = append(mounts, createMountsFor(d, cfg)...)
		}

		return mounts
	}

	var mounts []Mount
	for _, dirKey := range dirKeys {
		if componentsConfigured[dirKey.component] {
			continue
		}

		mounts = append(mounts, createMounts(dirKey)...)

	}

	// Add default configuration
	for _, dirKey := range dirKeys {
		if componentsConfigured[dirKey.component] {
			continue
		}
		mounts = append(mounts, Mount{Source: dirKey.component, Target: dirKey.component})
	}

	// Prepend the mounts from configuration.
	mounts = append(moda.mounts, mounts...)

	moda.mounts = mounts

	return nil
}

// DecodeConfig creates a modules Config from a given Hugo configuration.
func DecodeConfig(cfg config.Provider) (Config, error) {
	return decodeConfig(cfg, nil)
}

func decodeConfig(cfg config.Provider, pathReplacements map[string]string) (Config, error) {
	c := DefaultModuleConfig
	c.replacementsMap = pathReplacements

	if cfg == nil {
		return c, nil
	}

	themeSet := cfg.IsSet("theme")
	moduleSet := cfg.IsSet("module")

	if moduleSet {
		m := cfg.GetStringMap("module")
		if err := mapstructure.WeakDecode(m, &c); err != nil {
			return c, err
		}

		if c.replacementsMap == nil {

			if len(c.Replacements) == 1 {
				c.Replacements = strings.Split(c.Replacements[0], ",")
			}

			for i, repl := range c.Replacements {
				c.Replacements[i] = strings.TrimSpace(repl)
			}

			c.replacementsMap = make(map[string]string)
			for _, repl := range c.Replacements {
				parts := strings.Split(repl, "->")
				if len(parts) != 2 {
					return c, errors.Errorf(`invalid module.replacements: %q; configure replacement pairs on the form "oldpath->newpath" `, repl)
				}

				c.replacementsMap[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
			}
		}

		if c.replacementsMap != nil && c.Imports != nil {
			for i, imp := range c.Imports {
				if newImp, found := c.replacementsMap[imp.Path]; found {
					imp.Path = newImp
					imp.pathProjectReplaced = true
					c.Imports[i] = imp
				}
			}
		}

		for i, mnt := range c.Mounts {
			mnt.Source = filepath.Clean(mnt.Source)
			mnt.Target = filepath.Clean(mnt.Target)
			c.Mounts[i] = mnt
		}

	}

	if themeSet {
		imports := config.GetStringSlicePreserveString(cfg, "theme")
		for _, imp := range imports {
			c.Imports = append(c.Imports, Import{
				Path: imp,
			})
		}

	}

	return c, nil
}

// Config holds a module config.
type Config struct {
	Mounts  []Mount
	Imports []Import

	// Meta info about this module (license information etc.).
	Params map[string]interface{}

	// Will be validated against the running Hugo version.
	HugoVersion HugoVersion

	// A optional Glob pattern matching module paths to skip when vendoring, e.g.
	// "github.com/**".
	NoVendor string

	// When enabled, we will pick the vendored module closest to the module
	// using it.
	// The default behaviour is to pick the first.
	// Note that there can still be only one dependency of a given module path,
	// so once it is in use it cannot be redefined.
	VendorClosest bool

	Replacements    []string
	replacementsMap map[string]string

	// Configures GOPROXY.
	Proxy string
	// Configures GONOPROXY.
	NoProxy string
	// Configures GOPRIVATE.
	Private string
}

// hasModuleImport reports whether the project config have one or more
// modules imports, e.g. github.com/bep/myshortcodes.
func (c Config) hasModuleImport() bool {
	for _, imp := range c.Imports {
		if isProbablyModule(imp.Path) {
			return true
		}
	}
	return false
}

// HugoVersion holds Hugo binary version requirements for a module.
type HugoVersion struct {
	// The minimum Hugo version that this module works with.
	Min hugo.VersionString

	// The maxium Hugo version that this module works with.
	Max hugo.VersionString

	// Set if the extended version is needed.
	Extended bool
}

func (v HugoVersion) String() string {
	extended := ""
	if v.Extended {
		extended = " extended"
	}

	if v.Min != "" && v.Max != "" {
		return fmt.Sprintf("%s/%s%s", v.Min, v.Max, extended)
	}

	if v.Min != "" {
		return fmt.Sprintf("Min %s%s", v.Min, extended)
	}

	if v.Max != "" {
		return fmt.Sprintf("Max %s%s", v.Max, extended)
	}

	return extended
}

// IsValid reports whether this version is valid compared to the running
// Hugo binary.
func (v HugoVersion) IsValid() bool {
	current := hugo.CurrentVersion.Version()
	if v.Extended && !hugo.IsExtended {
		return false
	}

	isValid := true

	if v.Min != "" && current.Compare(v.Min) > 0 {
		isValid = false
	}

	if v.Max != "" && current.Compare(v.Max) < 0 {
		isValid = false
	}

	return isValid
}

type Import struct {
	Path                string // Module path
	pathProjectReplaced bool   // Set when Path is replaced in project config.
	IgnoreConfig        bool   // Ignore any config in config.toml (will still folow imports).
	IgnoreImports       bool   // Do not follow any configured imports.
	NoMounts            bool   // Do not mount any folder in this import.
	NoVendor            bool   // Never vendor this import (only allowed in main project).
	Disable             bool   // Turn off this module.
	Mounts              []Mount
}

type Mount struct {
	Source string // relative path in source repo, e.g. "scss"
	Target string // relative target path, e.g. "assets/bootstrap/scss"

	Lang string // any language code associated with this mount.

	// Include only files matching the given Glob patterns (string or slice).
	IncludeFiles interface{}

	// Exclude all files matching the given Glob patterns (string or slice).
	ExcludeFiles interface{}
}

// Used as key to remove duplicates.
func (m Mount) key() string {
	return strings.Join([]string{m.Lang, m.Source, m.Target}, "/")
}

func (m Mount) Component() string {
	return strings.Split(m.Target, fileSeparator)[0]
}

func (m Mount) ComponentAndName() (string, string) {
	k := strings.Index(m.Target, fileSeparator)
	if k == -1 {
		return m.Target, ""
	}
	return m.Target[:k], m.Target[k+1:]
}

func getStaticDirs(cfg config.Provider) []string {
	var staticDirs []string
	for i := -1; i <= 10; i++ {
		staticDirs = append(staticDirs, getStringOrStringSlice(cfg, "staticDir", i)...)
	}
	return staticDirs
}

func getStringOrStringSlice(cfg config.Provider, key string, id int) []string {
	if id >= 0 {
		key = fmt.Sprintf("%s%d", key, id)
	}

	return config.GetStringSlicePreserveString(cfg, key)
}
<|endoftext|>


<commit_before>package marathon

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/ckaznocha/marathon-resource/cmd/marathon-resource/dates"
	gomarathon "github.com/gambol99/go-marathon"
)

const (
	pathApp          = "/v2/apps/%s"
	pathAppVersions  = "/v2/apps/%s/versions"
	pathAppAtVersion = "/v2/apps/%s/versions/%s"
	pathDeployments  = "/v2/deployments"
	pathDeployment   = "/v2/deployments/%s"

	jsonContentType = "application/json"
)

type (
	doer interface {
		Do(req *http.Request) (*http.Response, error)
	}
	//Marathoner is an interface to interact with marathon
	Marathoner interface {
		LatestVersions(appID string, version string) ([]string, error)
		GetApp(appID, version string) (gomarathon.Application, error)
		UpdateApp(gomarathon.Application) (gomarathon.DeploymentID, error)
		CheckDeployment(deploymentID string) (bool, error)
		DeleteDeployment(deploymentID string) error
	}
	marathon struct {
		client doer
		url    *url.URL
		auth   *AuthCreds
	}

	//AuthCreds will be used for HTTP basic auth
	AuthCreds struct {
		UserName string `json:"user_name"`
		Password string `json:"password"`
	}
)

//NewMarathoner returns a new marathoner
func NewMarathoner(client doer, uri *url.URL, auth *AuthCreds) Marathoner {
	return &marathon{client: client, url: uri}
}

func (m *marathon) handleReq(
	method string,
	path string,
	payload io.Reader,
	wantCodes []int,
	resObj interface{},
) error {
	u := *m.url
	u.Path = path
	req, err := http.NewRequest(method, u.String(), payload)
	if err != nil {
		return err
	}
	req.Header.Set("Content-type", jsonContentType)
	if m.auth != nil {
		req.SetBasicAuth(m.auth.UserName, m.auth.Password)
	}
	res, err := m.client.Do(req)
	if err != nil {
		return err
	}
	if res.Body != nil {
		defer res.Body.Close()
	}

	gotWantCode := false
	for _, wantCode := range wantCodes {
		if res.StatusCode == wantCode {
			gotWantCode = true
			break
		}
	}

	if !gotWantCode {
		return fmt.Errorf(
			"Expected one of %v responses code but got %d",
			wantCodes,
			res.StatusCode,
		)
	}

	if res.Body == nil {
		return nil
	}

	if err = json.NewDecoder(res.Body).Decode(resObj); err != nil && err != io.EOF {
		return err
	}
	return nil
}

func (m *marathon) LatestVersions(appID, version string) ([]string, error) {
	var v gomarathon.ApplicationVersions
	if err := m.handleReq(
		http.MethodGet,
		fmt.Sprintf(pathAppVersions, appID),
		nil,
		[]int{http.StatusOK},
		&v,
	); err != nil {
		return nil, err
	}
	return dates.NewerTimestamps(v.Versions, version)
}

func (m *marathon) GetApp(appID, version string) (gomarathon.Application, error) {
	var app gomarathon.Application
	err := m.handleReq(
		http.MethodGet,
		fmt.Sprintf(pathAppAtVersion, appID, version),
		nil,
		[]int{http.StatusOK},
		&app,
	)
	return app, err
}

func (m *marathon) UpdateApp(inApp gomarathon.Application) (gomarathon.DeploymentID, error) {
	var (
		payload, _ = json.Marshal(inApp)
		deployment gomarathon.DeploymentID
	)
	err := m.handleReq(
		http.MethodPut,
		fmt.Sprintf(pathApp, inApp.ID),
		bytes.NewReader(payload),
		[]int{http.StatusOK, http.StatusCreated},
		&deployment,
	)
	return deployment, err
}

func (m *marathon) CheckDeployment(deploymentID string) (bool, error) {
	var (
		deployments []gomarathon.Deployment
	)
	err := m.handleReq(
		http.MethodGet,
		pathDeployments,
		nil,
		[]int{http.StatusOK},
		&deployments,
	)

	for _, v := range deployments {
		if v.ID == deploymentID {
			return true, nil
		}
	}
	return false, err
}

func (m *marathon) DeleteDeployment(deploymentID string) error {
	return m.handleReq(
		http.MethodDelete,
		fmt.Sprintf(pathDeployment, deploymentID),
		nil,
		[]int{http.StatusOK},
		nil,
	)
}
<commit_msg>Guard against nil return object<commit_after>package marathon

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"

	"github.com/ckaznocha/marathon-resource/cmd/marathon-resource/dates"
	gomarathon "github.com/gambol99/go-marathon"
)

const (
	pathApp          = "/v2/apps/%s"
	pathAppVersions  = "/v2/apps/%s/versions"
	pathAppAtVersion = "/v2/apps/%s/versions/%s"
	pathDeployments  = "/v2/deployments"
	pathDeployment   = "/v2/deployments/%s"

	jsonContentType = "application/json"
)

type (
	doer interface {
		Do(req *http.Request) (*http.Response, error)
	}
	//Marathoner is an interface to interact with marathon
	Marathoner interface {
		LatestVersions(appID string, version string) ([]string, error)
		GetApp(appID, version string) (gomarathon.Application, error)
		UpdateApp(gomarathon.Application) (gomarathon.DeploymentID, error)
		CheckDeployment(deploymentID string) (bool, error)
		DeleteDeployment(deploymentID string) error
	}
	marathon struct {
		client doer
		url    *url.URL
		auth   *AuthCreds
	}

	//AuthCreds will be used for HTTP basic auth
	AuthCreds struct {
		UserName string `json:"user_name"`
		Password string `json:"password"`
	}
)

//NewMarathoner returns a new marathoner
func NewMarathoner(client doer, uri *url.URL, auth *AuthCreds) Marathoner {
	return &marathon{client: client, url: uri}
}

func (m *marathon) handleReq(
	method string,
	path string,
	payload io.Reader,
	wantCodes []int,
	resObj interface{},
) error {
	u := *m.url
	u.Path = path
	req, err := http.NewRequest(method, u.String(), payload)
	if err != nil {
		return err
	}
	req.Header.Set("Content-type", jsonContentType)
	if m.auth != nil {
		req.SetBasicAuth(m.auth.UserName, m.auth.Password)
	}
	res, err := m.client.Do(req)
	if err != nil {
		return err
	}
	if res.Body != nil {
		defer res.Body.Close()
	}

	gotWantCode := false
	for _, wantCode := range wantCodes {
		if res.StatusCode == wantCode {
			gotWantCode = true
			break
		}
	}

	if !gotWantCode {
		return fmt.Errorf(
			"Expected one of %v responses code but got %d",
			wantCodes,
			res.StatusCode,
		)
	}

	if res.Body == nil || resObj == nil {
		return nil
	}

	if err = json.NewDecoder(res.Body).Decode(resObj); err != nil && err != io.EOF {
		return err
	}
	return nil
}

func (m *marathon) LatestVersions(appID, version string) ([]string, error) {
	var v gomarathon.ApplicationVersions
	if err := m.handleReq(
		http.MethodGet,
		fmt.Sprintf(pathAppVersions, appID),
		nil,
		[]int{http.StatusOK},
		&v,
	); err != nil {
		return nil, err
	}
	return dates.NewerTimestamps(v.Versions, version)
}

func (m *marathon) GetApp(appID, version string) (gomarathon.Application, error) {
	var app gomarathon.Application
	err := m.handleReq(
		http.MethodGet,
		fmt.Sprintf(pathAppAtVersion, appID, version),
		nil,
		[]int{http.StatusOK},
		&app,
	)
	return app, err
}

func (m *marathon) UpdateApp(inApp gomarathon.Application) (gomarathon.DeploymentID, error) {
	var (
		payload, _ = json.Marshal(inApp)
		deployment gomarathon.DeploymentID
	)
	err := m.handleReq(
		http.MethodPut,
		fmt.Sprintf(pathApp, inApp.ID),
		bytes.NewReader(payload),
		[]int{http.StatusOK, http.StatusCreated},
		&deployment,
	)
	return deployment, err
}

func (m *marathon) CheckDeployment(deploymentID string) (bool, error) {
	var (
		deployments []gomarathon.Deployment
	)
	err := m.handleReq(
		http.MethodGet,
		pathDeployments,
		nil,
		[]int{http.StatusOK},
		&deployments,
	)

	for _, v := range deployments {
		if v.ID == deploymentID {
			return true, nil
		}
	}
	return false, err
}

func (m *marathon) DeleteDeployment(deploymentID string) error {
	return m.handleReq(
		http.MethodDelete,
		fmt.Sprintf(pathDeployment, deploymentID),
		nil,
		[]int{http.StatusOK},
		nil,
	)
}
<|endoftext|>


<commit_before>package cmd

import (
	"flag"
	"fmt"
	"os"
	"os/exec"

	"github.com/pkg/errors"
)

var currentGrift *grifter

func init() {
	flag.Usage = func() {
		fmt.Printf("Grift Version: %s\n", Version)

		fmt.Print("\nUsage:\n")

		fmt.Println("grift <task name> [task arguments]")

		fmt.Println("\nFlags/Options:")
		flag.PrintDefaults()
	}
}

func Run(name string, args []string) error {
	defer func() {
		currentGrift.TearDown()
	}()
	if len(args) == 2 {
		switch args[1] {
		case "jim":
			jimTribute()
			return nil
		case "init":
			generateInit()
			return nil
		}
	}

	err := setup(name)
	if err != nil {
		return errors.WithStack(err)
	}

	err = run(args)
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func run(args []string) error {
	rargs := []string{"run", exePath}
	rargs = append(rargs, args...)
	runner := exec.Command("go", rargs...)
	runner.Stdin = os.Stdin
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr
	err := runner.Run()
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func list() error {
	rargs := []string{"run", exePath, "list"}
	runner := exec.Command("go", rargs...)
	runner.Stderr = os.Stderr
	runner.Stdin = os.Stdin
	runner.Stdout = os.Stdout
	return runner.Run()
}

func setup(name string) error {
	var err error
	currentGrift, err = newGrifter(name)
	if err != nil {
		return errors.WithStack(err)
	}
	err = currentGrift.Setup()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}
<commit_msg>Restore intent of the init function<commit_after>package cmd

import (
	"fmt"
	"os"
	"os/exec"

	"github.com/pkg/errors"
)

var currentGrift *grifter

func Run(name string, args []string) error {
	defer func() {
		currentGrift.TearDown()
	}()
	if len(args) == 2 {
		switch args[1] {
		case "jim":
			jimTribute()
			return nil
		case "init":
			generateInit()
			return nil
		case "--version", "-v":
			fmt.Println(Version)
			return nil
		case "--help", "-h":
			fmt.Println("grift <task name> [task arguments]")
			return nil
		}
	}

	err := setup(name)
	if err != nil {
		return errors.WithStack(err)
	}

	err = run(args)
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func run(args []string) error {
	rargs := []string{"run", exePath}
	rargs = append(rargs, args...)
	runner := exec.Command("go", rargs...)
	runner.Stdin = os.Stdin
	runner.Stdout = os.Stdout
	runner.Stderr = os.Stderr
	err := runner.Run()
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

func list() error {
	rargs := []string{"run", exePath, "list"}
	runner := exec.Command("go", rargs...)
	runner.Stderr = os.Stderr
	runner.Stdin = os.Stdin
	runner.Stdout = os.Stdout
	return runner.Run()
}

func setup(name string) error {
	var err error
	currentGrift, err = newGrifter(name)
	if err != nil {
		return errors.WithStack(err)
	}
	err = currentGrift.Setup()
	if err != nil {
		return errors.WithStack(err)
	}
	return nil
}
<|endoftext|>


<commit_before>/* Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 siesta

import (
	"io"
	"net"
	"time"
)

type ConnectionRequest struct {
	connection *net.TCPConn
	request    *NetworkRequest
}

//TODO proper config entry names that match upstream Kafka
type SelectorConfig struct {
	ClientID        string
	MaxRequests     int
	SendRoutines    int
	ReceiveRoutines int
	ReadTimeout     time.Duration
	WriteTimeout    time.Duration
	RequiredAcks    int
}

func DefaultSelectorConfig() *SelectorConfig {
	return &SelectorConfig{
		ClientID:        "siesta",
		MaxRequests:     10,
		SendRoutines:    10,
		ReceiveRoutines: 10,
		ReadTimeout:     5 * time.Second,
		WriteTimeout:    5 * time.Second,
		RequiredAcks:    1,
	}
}

func NewSelectorConfig(producerConfig *ProducerConfig) *SelectorConfig {
	return &SelectorConfig{
		ClientID:        producerConfig.ClientID,
		MaxRequests:     producerConfig.MaxRequests,
		SendRoutines:    producerConfig.SendRoutines,
		ReceiveRoutines: producerConfig.ReceiveRoutines,
		ReadTimeout:     producerConfig.ReadTimeout,
		WriteTimeout:    producerConfig.WriteTimeout,
		RequiredAcks:    producerConfig.RequiredAcks,
	}
}

type Selector struct {
	config    *SelectorConfig
	requests  chan *NetworkRequest
	responses chan *ConnectionRequest
}

func NewSelector(config *SelectorConfig) *Selector {
	selector := &Selector{
		config:    config,
		requests:  make(chan *NetworkRequest, config.MaxRequests),
		responses: make(chan *ConnectionRequest, config.MaxRequests),
	}
	selector.Start()
	return selector
}

func (s *Selector) Start() {
	for i := 0; i < s.config.SendRoutines; i++ {
		go s.requestDispatcher()
	}

	for i := 0; i < s.config.ReceiveRoutines; i++ {
		go s.responseDispatcher()
	}
}

func (s *Selector) Close() {
	close(s.requests)
	close(s.responses)
}

func (s *Selector) Send(link BrokerLink, request Request) <-chan *rawResponseAndError {
	responseChan := make(chan *rawResponseAndError, 1) //make this buffered so we don't block if noone reads the response
	s.requests <- &NetworkRequest{link, request, responseChan}

	return responseChan
}

func (s *Selector) requestDispatcher() {
	for request := range s.requests {
		link := request.link
		id, conn, err := link.GetConnection()
		if err != nil {
			link.Failed()
			request.responseChan <- &rawResponseAndError{nil, link, err}
			continue
		}

		if err := s.send(id, conn, request.request); err != nil {
			link.Failed()
			request.responseChan <- &rawResponseAndError{nil, link, err}
			continue
		}

		if s.config.RequiredAcks > 0 {
			s.responses <- &ConnectionRequest{connection: conn, request: request}
		}
	}
}

func (s *Selector) responseDispatcher() {
	for connectionResponse := range s.responses {
		link := connectionResponse.request.link
		conn := connectionResponse.connection
		responseChan := connectionResponse.request.responseChan

		bytes, err := s.receive(conn)
		if err != nil {
			link.Failed()
			responseChan <- &rawResponseAndError{nil, link, err}
			continue
		}

		link.Succeeded()
		link.ReturnConnection(conn)
		responseChan <- &rawResponseAndError{bytes, link, err}
	}
}

func (s *Selector) send(correlationID int32, conn *net.TCPConn, request Request) error {
	writer := NewRequestHeader(correlationID, s.config.ClientID, request)
	bytes := make([]byte, writer.Size())
	encoder := NewBinaryEncoder(bytes)
	writer.Write(encoder)

	conn.SetWriteDeadline(time.Now().Add(s.config.WriteTimeout))
	_, err := conn.Write(bytes)
	return err
}

func (s *Selector) receive(conn *net.TCPConn) ([]byte, error) {
	conn.SetReadDeadline(time.Now().Add(s.config.ReadTimeout))
	header := make([]byte, 8)
	_, err := io.ReadFull(conn, header)
	if err != nil {
		return nil, err
	}

	decoder := NewBinaryDecoder(header)
	length, err := decoder.GetInt32()
	if err != nil {
		return nil, err
	}
	response := make([]byte, length-4)
	_, err = io.ReadFull(conn, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

//TODO better struct name
type NetworkRequest struct {
	link         BrokerLink
	request      Request
	responseChan chan *rawResponseAndError
}
<commit_msg>Release free connections.<commit_after>/* Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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 siesta

import (
	"io"
	"net"
	"time"
)

type ConnectionRequest struct {
	connection *net.TCPConn
	request    *NetworkRequest
}

//TODO proper config entry names that match upstream Kafka
type SelectorConfig struct {
	ClientID        string
	MaxRequests     int
	SendRoutines    int
	ReceiveRoutines int
	ReadTimeout     time.Duration
	WriteTimeout    time.Duration
	RequiredAcks    int
}

func DefaultSelectorConfig() *SelectorConfig {
	return &SelectorConfig{
		ClientID:        "siesta",
		MaxRequests:     10,
		SendRoutines:    10,
		ReceiveRoutines: 10,
		ReadTimeout:     5 * time.Second,
		WriteTimeout:    5 * time.Second,
		RequiredAcks:    1,
	}
}

func NewSelectorConfig(producerConfig *ProducerConfig) *SelectorConfig {
	return &SelectorConfig{
		ClientID:        producerConfig.ClientID,
		MaxRequests:     producerConfig.MaxRequests,
		SendRoutines:    producerConfig.SendRoutines,
		ReceiveRoutines: producerConfig.ReceiveRoutines,
		ReadTimeout:     producerConfig.ReadTimeout,
		WriteTimeout:    producerConfig.WriteTimeout,
		RequiredAcks:    producerConfig.RequiredAcks,
	}
}

type Selector struct {
	config    *SelectorConfig
	requests  chan *NetworkRequest
	responses chan *ConnectionRequest
}

func NewSelector(config *SelectorConfig) *Selector {
	selector := &Selector{
		config:    config,
		requests:  make(chan *NetworkRequest, config.MaxRequests),
		responses: make(chan *ConnectionRequest, config.MaxRequests),
	}
	selector.Start()
	return selector
}

func (s *Selector) Start() {
	for i := 0; i < s.config.SendRoutines; i++ {
		go s.requestDispatcher()
	}

	if s.config.RequiredAcks > 0 {
		for i := 0; i < s.config.ReceiveRoutines; i++ {
			go s.responseDispatcher()
		}
	}
}

func (s *Selector) Close() {
	close(s.requests)
	close(s.responses)
}

func (s *Selector) Send(link BrokerLink, request Request) <-chan *rawResponseAndError {
	responseChan := make(chan *rawResponseAndError, 1) //make this buffered so we don't block if noone reads the response
	s.requests <- &NetworkRequest{link, request, responseChan}

	return responseChan
}

func (s *Selector) requestDispatcher() {
	for request := range s.requests {
		link := request.link
		id, conn, err := link.GetConnection()
		if err != nil {
			link.Failed()
			if s.config.RequiredAcks > 0 {
				request.responseChan <- &rawResponseAndError{nil, link, err}
			}
			continue
		}

		if err := s.send(id, conn, request.request); err != nil {
			link.Failed()
			if s.config.RequiredAcks > 0 {
				request.responseChan <- &rawResponseAndError{nil, link, err}
			} else {
				link.ReturnConnection(conn)
			}
			continue
		}

		if s.config.RequiredAcks > 0 {
			s.responses <- &ConnectionRequest{connection: conn, request: request}
		} else {
			link.Succeeded()
			link.ReturnConnection(conn)
		}
	}
}

func (s *Selector) responseDispatcher() {
	for connectionResponse := range s.responses {
		link := connectionResponse.request.link
		conn := connectionResponse.connection
		responseChan := connectionResponse.request.responseChan

		bytes, err := s.receive(conn)
		if err != nil {
			link.Failed()
			responseChan <- &rawResponseAndError{nil, link, err}
			link.ReturnConnection(conn)
			continue
		}

		link.Succeeded()
		link.ReturnConnection(conn)
		responseChan <- &rawResponseAndError{bytes, link, err}
	}
}

func (s *Selector) send(correlationID int32, conn *net.TCPConn, request Request) error {
	writer := NewRequestHeader(correlationID, s.config.ClientID, request)
	bytes := make([]byte, writer.Size())
	encoder := NewBinaryEncoder(bytes)
	writer.Write(encoder)

	conn.SetWriteDeadline(time.Now().Add(s.config.WriteTimeout))
	_, err := conn.Write(bytes)
	return err
}

func (s *Selector) receive(conn *net.TCPConn) ([]byte, error) {
	conn.SetReadDeadline(time.Now().Add(s.config.ReadTimeout))
	header := make([]byte, 8)
	_, err := io.ReadFull(conn, header)
	if err != nil {
		return nil, err
	}

	decoder := NewBinaryDecoder(header)
	length, err := decoder.GetInt32()
	if err != nil {
		return nil, err
	}
	response := make([]byte, length-4)
	_, err = io.ReadFull(conn, response)
	if err != nil {
		return nil, err
	}

	return response, nil
}

//TODO better struct name
type NetworkRequest struct {
	link         BrokerLink
	request      Request
	responseChan chan *rawResponseAndError
}
<|endoftext|>


<commit_before>/**
 工具类api

**/
package a

import (
	"errors"
)

//=========================
/**
换取授权访问令牌
alipay.system.oauth.token
换取授权访问令牌
*/
type alipay_system_oauth_token struct {
	AlipayApi
}

func (a *alipay_system_oauth_token) apiMethod() string {
	return "alipay.system.oauth.token"
}

func (a *alipay_system_oauth_token) apiName() string {
	return "换取授权访问令牌"
}

type Biz_alipay_system_oauth_token struct {
	GrantType    string `json:"grant_type,omitempty"`
	Code         string `json:"code,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

func (b Biz_alipay_system_oauth_token) valid() error {
	if len(b.GrantType) == 0 {
		return errors.New("grant_type" + CAN_NOT_NIL)
	}

	if b.GrantType != "authorization_code" && b.GrantType != "refresh_token" {
		return errors.New("grant_type" + FORAMT_ERROR)
	}

	if b.GrantType == "authorization_code" && len(b.Code) == 0 {
		return errors.New("code" + CAN_NOT_NIL)
	}

	if b.GrantType == "refresh_token" && len(b.RefreshToken) == 0 {
		return errors.New("refresh_token" + CAN_NOT_NIL)
	}
	return nil
}

type Resp_alipay_system_oauth_token struct {
	Response
	AlipayUserId string `json:"alipay_user_id,omitempty"`
	UserId       string `json:"user_id,omitempty"`
	AccessToken  string `json:"access_token,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
	ExpiresIn    string `json:"expires_in,omitempty"`
	ReExpiresIn  string `json:"re_expires_in,omitempty"`
}

//=========================
/**
换取授权访问令牌
alipay.open.auth.token.app
换取授权访问令牌
*/
type alipay_open_auth_token_app struct {
	AlipayApi
}

func (a *alipay_open_auth_token_app) apiMethod() string {
	return "alipay.open.auth.token.app"
}

func (a *alipay_open_auth_token_app) apiName() string {
	return "换取授权访问令牌"
}

type Biz_alipay_open_auth_token_app struct {
	GrantType    string `json:"grant_type,omitempty"`
	Code         string `json:"code,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

func (b Biz_alipay_open_auth_token_app) valid() error {
	if len(b.GrantType) == 0 {
		return errors.New("grant_type" + CAN_NOT_NIL)
	}

	if b.GrantType != "authorization_code" && b.GrantType != "refresh_token" {
		return errors.New("grant_type" + FORAMT_ERROR)
	}

	if b.GrantType == "authorization_code" && len(b.Code) == 0 {
		return errors.New("code" + CAN_NOT_NIL)
	}

	if b.GrantType == "refresh_token" && len(b.RefreshToken) == 0 {
		return errors.New("refresh_token" + CAN_NOT_NIL)
	}
	return nil
}

type Resp_alipay_open_auth_token_app struct {
	Response
	UserId          string `json:"user_id,omitempty"`
	AuthAppId       string `json:"auth_app_id,omitempty"`
	AppAuthToken    string `json:"app_auth_token,omitempty"`
	AppRefreshToken string `json:"app_refresh_token,omitempty"`
	ExpiresIn       string `json:"expires_in,omitempty"`
	ReExpiresIn     string `json:"re_expires_in,omitempty"`
}

func init() {
	registerApi(new(alipay_system_oauth_token))
	registerApi(new(alipay_open_auth_token_app))
}
<commit_msg>修改过期时间格式为float64<commit_after>/**
 工具类api

**/
package a

import (
	"errors"
)

//=========================
/**
换取授权访问令牌
alipay.system.oauth.token
换取授权访问令牌
*/
type alipay_system_oauth_token struct {
	AlipayApi
}

func (a *alipay_system_oauth_token) apiMethod() string {
	return "alipay.system.oauth.token"
}

func (a *alipay_system_oauth_token) apiName() string {
	return "换取授权访问令牌"
}

type Biz_alipay_system_oauth_token struct {
	GrantType    string `json:"grant_type,omitempty"`
	Code         string `json:"code,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

func (b Biz_alipay_system_oauth_token) valid() error {
	if len(b.GrantType) == 0 {
		return errors.New("grant_type" + CAN_NOT_NIL)
	}

	if b.GrantType != "authorization_code" && b.GrantType != "refresh_token" {
		return errors.New("grant_type" + FORAMT_ERROR)
	}

	if b.GrantType == "authorization_code" && len(b.Code) == 0 {
		return errors.New("code" + CAN_NOT_NIL)
	}

	if b.GrantType == "refresh_token" && len(b.RefreshToken) == 0 {
		return errors.New("refresh_token" + CAN_NOT_NIL)
	}
	return nil
}

type Resp_alipay_system_oauth_token struct {
	Response
	AlipayUserId string  `json:"alipay_user_id,omitempty"`
	UserId       string  `json:"user_id,omitempty"`
	AccessToken  string  `json:"access_token,omitempty"`
	RefreshToken string  `json:"refresh_token,omitempty"`
	ExpiresIn    float64 `json:"expires_in,omitempty"`
	ReExpiresIn  float64 `json:"re_expires_in,omitempty"`
}

//=========================
/**
换取授权访问令牌
alipay.open.auth.token.app
换取授权访问令牌
*/
type alipay_open_auth_token_app struct {
	AlipayApi
}

func (a *alipay_open_auth_token_app) apiMethod() string {
	return "alipay.open.auth.token.app"
}

func (a *alipay_open_auth_token_app) apiName() string {
	return "换取授权访问令牌"
}

type Biz_alipay_open_auth_token_app struct {
	GrantType    string `json:"grant_type,omitempty"`
	Code         string `json:"code,omitempty"`
	RefreshToken string `json:"refresh_token,omitempty"`
}

func (b Biz_alipay_open_auth_token_app) valid() error {
	if len(b.GrantType) == 0 {
		return errors.New("grant_type" + CAN_NOT_NIL)
	}

	if b.GrantType != "authorization_code" && b.GrantType != "refresh_token" {
		return errors.New("grant_type" + FORAMT_ERROR)
	}

	if b.GrantType == "authorization_code" && len(b.Code) == 0 {
		return errors.New("code" + CAN_NOT_NIL)
	}

	if b.GrantType == "refresh_token" && len(b.RefreshToken) == 0 {
		return errors.New("refresh_token" + CAN_NOT_NIL)
	}
	return nil
}

type Resp_alipay_open_auth_token_app struct {
	Response
	UserId          string  `json:"user_id,omitempty"`
	AuthAppId       string  `json:"auth_app_id,omitempty"`
	AppAuthToken    string  `json:"app_auth_token,omitempty"`
	AppRefreshToken string  `json:"app_refresh_token,omitempty"`
	ExpiresIn       float64 `json:"expires_in,omitempty"`
	ReExpiresIn     float64 `json:"re_expires_in,omitempty"`
}

func init() {
	registerApi(new(alipay_system_oauth_token))
	registerApi(new(alipay_open_auth_token_app))
}
<|endoftext|>