<commit_before>// Copyright 2015 go-swagger maintainers
//
// 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 middleware

import (
	"net/http"

	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/httpkit/middleware/untyped"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
	"github.com/golang/gddo/httputil"
	"github.com/gorilla/context"
)

// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
	BindRequest(*http.Request, *MatchedRoute) error
}

// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
	WriteResponse(http.ResponseWriter, httpkit.Producer)
}

// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
type Context struct {
	spec    *spec.Document
	api     RoutableAPI
	router  Router
	formats strfmt.Registry
}

type routableUntypedAPI struct {
	api             *untyped.API
	handlers        map[string]http.Handler
	defaultConsumes string
	defaultProduces string
}

func newRoutableUntypedAPI(spec *spec.Document, api *untyped.API, context *Context) *routableUntypedAPI {
	var handlers map[string]http.Handler
	if spec == nil || api == nil {
		return nil
	}
	for _, hls := range spec.Operations() {
		for _, op := range hls {
			schemes := spec.SecurityDefinitionsFor(op)

			if oh, ok := api.OperationHandlerFor(op.ID); ok {
				if handlers == nil {
					handlers = make(map[string]http.Handler)
				}

				handlers[op.ID] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					// lookup route info in the context
					route, _ := context.RouteInfo(r)

					// bind and validate the request using reflection
					bound, validation := context.BindAndValidate(r, route)
					if validation != nil {
						context.Respond(w, r, route.Produces, route, validation)
						return
					}

					// actually handle the request
					result, err := oh.Handle(bound)
					if err != nil {
						// respond with failure
						context.Respond(w, r, route.Produces, route, err)
						return
					}

					// respond with success
					context.Respond(w, r, route.Produces, route, result)
				})

				if len(schemes) > 0 {
					handlers[op.ID] = newSecureAPI(context, handlers[op.ID])
				}
			}
		}
	}

	return &routableUntypedAPI{
		api:             api,
		handlers:        handlers,
		defaultProduces: api.DefaultProduces,
		defaultConsumes: api.DefaultConsumes,
	}
}

func (r *routableUntypedAPI) HandlerFor(operationID string) (http.Handler, bool) {
	handler, ok := r.handlers[operationID]
	return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
	return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]httpkit.Consumer {
	return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]httpkit.Producer {
	return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]httpkit.Authenticator {
	return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
	return r.api.Formats()
}

func (r *routableUntypedAPI) DefaultProduces() string {
	return r.defaultProduces
}

func (r *routableUntypedAPI) DefaultConsumes() string {
	return r.defaultConsumes
}

// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *spec.Document, routableAPI RoutableAPI, routes Router) *Context {
	ctx := &Context{spec: spec, api: routableAPI}
	return ctx
}

// NewContext creates a new context wrapper
func NewContext(spec *spec.Document, api *untyped.API, routes Router) *Context {
	ctx := &Context{spec: spec}
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
	return ctx
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *untyped.API) http.Handler {
	context := NewContext(spec, api, nil)
	return context.APIHandler()
}

type contextKey int8

const (
	_ contextKey = iota
	ctxContentType
	ctxResponseFormat
	ctxMatchedRoute
	ctxAllowedMethods
	ctxBoundParams
	ctxSecurityPrincipal

	ctxConsumer
)

type contentTypeValue struct {
	MediaType string
	Charset   string
}

// BasePath returns the base path for this API
func (c *Context) BasePath() string {
	return c.spec.BasePath()
}

// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
	return c.spec.RequiredProduces()
}

// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
	var res []error

	// check and validate content type, select consumer
	if httpkit.CanHaveBody(request.Method) {
		ct, _, err := httpkit.ContentType(request.Header)
		if err != nil {
			res = append(res, err)
		} else {
			if err := validateContentType(route.Consumes, ct); err != nil {
				res = append(res, err)
			}
			route.Consumer = route.Consumers[ct]
		}
	}

	// check and validate the response format
	if len(res) == 0 {
		if str := httputil.NegotiateContentType(request, route.Produces, ""); str == "" {
			res = append(res, errors.InvalidResponseFormat(request.Header.Get(httpkit.HeaderAccept), route.Produces))
		}
	}

	// now bind the request with the provided binder
	// it's assumed the binder will also validate the request and return an error if the
	// request is invalid
	if binder != nil && len(res) == 0 {
		if err := binder.BindRequest(request, route); err != nil {
			res = append(res, err)
		}
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, *errors.ParseError) {
	if v, ok := context.GetOk(request, ctxContentType); ok {
		if val, ok := v.(*contentTypeValue); ok {
			return val.MediaType, val.Charset, nil
		}
	}

	mt, cs, err := httpkit.ContentType(request.Header)
	if err != nil {
		return "", "", err
	}
	context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
	return mt, cs, nil
}

// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
	if route, ok := c.router.Lookup(request.Method, request.URL.Path); ok {
		return route, ok
	}
	return nil, false
}

// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
	if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
		if val, ok := v.(*MatchedRoute); ok {
			return val, ok
		}
	}

	if route, ok := c.LookupRoute(request); ok {
		context.Set(request, ctxMatchedRoute, route)
		return route, ok
	}

	return nil, false
}

// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
	if v, ok := context.GetOk(r, ctxResponseFormat); ok {
		if val, ok := v.(string); ok {
			return val
		}
	}

	format := httputil.NegotiateContentType(r, offers, "")
	if format != "" {
		context.Set(r, ctxResponseFormat, format)
	}
	return format
}

// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
	return c.router.OtherMethods(request.Method, request.URL.Path)
}

// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
	if len(route.Authenticators) == 0 {
		return nil, nil
	}
	if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
		return v, nil
	}

	for _, authenticator := range route.Authenticators {
		applies, usr, err := authenticator.Authenticate(request)
		if !applies || err != nil || usr == nil {
			continue
		}
		context.Set(request, ctxSecurityPrincipal, usr)
		return usr, nil
	}

	return nil, errors.Unauthenticated("invalid credentials")
}

// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
	if v, ok := context.GetOk(request, ctxBoundParams); ok {
		if val, ok := v.(*validation); ok {
			if len(val.result) > 0 {
				return val.bound, errors.CompositeValidationError(val.result...)
			}
			return val.bound, nil
		}
	}
	result := validateRequest(c, request, matched)
	if result != nil {
		context.Set(request, ctxBoundParams, result)
	}
	if len(result.result) > 0 {
		return result.bound, errors.CompositeValidationError(result.result...)
	}
	return result.bound, nil
}

// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
	c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}

// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
	offers := []string{c.api.DefaultProduces()}
	for _, mt := range produces {
		if mt != c.api.DefaultProduces() {
			offers = append(offers, mt)
		}
	}

	format := c.ResponseFormat(r, offers)
	rw.Header().Set(httpkit.HeaderContentType, format)

	if resp, ok := data.(Responder); ok {
		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		resp.WriteResponse(rw, prod)
		return
	}

	if err, ok := data.(error); ok {
		if format == "" {
			rw.Header().Set(httpkit.HeaderContentType, httpkit.JSONMime)
		}
		if route == nil || route.Operation == nil {
			c.api.ServeErrorFor("")(rw, r, err)
			return
		}
		c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
		return
	}

	if route == nil || route.Operation == nil {
		rw.WriteHeader(200)
		if r.Method == "HEAD" {
			return
		}
		producers := c.api.ProducersFor(offers)
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	if _, code, ok := route.Operation.SuccessResponse(); ok {
		rw.WriteHeader(code)
		if code == 204 || r.Method == "HEAD" {
			return
		}

		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}

// APIHandler returns a handler to serve
func (c *Context) APIHandler() http.Handler {
	return specMiddleware(c, newRouter(c, newOperationExecutor(c)))
}
<commit_msg>fixes enum validation fail<commit_after>// Copyright 2015 go-swagger maintainers
//
// 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 middleware

import (
	"fmt"
	"net/http"

	"github.com/go-swagger/go-swagger/errors"
	"github.com/go-swagger/go-swagger/httpkit"
	"github.com/go-swagger/go-swagger/httpkit/middleware/untyped"
	"github.com/go-swagger/go-swagger/spec"
	"github.com/go-swagger/go-swagger/strfmt"
	"github.com/golang/gddo/httputil"
	"github.com/gorilla/context"
)

// RequestBinder is an interface for types to implement
// when they want to be able to bind from a request
type RequestBinder interface {
	BindRequest(*http.Request, *MatchedRoute) error
}

// Responder is an interface for types to implement
// when they want to be considered for writing HTTP responses
type Responder interface {
	WriteResponse(http.ResponseWriter, httpkit.Producer)
}

// Context is a type safe wrapper around an untyped request context
// used throughout to store request context with the gorilla context module
type Context struct {
	spec    *spec.Document
	api     RoutableAPI
	router  Router
	formats strfmt.Registry
}

type routableUntypedAPI struct {
	api             *untyped.API
	handlers        map[string]http.Handler
	defaultConsumes string
	defaultProduces string
}

func newRoutableUntypedAPI(spec *spec.Document, api *untyped.API, context *Context) *routableUntypedAPI {
	var handlers map[string]http.Handler
	if spec == nil || api == nil {
		return nil
	}
	for _, hls := range spec.Operations() {
		for _, op := range hls {
			schemes := spec.SecurityDefinitionsFor(op)

			if oh, ok := api.OperationHandlerFor(op.ID); ok {
				if handlers == nil {
					handlers = make(map[string]http.Handler)
				}

				handlers[op.ID] = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
					// lookup route info in the context
					route, _ := context.RouteInfo(r)

					// bind and validate the request using reflection
					bound, validation := context.BindAndValidate(r, route)
					if validation != nil {
						context.Respond(w, r, route.Produces, route, validation)
						return
					}

					// actually handle the request
					result, err := oh.Handle(bound)
					if err != nil {
						// respond with failure
						context.Respond(w, r, route.Produces, route, err)
						return
					}

					// respond with success
					context.Respond(w, r, route.Produces, route, result)
				})

				if len(schemes) > 0 {
					handlers[op.ID] = newSecureAPI(context, handlers[op.ID])
				}
			}
		}
	}

	return &routableUntypedAPI{
		api:             api,
		handlers:        handlers,
		defaultProduces: api.DefaultProduces,
		defaultConsumes: api.DefaultConsumes,
	}
}

func (r *routableUntypedAPI) HandlerFor(operationID string) (http.Handler, bool) {
	handler, ok := r.handlers[operationID]
	return handler, ok
}
func (r *routableUntypedAPI) ServeErrorFor(operationID string) func(http.ResponseWriter, *http.Request, error) {
	return r.api.ServeError
}
func (r *routableUntypedAPI) ConsumersFor(mediaTypes []string) map[string]httpkit.Consumer {
	return r.api.ConsumersFor(mediaTypes)
}
func (r *routableUntypedAPI) ProducersFor(mediaTypes []string) map[string]httpkit.Producer {
	return r.api.ProducersFor(mediaTypes)
}
func (r *routableUntypedAPI) AuthenticatorsFor(schemes map[string]spec.SecurityScheme) map[string]httpkit.Authenticator {
	return r.api.AuthenticatorsFor(schemes)
}
func (r *routableUntypedAPI) Formats() strfmt.Registry {
	return r.api.Formats()
}

func (r *routableUntypedAPI) DefaultProduces() string {
	return r.defaultProduces
}

func (r *routableUntypedAPI) DefaultConsumes() string {
	return r.defaultConsumes
}

// NewRoutableContext creates a new context for a routable API
func NewRoutableContext(spec *spec.Document, routableAPI RoutableAPI, routes Router) *Context {
	ctx := &Context{spec: spec, api: routableAPI}
	return ctx
}

// NewContext creates a new context wrapper
func NewContext(spec *spec.Document, api *untyped.API, routes Router) *Context {
	ctx := &Context{spec: spec}
	ctx.api = newRoutableUntypedAPI(spec, api, ctx)
	return ctx
}

// Serve serves the specified spec with the specified api registrations as a http.Handler
func Serve(spec *spec.Document, api *untyped.API) http.Handler {
	context := NewContext(spec, api, nil)
	return context.APIHandler()
}

type contextKey int8

const (
	_ contextKey = iota
	ctxContentType
	ctxResponseFormat
	ctxMatchedRoute
	ctxAllowedMethods
	ctxBoundParams
	ctxSecurityPrincipal

	ctxConsumer
)

type contentTypeValue struct {
	MediaType string
	Charset   string
}

// BasePath returns the base path for this API
func (c *Context) BasePath() string {
	return c.spec.BasePath()
}

// RequiredProduces returns the accepted content types for responses
func (c *Context) RequiredProduces() []string {
	return c.spec.RequiredProduces()
}

// BindValidRequest binds a params object to a request but only when the request is valid
// if the request is not valid an error will be returned
func (c *Context) BindValidRequest(request *http.Request, route *MatchedRoute, binder RequestBinder) error {
	var res []error

	// check and validate content type, select consumer
	if httpkit.CanHaveBody(request.Method) {
		ct, _, err := httpkit.ContentType(request.Header)
		if err != nil {
			res = append(res, err)
		} else {
			if err := validateContentType(route.Consumes, ct); err != nil {
				res = append(res, err)
			}
			route.Consumer = route.Consumers[ct]
		}
	}

	// check and validate the response format
	if len(res) == 0 {
		if str := httputil.NegotiateContentType(request, route.Produces, ""); str == "" {
			res = append(res, errors.InvalidResponseFormat(request.Header.Get(httpkit.HeaderAccept), route.Produces))
		}
	}

	// now bind the request with the provided binder
	// it's assumed the binder will also validate the request and return an error if the
	// request is invalid
	if binder != nil && len(res) == 0 {
		if err := binder.BindRequest(request, route); err != nil {
			res = append(res, err)
		}
	}

	if len(res) > 0 {
		return errors.CompositeValidationError(res...)
	}
	return nil
}

// ContentType gets the parsed value of a content type
func (c *Context) ContentType(request *http.Request) (string, string, *errors.ParseError) {
	if v, ok := context.GetOk(request, ctxContentType); ok {
		if val, ok := v.(*contentTypeValue); ok {
			return val.MediaType, val.Charset, nil
		}
	}

	mt, cs, err := httpkit.ContentType(request.Header)
	if err != nil {
		return "", "", err
	}
	context.Set(request, ctxContentType, &contentTypeValue{mt, cs})
	return mt, cs, nil
}

// LookupRoute looks a route up and returns true when it is found
func (c *Context) LookupRoute(request *http.Request) (*MatchedRoute, bool) {
	if route, ok := c.router.Lookup(request.Method, request.URL.Path); ok {
		return route, ok
	}
	return nil, false
}

// RouteInfo tries to match a route for this request
func (c *Context) RouteInfo(request *http.Request) (*MatchedRoute, bool) {
	if v, ok := context.GetOk(request, ctxMatchedRoute); ok {
		if val, ok := v.(*MatchedRoute); ok {
			return val, ok
		}
	}

	if route, ok := c.LookupRoute(request); ok {
		context.Set(request, ctxMatchedRoute, route)
		return route, ok
	}

	return nil, false
}

// ResponseFormat negotiates the response content type
func (c *Context) ResponseFormat(r *http.Request, offers []string) string {
	if v, ok := context.GetOk(r, ctxResponseFormat); ok {
		if val, ok := v.(string); ok {
			return val
		}
	}

	format := httputil.NegotiateContentType(r, offers, "")
	if format != "" {
		context.Set(r, ctxResponseFormat, format)
	}
	return format
}

// AllowedMethods gets the allowed methods for the path of this request
func (c *Context) AllowedMethods(request *http.Request) []string {
	return c.router.OtherMethods(request.Method, request.URL.Path)
}

// Authorize authorizes the request
func (c *Context) Authorize(request *http.Request, route *MatchedRoute) (interface{}, error) {
	if len(route.Authenticators) == 0 {
		return nil, nil
	}
	if v, ok := context.GetOk(request, ctxSecurityPrincipal); ok {
		return v, nil
	}

	for _, authenticator := range route.Authenticators {
		applies, usr, err := authenticator.Authenticate(request)
		if !applies || err != nil || usr == nil {
			continue
		}
		context.Set(request, ctxSecurityPrincipal, usr)
		return usr, nil
	}

	return nil, errors.Unauthenticated("invalid credentials")
}

// BindAndValidate binds and validates the request
func (c *Context) BindAndValidate(request *http.Request, matched *MatchedRoute) (interface{}, error) {
	if v, ok := context.GetOk(request, ctxBoundParams); ok {
		if val, ok := v.(*validation); ok {
			if len(val.result) > 0 {
				return val.bound, errors.CompositeValidationError(val.result...)
			}
			return val.bound, nil
		}
	}
	result := validateRequest(c, request, matched)
	if result != nil {
		context.Set(request, ctxBoundParams, result)
	}
	if len(result.result) > 0 {
		return result.bound, errors.CompositeValidationError(result.result...)
	}
	return result.bound, nil
}

// NotFound the default not found responder for when no route has been matched yet
func (c *Context) NotFound(rw http.ResponseWriter, r *http.Request) {
	c.Respond(rw, r, []string{c.api.DefaultProduces()}, nil, errors.NotFound("not found"))
}

// Respond renders the response after doing some content negotiation
func (c *Context) Respond(rw http.ResponseWriter, r *http.Request, produces []string, route *MatchedRoute, data interface{}) {
	offers := []string{c.api.DefaultProduces()}
	for _, mt := range produces {
		if mt != c.api.DefaultProduces() {
			offers = append(offers, mt)
		}
	}

	format := c.ResponseFormat(r, offers)
	rw.Header().Set(httpkit.HeaderContentType, format)

	if resp, ok := data.(Responder); ok {
		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		resp.WriteResponse(rw, prod)
		return
	}

	if err, ok := data.(error); ok {
		if format == "" {
			rw.Header().Set(httpkit.HeaderContentType, httpkit.JSONMime)
		}
		if route == nil || route.Operation == nil {
			c.api.ServeErrorFor("")(rw, r, err)
			return
		}
		fmt.Println("ERROR:", err)
		c.api.ServeErrorFor(route.Operation.ID)(rw, r, err)
		return
	}

	if route == nil || route.Operation == nil {
		rw.WriteHeader(200)
		if r.Method == "HEAD" {
			return
		}
		producers := c.api.ProducersFor(offers)
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	if _, code, ok := route.Operation.SuccessResponse(); ok {
		rw.WriteHeader(code)
		if code == 204 || r.Method == "HEAD" {
			return
		}

		producers := route.Producers
		prod, ok := producers[format]
		if !ok {
			panic(errors.New(http.StatusInternalServerError, "can't find a producer for "+format))
		}
		if err := prod.Produce(rw, data); err != nil {
			panic(err) // let the recovery middleware deal with this
		}
		return
	}

	c.api.ServeErrorFor(route.Operation.ID)(rw, r, errors.New(http.StatusInternalServerError, "can't produce response"))
}

// APIHandler returns a handler to serve
func (c *Context) APIHandler() http.Handler {
	return specMiddleware(c, newRouter(c, newOperationExecutor(c)))
}
<|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 hugolib

import (
	"fmt"
	"path"
	"strings"
	"sync"

	"github.com/gohugoio/hugo/output"
	"github.com/pkg/errors"

	"github.com/gohugoio/hugo/resources/page"
	"github.com/gohugoio/hugo/resources/page/pagemeta"
)

type siteRenderContext struct {
	cfg *BuildCfg

	// Zero based index for all output formats combined.
	sitesOutIdx int

	// Zero based index of the output formats configured within a Site.
	outIdx int

	multihost bool
}

// Whether to render 404.html, robotsTXT.txt which usually is rendered
// once only in the site root.
func (s siteRenderContext) renderSingletonPages() bool {
	if s.multihost {
		// 1 per site
		return s.outIdx == 0
	}

	// 1 for all sites
	return s.sitesOutIdx == 0

}

// renderPages renders pages each corresponding to a markdown file.
// TODO(bep np doc
func (s *Site) renderPages(ctx *siteRenderContext) error {

	results := make(chan error)
	pages := make(chan *pageState)
	errs := make(chan error)

	go s.errorCollator(results, errs)

	numWorkers := getGoMaxProcs() * 4

	wg := &sync.WaitGroup{}

	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go pageRenderer(ctx, s, pages, results, wg)
	}

	cfg := ctx.cfg

	if !cfg.PartialReRender && ctx.outIdx == 0 && len(s.headlessPages) > 0 {
		wg.Add(1)
		go headlessPagesPublisher(s, wg)
	}

L:
	for _, page := range s.workAllPages {
		if cfg.shouldRender(page) {
			select {
			case <-s.h.Done():
				break L
			default:
				pages <- page
			}
		}
	}

	close(pages)

	wg.Wait()

	close(results)

	err := <-errs
	if err != nil {
		return errors.Wrap(err, "failed to render pages")
	}
	return nil
}

func headlessPagesPublisher(s *Site, wg *sync.WaitGroup) {
	defer wg.Done()
	for _, p := range s.headlessPages {
		if err := p.renderResources(); err != nil {
			s.SendError(p.errorf(err, "failed to render page resources"))
		}
	}
}

func pageRenderer(
	ctx *siteRenderContext,
	s *Site,
	pages <-chan *pageState,
	results chan<- error,
	wg *sync.WaitGroup) {

	defer wg.Done()

	for p := range pages {
		f := p.outputFormat()

		// TODO(bep) get rid of this odd construct. RSS is an output format.
		if f.Name == "RSS" && !s.isEnabled(kindRSS) {
			continue
		}

		if ctx.outIdx == 0 {
			if err := p.renderResources(); err != nil {
				s.SendError(p.errorf(err, "failed to render page resources"))
				continue
			}
		}

		layouts, err := p.getLayouts()
		if err != nil {
			s.Log.ERROR.Printf("Failed to resolve layout for output %q for page %q: %s", f.Name, p, err)
			continue
		}

		targetPath := p.targetPaths().TargetFilename

		if targetPath == "" {
			s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", f.Name, p, err)
			continue
		}

		if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, layouts...); err != nil {
			results <- err
		}

		if p.paginator != nil && p.paginator.current != nil {
			if err := s.renderPaginator(p, layouts); err != nil {
				results <- err
			}
		}
	}
}

// renderPaginator must be run after the owning Page has been rendered.
func (s *Site) renderPaginator(p *pageState, layouts []string) error {

	paginatePath := s.Cfg.GetString("paginatePath")

	d := p.targetPathDescriptor
	f := p.s.rc.Format
	d.Type = f

	// Rewind
	p.paginator.current = p.paginator.current.First()

	// Write alias for page 1
	d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
	targetPaths := page.CreateTargetPaths(d)

	if err := s.writeDestAlias(targetPaths.TargetFilename, p.Permalink(), f, nil); err != nil {
		return err
	}

	// Render pages for the rest
	for current := p.paginator.current.Next(); current != nil; current = current.Next() {

		p.paginator.current = current
		d.Addends = fmt.Sprintf("/%s/%d", paginatePath, current.PageNumber())
		targetPaths := page.CreateTargetPaths(d)

		if err := s.renderAndWritePage(
			&s.PathSpec.ProcessingStats.PaginatorPages,
			p.Title(),
			targetPaths.TargetFilename, p, layouts...); err != nil {
			return err
		}

	}

	return nil
}

func (s *Site) render404() error {
	if !s.isEnabled(kind404) {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kind404,
		urlPaths: pagemeta.URLPath{
			URL: "404.html",
		},
	},
		output.HTMLFormat,
	)

	if err != nil {
		return err
	}

	nfLayouts := []string{"404.html"}

	targetPath := p.targetPaths().TargetFilename

	if targetPath == "" {
		return errors.New("failed to create targetPath for 404 page")
	}

	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, nfLayouts...)
}

func (s *Site) renderSitemap() error {
	if !s.isEnabled(kindSitemap) {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kindSitemap,
		urlPaths: pagemeta.URLPath{
			URL: s.siteCfg.sitemap.Filename,
		}},
		output.HTMLFormat,
	)

	if err != nil {
		return err
	}

	targetPath := p.targetPaths().TargetFilename

	if targetPath == "" {
		return errors.New("failed to create targetPath for sitemap")
	}

	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}

	return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, smLayouts...)
}

func (s *Site) renderRobotsTXT() error {
	if !s.isEnabled(kindRobotsTXT) {
		return nil
	}

	if !s.Cfg.GetBool("enableRobotsTXT") {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kindRobotsTXT,
		urlPaths: pagemeta.URLPath{
			URL: "robots.txt",
		},
	},
		output.RobotsTxtFormat)

	if err != nil {
		return err
	}

	rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}

	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, rLayouts...)

}

// renderAliases renders shell pages that simply have a redirect in the header.
func (s *Site) renderAliases() error {
	for _, p := range s.workAllPages {

		if len(p.Aliases()) == 0 {
			continue
		}

		for _, of := range p.OutputFormats() {
			if !of.Format.IsHTML {
				continue
			}

			plink := of.Permalink()
			f := of.Format

			for _, a := range p.Aliases() {
				isRelative := !strings.HasPrefix(a, "/")

				if isRelative {
					// Make alias relative, where "." will be on the
					// same directory level as the current page.
					// TODO(bep) ugly URLs doesn't seem to be supported in
					// aliases, I'm not sure why not.
					basePath := of.RelPermalink()
					if strings.HasSuffix(basePath, "/") {
						basePath = path.Join(basePath, "..")
					}
					a = path.Join(basePath, a)

				} else if f.Path != "" {
					// Make sure AMP and similar doesn't clash with regular aliases.
					a = path.Join(f.Path, a)
				}

				lang := p.Language().Lang

				if s.h.multihost && !strings.HasPrefix(a, "/"+lang) {
					// These need to be in its language root.
					a = path.Join(lang, a)
				}

				if err := s.writeDestAlias(a, plink, f, p); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// renderMainLanguageRedirect creates a redirect to the main language home,
// depending on if it lives in sub folder (e.g. /en) or not.
func (s *Site) renderMainLanguageRedirect() error {

	if !s.h.multilingual.enabled() || s.h.IsMultihost() {
		// No need for a redirect
		return nil
	}

	html, found := s.outputFormatsConfig.GetByName("HTML")
	if found {
		mainLang := s.h.multilingual.DefaultLang
		if s.Info.defaultContentLanguageInSubdir {
			mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil {
				return err
			}
		} else {
			mainLangURL := s.PathSpec.AbsURL("", false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil {
				return err
			}
		}
	}

	return nil
}
<commit_msg>hugolib: Buffer the render pages chan<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 hugolib

import (
	"fmt"
	"path"
	"strings"
	"sync"

	"github.com/gohugoio/hugo/output"
	"github.com/pkg/errors"

	"github.com/gohugoio/hugo/resources/page"
	"github.com/gohugoio/hugo/resources/page/pagemeta"
)

type siteRenderContext struct {
	cfg *BuildCfg

	// Zero based index for all output formats combined.
	sitesOutIdx int

	// Zero based index of the output formats configured within a Site.
	outIdx int

	multihost bool
}

// Whether to render 404.html, robotsTXT.txt which usually is rendered
// once only in the site root.
func (s siteRenderContext) renderSingletonPages() bool {
	if s.multihost {
		// 1 per site
		return s.outIdx == 0
	}

	// 1 for all sites
	return s.sitesOutIdx == 0

}

// renderPages renders pages each corresponding to a markdown file.
// TODO(bep np doc
func (s *Site) renderPages(ctx *siteRenderContext) error {

	numWorkers := getGoMaxProcs() * 4

	results := make(chan error)
	pages := make(chan *pageState, numWorkers) // buffered for performance
	errs := make(chan error)

	go s.errorCollator(results, errs)

	wg := &sync.WaitGroup{}

	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go pageRenderer(ctx, s, pages, results, wg)
	}

	cfg := ctx.cfg

	if !cfg.PartialReRender && ctx.outIdx == 0 && len(s.headlessPages) > 0 {
		wg.Add(1)
		go headlessPagesPublisher(s, wg)
	}

L:
	for _, page := range s.workAllPages {
		if cfg.shouldRender(page) {
			select {
			case <-s.h.Done():
				break L
			default:
				pages <- page
			}
		}
	}

	close(pages)

	wg.Wait()

	close(results)

	err := <-errs
	if err != nil {
		return errors.Wrap(err, "failed to render pages")
	}
	return nil
}

func headlessPagesPublisher(s *Site, wg *sync.WaitGroup) {
	defer wg.Done()
	for _, p := range s.headlessPages {
		if err := p.renderResources(); err != nil {
			s.SendError(p.errorf(err, "failed to render page resources"))
		}
	}
}

func pageRenderer(
	ctx *siteRenderContext,
	s *Site,
	pages <-chan *pageState,
	results chan<- error,
	wg *sync.WaitGroup) {

	defer wg.Done()

	for p := range pages {
		f := p.outputFormat()

		// TODO(bep) get rid of this odd construct. RSS is an output format.
		if f.Name == "RSS" && !s.isEnabled(kindRSS) {
			continue
		}

		if ctx.outIdx == 0 {
			if err := p.renderResources(); err != nil {
				s.SendError(p.errorf(err, "failed to render page resources"))
				continue
			}
		}

		layouts, err := p.getLayouts()
		if err != nil {
			s.Log.ERROR.Printf("Failed to resolve layout for output %q for page %q: %s", f.Name, p, err)
			continue
		}

		targetPath := p.targetPaths().TargetFilename

		if targetPath == "" {
			s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", f.Name, p, err)
			continue
		}

		if err := s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "page "+p.Title(), targetPath, p, layouts...); err != nil {
			results <- err
		}

		if p.paginator != nil && p.paginator.current != nil {
			if err := s.renderPaginator(p, layouts); err != nil {
				results <- err
			}
		}
	}
}

// renderPaginator must be run after the owning Page has been rendered.
func (s *Site) renderPaginator(p *pageState, layouts []string) error {

	paginatePath := s.Cfg.GetString("paginatePath")

	d := p.targetPathDescriptor
	f := p.s.rc.Format
	d.Type = f

	// Rewind
	p.paginator.current = p.paginator.current.First()

	// Write alias for page 1
	d.Addends = fmt.Sprintf("/%s/%d", paginatePath, 1)
	targetPaths := page.CreateTargetPaths(d)

	if err := s.writeDestAlias(targetPaths.TargetFilename, p.Permalink(), f, nil); err != nil {
		return err
	}

	// Render pages for the rest
	for current := p.paginator.current.Next(); current != nil; current = current.Next() {

		p.paginator.current = current
		d.Addends = fmt.Sprintf("/%s/%d", paginatePath, current.PageNumber())
		targetPaths := page.CreateTargetPaths(d)

		if err := s.renderAndWritePage(
			&s.PathSpec.ProcessingStats.PaginatorPages,
			p.Title(),
			targetPaths.TargetFilename, p, layouts...); err != nil {
			return err
		}

	}

	return nil
}

func (s *Site) render404() error {
	if !s.isEnabled(kind404) {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kind404,
		urlPaths: pagemeta.URLPath{
			URL: "404.html",
		},
	},
		output.HTMLFormat,
	)

	if err != nil {
		return err
	}

	nfLayouts := []string{"404.html"}

	targetPath := p.targetPaths().TargetFilename

	if targetPath == "" {
		return errors.New("failed to create targetPath for 404 page")
	}

	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "404 page", targetPath, p, nfLayouts...)
}

func (s *Site) renderSitemap() error {
	if !s.isEnabled(kindSitemap) {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kindSitemap,
		urlPaths: pagemeta.URLPath{
			URL: s.siteCfg.sitemap.Filename,
		}},
		output.HTMLFormat,
	)

	if err != nil {
		return err
	}

	targetPath := p.targetPaths().TargetFilename

	if targetPath == "" {
		return errors.New("failed to create targetPath for sitemap")
	}

	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}

	return s.renderAndWriteXML(&s.PathSpec.ProcessingStats.Sitemaps, "sitemap", targetPath, p, smLayouts...)
}

func (s *Site) renderRobotsTXT() error {
	if !s.isEnabled(kindRobotsTXT) {
		return nil
	}

	if !s.Cfg.GetBool("enableRobotsTXT") {
		return nil
	}

	p, err := newPageStandalone(&pageMeta{
		s:    s,
		kind: kindRobotsTXT,
		urlPaths: pagemeta.URLPath{
			URL: "robots.txt",
		},
	},
		output.RobotsTxtFormat)

	if err != nil {
		return err
	}

	rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}

	return s.renderAndWritePage(&s.PathSpec.ProcessingStats.Pages, "Robots Txt", p.targetPaths().TargetFilename, p, rLayouts...)

}

// renderAliases renders shell pages that simply have a redirect in the header.
func (s *Site) renderAliases() error {
	for _, p := range s.workAllPages {

		if len(p.Aliases()) == 0 {
			continue
		}

		for _, of := range p.OutputFormats() {
			if !of.Format.IsHTML {
				continue
			}

			plink := of.Permalink()
			f := of.Format

			for _, a := range p.Aliases() {
				isRelative := !strings.HasPrefix(a, "/")

				if isRelative {
					// Make alias relative, where "." will be on the
					// same directory level as the current page.
					// TODO(bep) ugly URLs doesn't seem to be supported in
					// aliases, I'm not sure why not.
					basePath := of.RelPermalink()
					if strings.HasSuffix(basePath, "/") {
						basePath = path.Join(basePath, "..")
					}
					a = path.Join(basePath, a)

				} else if f.Path != "" {
					// Make sure AMP and similar doesn't clash with regular aliases.
					a = path.Join(f.Path, a)
				}

				lang := p.Language().Lang

				if s.h.multihost && !strings.HasPrefix(a, "/"+lang) {
					// These need to be in its language root.
					a = path.Join(lang, a)
				}

				if err := s.writeDestAlias(a, plink, f, p); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// renderMainLanguageRedirect creates a redirect to the main language home,
// depending on if it lives in sub folder (e.g. /en) or not.
func (s *Site) renderMainLanguageRedirect() error {

	if !s.h.multilingual.enabled() || s.h.IsMultihost() {
		// No need for a redirect
		return nil
	}

	html, found := s.outputFormatsConfig.GetByName("HTML")
	if found {
		mainLang := s.h.multilingual.DefaultLang
		if s.Info.defaultContentLanguageInSubdir {
			mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, "/", mainLangURL, html, nil); err != nil {
				return err
			}
		} else {
			mainLangURL := s.PathSpec.AbsURL("", false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, html, nil); err != nil {
				return err
			}
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 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 hugolib

import (
	"fmt"
	"path"
	"sync"
	"time"

	"github.com/spf13/hugo/helpers"

	"github.com/spf13/hugo/output"

	bp "github.com/spf13/hugo/bufferpool"
)

// renderPages renders pages each corresponding to a markdown file.
// TODO(bep np doc
func (s *Site) renderPages() error {

	results := make(chan error)
	pages := make(chan *Page)
	errs := make(chan error)

	go errorCollator(results, errs)

	numWorkers := getGoMaxProcs() * 4

	wg := &sync.WaitGroup{}

	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go pageRenderer(s, pages, results, wg)
	}

	for _, page := range s.Pages {
		pages <- page
	}

	close(pages)

	wg.Wait()

	close(results)

	err := <-errs
	if err != nil {
		return fmt.Errorf("Error(s) rendering pages: %s", err)
	}
	return nil
}

func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) {
	defer wg.Done()

	for page := range pages {

		for i, outFormat := range page.outputFormats {

			var (
				pageOutput *PageOutput
				err        error
			)

			if i == 0 {
				pageOutput, err = newPageOutput(page, false, outFormat)
				page.mainPageOutput = pageOutput
			}

			if outFormat != page.s.rc.Format {
				// Will be rendered  ... later.
				continue
			}

			if pageOutput == nil {
				pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
			}

			if err != nil {
				s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, page, err)
				continue
			}

			var layouts []string

			if page.selfLayout != "" {
				layouts = []string{page.selfLayout}
			} else {
				layouts, err = s.layouts(pageOutput)
				if err != nil {
					s.Log.ERROR.Printf("Failed to resolve layout output %q for page %q: %s", outFormat.Name, page, err)
					continue
				}
			}

			switch pageOutput.outputFormat.Name {

			case "RSS":
				if err := s.renderRSS(pageOutput); err != nil {
					results <- err
				}
			default:
				targetPath, err := pageOutput.targetPath()
				if err != nil {
					s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", outFormat.Name, page, err)
					continue
				}

				s.Log.DEBUG.Printf("Render %s to %q with layouts %q", pageOutput.Kind, targetPath, layouts)

				if err := s.renderAndWritePage("page "+pageOutput.FullFilePath(), targetPath, pageOutput, layouts...); err != nil {
					results <- err
				}

				if pageOutput.IsNode() {
					if err := s.renderPaginator(pageOutput); err != nil {
						results <- err
					}
				}
			}

		}
	}
}

// renderPaginator must be run after the owning Page has been rendered.
func (s *Site) renderPaginator(p *PageOutput) error {
	if p.paginator != nil {
		s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
		paginatePath := s.Cfg.GetString("paginatePath")

		// write alias for page 1
		addend := fmt.Sprintf("/%s/%d", paginatePath, 1)
		target, err := p.createTargetPath(p.outputFormat, addend)
		if err != nil {
			return err
		}

		// TODO(bep) do better
		link := newOutputFormat(p.Page, p.outputFormat).Permalink()
		if err := s.writeDestAlias(target, link, nil); err != nil {
			return err
		}

		pagers := p.paginator.Pagers()

		for i, pager := range pagers {
			if i == 0 {
				// already created
				continue
			}

			pagerNode, err := p.copy()
			if err != nil {
				return err
			}

			pagerNode.origOnCopy = p.Page

			pagerNode.paginator = pager
			if pager.TotalPages() > 0 {
				first, _ := pager.page(0)
				pagerNode.Date = first.Date
				pagerNode.Lastmod = first.Lastmod
			}

			pageNumber := i + 1
			addend := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
			targetPath, _ := p.targetPath(addend)
			layouts, err := p.layouts()

			if err != nil {
				return err
			}

			if err := s.renderAndWritePage(
				pagerNode.Title,
				targetPath, pagerNode, layouts...); err != nil {
				return err
			}

		}
	}
	return nil
}

func (s *Site) renderRSS(p *PageOutput) error {

	if !s.isEnabled(kindRSS) {
		return nil
	}

	if s.Cfg.GetBool("disableRSS") {
		return nil
	}

	p.Kind = kindRSS

	// TODO(bep) we zero the date here to get the number of diffs down in
	// testing. But this should be set back later; the RSS feed should
	// inherit the publish date from the node it represents.
	if p.Kind == KindTaxonomy {
		var zeroDate time.Time
		p.Date = zeroDate
	}

	limit := s.Cfg.GetInt("rssLimit")
	if limit >= 0 && len(p.Pages) > limit {
		p.Pages = p.Pages[:limit]
		p.Data["Pages"] = p.Pages
	}

	layouts, err := s.layoutHandler.For(
		p.layoutDescriptor,
		"",
		p.outputFormat)
	if err != nil {
		return err
	}

	targetPath, err := p.targetPath()
	if err != nil {
		return err
	}

	return s.renderAndWriteXML(p.Title,
		targetPath, p, layouts...)
}

func (s *Site) render404() error {
	if !s.isEnabled(kind404) {
		return nil
	}

	if s.Cfg.GetBool("disable404") {
		return nil
	}

	if s.owner.multilingual.enabled() && (s.Language.Lang != s.owner.multilingual.DefaultLang.Lang) {
		return nil
	}

	p := s.newNodePage(kind404)

	p.Title = "404 Page not found"
	p.Data["Pages"] = s.Pages
	p.Pages = s.Pages
	p.URLPath.URL = "404.html"

	if err := p.initTargetPathDescriptor(); err != nil {
		return err
	}

	nfLayouts := []string{"404.html"}

	pageOutput, err := newPageOutput(p, false, output.HTMLFormat)
	if err != nil {
		return err
	}

	return s.renderAndWritePage("404 page", "404.html", pageOutput, s.appendThemeTemplates(nfLayouts)...)

}

func (s *Site) renderSitemap() error {
	if !s.isEnabled(kindSitemap) {
		return nil
	}

	if s.Cfg.GetBool("disableSitemap") {
		return nil
	}

	sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap"))

	n := s.newNodePage(kindSitemap)

	// Include all pages (regular, home page, taxonomies etc.)
	pages := s.Pages

	page := s.newNodePage(kindSitemap)
	page.URLPath.URL = ""
	if err := page.initTargetPathDescriptor(); err != nil {
		return err
	}
	page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
	page.Sitemap.Priority = sitemapDefault.Priority
	page.Sitemap.Filename = sitemapDefault.Filename

	n.Data["Pages"] = pages
	n.Pages = pages

	// TODO(bep) we have several of these
	if err := page.initTargetPathDescriptor(); err != nil {
		return err
	}

	// TODO(bep) this should be done somewhere else
	for _, page := range pages {
		if page.Sitemap.ChangeFreq == "" {
			page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
		}

		if page.Sitemap.Priority == -1 {
			page.Sitemap.Priority = sitemapDefault.Priority
		}

		if page.Sitemap.Filename == "" {
			page.Sitemap.Filename = sitemapDefault.Filename
		}
	}

	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
	addLanguagePrefix := n.Site.IsMultiLingual()

	return s.renderAndWriteXML("sitemap",
		n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...)
}

func (s *Site) renderRobotsTXT() error {
	if !s.isEnabled(kindRobotsTXT) {
		return nil
	}

	if !s.Cfg.GetBool("enableRobotsTXT") {
		return nil
	}

	n := s.newNodePage(kindRobotsTXT)
	if err := n.initTargetPathDescriptor(); err != nil {
		return err
	}
	n.Data["Pages"] = s.Pages
	n.Pages = s.Pages

	rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
	outBuffer := bp.GetBuffer()
	defer bp.PutBuffer(outBuffer)
	if err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...); err != nil {
		helpers.DistinctWarnLog.Println(err)
		return nil
	}

	if outBuffer.Len() == 0 {
		return nil
	}

	return s.publish("robots.txt", outBuffer)
}

// renderAliases renders shell pages that simply have a redirect in the header.
func (s *Site) renderAliases() error {
	for _, p := range s.Pages {
		if len(p.Aliases) == 0 {
			continue
		}

		for _, f := range p.outputFormats {
			if !f.IsHTML {
				continue
			}

			o := newOutputFormat(p, f)
			plink := o.Permalink()

			for _, a := range p.Aliases {
				if f.Path != "" {
					// Make sure AMP and similar doesn't clash with regular aliases.
					a = path.Join(a, f.Path)
				}

				if err := s.writeDestAlias(a, plink, p); err != nil {
					return err
				}
			}
		}
	}

	if s.owner.multilingual.enabled() {
		mainLang := s.owner.multilingual.DefaultLang
		if s.Info.defaultContentLanguageInSubdir {
			mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, "/", mainLangURL, nil); err != nil {
				return err
			}
		} else {
			mainLangURL := s.PathSpec.AbsURL("", false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, nil); err != nil {
				return err
			}
		}
	}

	return nil
}
<commit_msg>hugolib: Make the RSS feed use the date for the node it represents<commit_after>// Copyright 2016 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 hugolib

import (
	"fmt"
	"path"
	"sync"

	"github.com/spf13/hugo/helpers"

	"github.com/spf13/hugo/output"

	bp "github.com/spf13/hugo/bufferpool"
)

// renderPages renders pages each corresponding to a markdown file.
// TODO(bep np doc
func (s *Site) renderPages() error {

	results := make(chan error)
	pages := make(chan *Page)
	errs := make(chan error)

	go errorCollator(results, errs)

	numWorkers := getGoMaxProcs() * 4

	wg := &sync.WaitGroup{}

	for i := 0; i < numWorkers; i++ {
		wg.Add(1)
		go pageRenderer(s, pages, results, wg)
	}

	for _, page := range s.Pages {
		pages <- page
	}

	close(pages)

	wg.Wait()

	close(results)

	err := <-errs
	if err != nil {
		return fmt.Errorf("Error(s) rendering pages: %s", err)
	}
	return nil
}

func pageRenderer(s *Site, pages <-chan *Page, results chan<- error, wg *sync.WaitGroup) {
	defer wg.Done()

	for page := range pages {

		for i, outFormat := range page.outputFormats {

			var (
				pageOutput *PageOutput
				err        error
			)

			if i == 0 {
				pageOutput, err = newPageOutput(page, false, outFormat)
				page.mainPageOutput = pageOutput
			}

			if outFormat != page.s.rc.Format {
				// Will be rendered  ... later.
				continue
			}

			if pageOutput == nil {
				pageOutput, err = page.mainPageOutput.copyWithFormat(outFormat)
			}

			if err != nil {
				s.Log.ERROR.Printf("Failed to create output page for type %q for page %q: %s", outFormat.Name, page, err)
				continue
			}

			var layouts []string

			if page.selfLayout != "" {
				layouts = []string{page.selfLayout}
			} else {
				layouts, err = s.layouts(pageOutput)
				if err != nil {
					s.Log.ERROR.Printf("Failed to resolve layout output %q for page %q: %s", outFormat.Name, page, err)
					continue
				}
			}

			switch pageOutput.outputFormat.Name {

			case "RSS":
				if err := s.renderRSS(pageOutput); err != nil {
					results <- err
				}
			default:
				targetPath, err := pageOutput.targetPath()
				if err != nil {
					s.Log.ERROR.Printf("Failed to create target path for output %q for page %q: %s", outFormat.Name, page, err)
					continue
				}

				s.Log.DEBUG.Printf("Render %s to %q with layouts %q", pageOutput.Kind, targetPath, layouts)

				if err := s.renderAndWritePage("page "+pageOutput.FullFilePath(), targetPath, pageOutput, layouts...); err != nil {
					results <- err
				}

				if pageOutput.IsNode() {
					if err := s.renderPaginator(pageOutput); err != nil {
						results <- err
					}
				}
			}

		}
	}
}

// renderPaginator must be run after the owning Page has been rendered.
func (s *Site) renderPaginator(p *PageOutput) error {
	if p.paginator != nil {
		s.Log.DEBUG.Printf("Render paginator for page %q", p.Path())
		paginatePath := s.Cfg.GetString("paginatePath")

		// write alias for page 1
		addend := fmt.Sprintf("/%s/%d", paginatePath, 1)
		target, err := p.createTargetPath(p.outputFormat, addend)
		if err != nil {
			return err
		}

		// TODO(bep) do better
		link := newOutputFormat(p.Page, p.outputFormat).Permalink()
		if err := s.writeDestAlias(target, link, nil); err != nil {
			return err
		}

		pagers := p.paginator.Pagers()

		for i, pager := range pagers {
			if i == 0 {
				// already created
				continue
			}

			pagerNode, err := p.copy()
			if err != nil {
				return err
			}

			pagerNode.origOnCopy = p.Page

			pagerNode.paginator = pager
			if pager.TotalPages() > 0 {
				first, _ := pager.page(0)
				pagerNode.Date = first.Date
				pagerNode.Lastmod = first.Lastmod
			}

			pageNumber := i + 1
			addend := fmt.Sprintf("/%s/%d", paginatePath, pageNumber)
			targetPath, _ := p.targetPath(addend)
			layouts, err := p.layouts()

			if err != nil {
				return err
			}

			if err := s.renderAndWritePage(
				pagerNode.Title,
				targetPath, pagerNode, layouts...); err != nil {
				return err
			}

		}
	}
	return nil
}

func (s *Site) renderRSS(p *PageOutput) error {

	if !s.isEnabled(kindRSS) {
		return nil
	}

	if s.Cfg.GetBool("disableRSS") {
		return nil
	}

	p.Kind = kindRSS

	limit := s.Cfg.GetInt("rssLimit")
	if limit >= 0 && len(p.Pages) > limit {
		p.Pages = p.Pages[:limit]
		p.Data["Pages"] = p.Pages
	}

	layouts, err := s.layoutHandler.For(
		p.layoutDescriptor,
		"",
		p.outputFormat)
	if err != nil {
		return err
	}

	targetPath, err := p.targetPath()
	if err != nil {
		return err
	}

	return s.renderAndWriteXML(p.Title,
		targetPath, p, layouts...)
}

func (s *Site) render404() error {
	if !s.isEnabled(kind404) {
		return nil
	}

	if s.Cfg.GetBool("disable404") {
		return nil
	}

	if s.owner.multilingual.enabled() && (s.Language.Lang != s.owner.multilingual.DefaultLang.Lang) {
		return nil
	}

	p := s.newNodePage(kind404)

	p.Title = "404 Page not found"
	p.Data["Pages"] = s.Pages
	p.Pages = s.Pages
	p.URLPath.URL = "404.html"

	if err := p.initTargetPathDescriptor(); err != nil {
		return err
	}

	nfLayouts := []string{"404.html"}

	pageOutput, err := newPageOutput(p, false, output.HTMLFormat)
	if err != nil {
		return err
	}

	return s.renderAndWritePage("404 page", "404.html", pageOutput, s.appendThemeTemplates(nfLayouts)...)

}

func (s *Site) renderSitemap() error {
	if !s.isEnabled(kindSitemap) {
		return nil
	}

	if s.Cfg.GetBool("disableSitemap") {
		return nil
	}

	sitemapDefault := parseSitemap(s.Cfg.GetStringMap("sitemap"))

	n := s.newNodePage(kindSitemap)

	// Include all pages (regular, home page, taxonomies etc.)
	pages := s.Pages

	page := s.newNodePage(kindSitemap)
	page.URLPath.URL = ""
	if err := page.initTargetPathDescriptor(); err != nil {
		return err
	}
	page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
	page.Sitemap.Priority = sitemapDefault.Priority
	page.Sitemap.Filename = sitemapDefault.Filename

	n.Data["Pages"] = pages
	n.Pages = pages

	// TODO(bep) we have several of these
	if err := page.initTargetPathDescriptor(); err != nil {
		return err
	}

	// TODO(bep) this should be done somewhere else
	for _, page := range pages {
		if page.Sitemap.ChangeFreq == "" {
			page.Sitemap.ChangeFreq = sitemapDefault.ChangeFreq
		}

		if page.Sitemap.Priority == -1 {
			page.Sitemap.Priority = sitemapDefault.Priority
		}

		if page.Sitemap.Filename == "" {
			page.Sitemap.Filename = sitemapDefault.Filename
		}
	}

	smLayouts := []string{"sitemap.xml", "_default/sitemap.xml", "_internal/_default/sitemap.xml"}
	addLanguagePrefix := n.Site.IsMultiLingual()

	return s.renderAndWriteXML("sitemap",
		n.addLangPathPrefixIfFlagSet(page.Sitemap.Filename, addLanguagePrefix), n, s.appendThemeTemplates(smLayouts)...)
}

func (s *Site) renderRobotsTXT() error {
	if !s.isEnabled(kindRobotsTXT) {
		return nil
	}

	if !s.Cfg.GetBool("enableRobotsTXT") {
		return nil
	}

	n := s.newNodePage(kindRobotsTXT)
	if err := n.initTargetPathDescriptor(); err != nil {
		return err
	}
	n.Data["Pages"] = s.Pages
	n.Pages = s.Pages

	rLayouts := []string{"robots.txt", "_default/robots.txt", "_internal/_default/robots.txt"}
	outBuffer := bp.GetBuffer()
	defer bp.PutBuffer(outBuffer)
	if err := s.renderForLayouts("robots", n, outBuffer, s.appendThemeTemplates(rLayouts)...); err != nil {
		helpers.DistinctWarnLog.Println(err)
		return nil
	}

	if outBuffer.Len() == 0 {
		return nil
	}

	return s.publish("robots.txt", outBuffer)
}

// renderAliases renders shell pages that simply have a redirect in the header.
func (s *Site) renderAliases() error {
	for _, p := range s.Pages {
		if len(p.Aliases) == 0 {
			continue
		}

		for _, f := range p.outputFormats {
			if !f.IsHTML {
				continue
			}

			o := newOutputFormat(p, f)
			plink := o.Permalink()

			for _, a := range p.Aliases {
				if f.Path != "" {
					// Make sure AMP and similar doesn't clash with regular aliases.
					a = path.Join(a, f.Path)
				}

				if err := s.writeDestAlias(a, plink, p); err != nil {
					return err
				}
			}
		}
	}

	if s.owner.multilingual.enabled() {
		mainLang := s.owner.multilingual.DefaultLang
		if s.Info.defaultContentLanguageInSubdir {
			mainLangURL := s.PathSpec.AbsURL(mainLang.Lang, false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, "/", mainLangURL, nil); err != nil {
				return err
			}
		} else {
			mainLangURL := s.PathSpec.AbsURL("", false)
			s.Log.DEBUG.Printf("Write redirect to main language %s: %s", mainLang, mainLangURL)
			if err := s.publishDestAlias(true, mainLang.Lang, mainLangURL, nil); err != nil {
				return err
			}
		}
	}

	return nil
}
<|endoftext|>


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

package storage

import (
	"sync"
	"time"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/config"
	"github.com/cockroachdb/cockroach/gossip"
	"github.com/cockroachdb/cockroach/keys"
	"github.com/cockroachdb/cockroach/roachpb"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

const (
	// replicaGCQueueMaxSize is the max size of the gc queue.
	replicaGCQueueMaxSize = 100

	// replicaGCQueueTimerDuration is the duration between GCs of queued replicas.
	replicaGCQueueTimerDuration = 10 * time.Second

	// ReplicaGCQueueInactivityThreshold is the inactivity duration after which
	// a range will be considered for garbage collection. Exported for testing.
	ReplicaGCQueueInactivityThreshold = 10 * 24 * time.Hour // 10 days
)

// replicaGCQueue manages a queue of replicas to be considered for garbage
// collections. The GC process asynchronously removes local data for
// ranges that have been rebalanced away from this store.
type replicaGCQueue struct {
	baseQueue
	db     *client.DB
	locker sync.Locker
}

// newReplicaGCQueue returns a new instance of replicaGCQueue.
func newReplicaGCQueue(db *client.DB, gossip *gossip.Gossip, locker sync.Locker) *replicaGCQueue {
	q := &replicaGCQueue{
		db:     db,
		locker: locker,
	}
	q.baseQueue = makeBaseQueue("replicaGC", q, gossip, replicaGCQueueMaxSize)
	return q
}

func (*replicaGCQueue) needsLeaderLease() bool {
	return false
}

func (*replicaGCQueue) acceptsUnsplitRanges() bool {
	return true
}

// shouldQueue determines whether a replica should be queued for GC, and
// if so at what priority. Replicas which have been inactive for longer
// than ReplicaGCQueueInactivityThreshold are considered for possible GC
// at equal priority.
func (*replicaGCQueue) shouldQueue(now roachpb.Timestamp, rng *Replica,
	_ *config.SystemConfig) (bool, float64) {

	if l := rng.getLease(); l.Expiration.Add(ReplicaGCQueueInactivityThreshold.Nanoseconds(), 0).Less(now) {
		return true, 0
	}
	return false, 0
}

// process performs a consistent lookup on the range descriptor to see if we are
// still a member of the range.
func (q *replicaGCQueue) process(now roachpb.Timestamp, rng *Replica, _ *config.SystemConfig) error {
	desc := rng.Desc()

	// Calls to RangeLookup typically use inconsistent reads, but we
	// want to do a consistent read here. This is important when we are
	// considering one of the metadata ranges: we must not do an
	// inconsistent lookup in our own copy of the range.
	b := &client.Batch{}
	b.InternalAddRequest(&roachpb.RangeLookupRequest{
		Span: roachpb.Span{
			Key: keys.RangeMetaKey(desc.StartKey),
		},
		MaxRanges: 1,
	})
	br, err := q.db.RunWithResponse(b)
	if err != nil {
		return err
	}
	reply := br.Responses[0].GetInner().(*roachpb.RangeLookupResponse)

	if len(reply.Ranges) != 1 {
		return util.Errorf("expected 1 range descriptor, got %d", len(reply.Ranges))
	}

	replyDesc := reply.Ranges[0]
	currentMember := false
	if me := rng.GetReplica(); me != nil {
		for _, rep := range replyDesc.Replicas {
			if rep.StoreID == me.StoreID {
				currentMember = true
				break
			}
		}
	}

	if !currentMember {
		// We are no longer a member of this range; clean up our local data.
		if log.V(1) {
			log.Infof("destroying local data from range %d", desc.RangeID)
		}
		if err := rng.store.RemoveReplica(rng); err != nil {
			return err
		}

		// Lock the store to prevent a new replica of the range from being
		// added while we're deleting the previous one. We'd really like
		// to do this before calling RemoveReplica, but this could
		// deadlock with other work on the Store.processRaft goroutine.
		// Instead, we check after acquiring the lock to make sure the
		// range is still absent.
		q.locker.Lock()
		defer q.locker.Unlock()

		if _, err := rng.store.GetReplica(desc.RangeID); err == nil {
			log.Infof("replica recreated during deletion; aborting deletion")
			return nil
		}

		if err := rng.Destroy(); err != nil {
			return err
		}
	} else if desc.RangeID != desc.RangeID {
		// If we get a different  range ID back, then the range has been merged
		// away. But currentMember is true, so we are still a member of the
		// subsuming range. Shut down raft processing for the former range
		// and delete any remaining metadata, but do not delete the data.
		if log.V(1) {
			log.Infof("removing merged range %d", desc.RangeID)
		}
		if err := rng.store.RemoveReplica(rng); err != nil {
			return err
		}

		// TODO(bdarnell): remove raft logs and other metadata (while leaving a
		// tombstone). Add tests for GC of merged ranges.
	} else {
		// This range is a current member of the raft group. Acquire the lease
		// to avoid processing this range again before the next inactivity threshold.
		if err := rng.requestLeaderLease(now); err != nil {
			if _, ok := err.(*roachpb.LeaseRejectedError); !ok {
				if log.V(1) {
					log.Infof("unable to acquire lease from valid range %s: %s", rng, err)
				}
			}
		}
	}

	return nil
}

func (*replicaGCQueue) timer() time.Duration {
	return replicaGCQueueTimerDuration
}
<commit_msg>storage: remove branch<commit_after>// Copyright 2015 The Cockroach Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License. See the AUTHORS file
// for names of contributors.
//
// Author: Ben Darnell

package storage

import (
	"sync"
	"time"

	"github.com/cockroachdb/cockroach/client"
	"github.com/cockroachdb/cockroach/config"
	"github.com/cockroachdb/cockroach/gossip"
	"github.com/cockroachdb/cockroach/keys"
	"github.com/cockroachdb/cockroach/roachpb"
	"github.com/cockroachdb/cockroach/util"
	"github.com/cockroachdb/cockroach/util/log"
)

const (
	// replicaGCQueueMaxSize is the max size of the gc queue.
	replicaGCQueueMaxSize = 100

	// replicaGCQueueTimerDuration is the duration between GCs of queued replicas.
	replicaGCQueueTimerDuration = 10 * time.Second

	// ReplicaGCQueueInactivityThreshold is the inactivity duration after which
	// a range will be considered for garbage collection. Exported for testing.
	ReplicaGCQueueInactivityThreshold = 10 * 24 * time.Hour // 10 days
)

// replicaGCQueue manages a queue of replicas to be considered for garbage
// collections. The GC process asynchronously removes local data for
// ranges that have been rebalanced away from this store.
type replicaGCQueue struct {
	baseQueue
	db     *client.DB
	locker sync.Locker
}

// newReplicaGCQueue returns a new instance of replicaGCQueue.
func newReplicaGCQueue(db *client.DB, gossip *gossip.Gossip, locker sync.Locker) *replicaGCQueue {
	q := &replicaGCQueue{
		db:     db,
		locker: locker,
	}
	q.baseQueue = makeBaseQueue("replicaGC", q, gossip, replicaGCQueueMaxSize)
	return q
}

func (*replicaGCQueue) needsLeaderLease() bool {
	return false
}

func (*replicaGCQueue) acceptsUnsplitRanges() bool {
	return true
}

// shouldQueue determines whether a replica should be queued for GC, and
// if so at what priority. Replicas which have been inactive for longer
// than ReplicaGCQueueInactivityThreshold are considered for possible GC
// at equal priority.
func (*replicaGCQueue) shouldQueue(now roachpb.Timestamp, rng *Replica,
	_ *config.SystemConfig) (bool, float64) {

	return rng.getLease().Expiration.Add(
		ReplicaGCQueueInactivityThreshold.Nanoseconds(), 0,
	).Less(now), 0
}

// process performs a consistent lookup on the range descriptor to see if we are
// still a member of the range.
func (q *replicaGCQueue) process(now roachpb.Timestamp, rng *Replica, _ *config.SystemConfig) error {
	desc := rng.Desc()

	// Calls to RangeLookup typically use inconsistent reads, but we
	// want to do a consistent read here. This is important when we are
	// considering one of the metadata ranges: we must not do an
	// inconsistent lookup in our own copy of the range.
	b := &client.Batch{}
	b.InternalAddRequest(&roachpb.RangeLookupRequest{
		Span: roachpb.Span{
			Key: keys.RangeMetaKey(desc.StartKey),
		},
		MaxRanges: 1,
	})
	br, err := q.db.RunWithResponse(b)
	if err != nil {
		return err
	}
	reply := br.Responses[0].GetInner().(*roachpb.RangeLookupResponse)

	if len(reply.Ranges) != 1 {
		return util.Errorf("expected 1 range descriptor, got %d", len(reply.Ranges))
	}

	replyDesc := reply.Ranges[0]
	currentMember := false
	if me := rng.GetReplica(); me != nil {
		for _, rep := range replyDesc.Replicas {
			if rep.StoreID == me.StoreID {
				currentMember = true
				break
			}
		}
	}

	if !currentMember {
		// We are no longer a member of this range; clean up our local data.
		if log.V(1) {
			log.Infof("destroying local data from range %d", desc.RangeID)
		}
		if err := rng.store.RemoveReplica(rng); err != nil {
			return err
		}

		// Lock the store to prevent a new replica of the range from being
		// added while we're deleting the previous one. We'd really like
		// to do this before calling RemoveReplica, but this could
		// deadlock with other work on the Store.processRaft goroutine.
		// Instead, we check after acquiring the lock to make sure the
		// range is still absent.
		q.locker.Lock()
		defer q.locker.Unlock()

		if _, err := rng.store.GetReplica(desc.RangeID); err == nil {
			log.Infof("replica recreated during deletion; aborting deletion")
			return nil
		}

		if err := rng.Destroy(); err != nil {
			return err
		}
	} else if desc.RangeID != desc.RangeID {
		// If we get a different  range ID back, then the range has been merged
		// away. But currentMember is true, so we are still a member of the
		// subsuming range. Shut down raft processing for the former range
		// and delete any remaining metadata, but do not delete the data.
		if log.V(1) {
			log.Infof("removing merged range %d", desc.RangeID)
		}
		if err := rng.store.RemoveReplica(rng); err != nil {
			return err
		}

		// TODO(bdarnell): remove raft logs and other metadata (while leaving a
		// tombstone). Add tests for GC of merged ranges.
	} else {
		// This range is a current member of the raft group. Acquire the lease
		// to avoid processing this range again before the next inactivity threshold.
		if err := rng.requestLeaderLease(now); err != nil {
			if _, ok := err.(*roachpb.LeaseRejectedError); !ok {
				if log.V(1) {
					log.Infof("unable to acquire lease from valid range %s: %s", rng, err)
				}
			}
		}
	}

	return nil
}

func (*replicaGCQueue) timer() time.Duration {
	return replicaGCQueueTimerDuration
}
<|endoftext|>


<commit_before>package requestHandler

import (
	"encoding/json"
	"fmt"
	"github.com/zwirec/TGChatScanner/TGBotApi"
	"io/ioutil"
	"net/http"
	"regexp"
	"log"
)

func BotUpdateHanlder(w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	logger := req.Context().Value(loggerContextKey).(*log.Logger)
	if err != nil {
		logger.Printf("Error during handling request on %s : %s", req.URL.String(), err)
		return
	}

	var update TGBotApi.Update
	err = json.Unmarshal(body, &update)
	toLog,_ := json.MarshalIndent(body,"","      ")
	logger.Printf("%+v", toLog)
	if err != nil {
		logger.Printf("Error during unmarshaling request on %s : %s", req.URL.String(), err)
		return
	}
	if pl := len(update.Message.Photo);pl != 0 {
		photo := update.Message.Photo[pl - 1]
		ctx := map[string]interface{}{}
		ctx["From"] = update.Message.From
		fb := &FileBasic{
			FileId:photo.FileId,
			Type:"photo",
			Context:ctx,
		}
		appContext.DownloadRequests <- fb
	} else if update.Message.Entities[0].Type == "bot_command" {
		if err := AddSubsription(update.Message, appContext.Cache); err != nil {
			logger.Println(err)
			return
		}
	}
	w.WriteHeader(http.StatusOK)
}

func AddSubsription(message TGBotApi.Message, cache *MemoryCache) error {
	r := regexp.MustCompile(`\/(startgroup|start)?\s+(?P<token>[[:alnum:]]+)`)
	command := r.FindStringSubmatch(message.Text)
	if len(command) == 0 {
		return fmt.Errorf("unexpected command %s", message.Text)
	}

	userKey := command[2]
	userId, ok := cache.Get(userKey)
	if !ok {
		return fmt.Errorf("user not found, key %s", userKey)
	}

	chatId := message.From.Id

	//TODO: store subscripton in database
	return fmt.Errorf("New subscription: %s, $s", userId, chatId)
}
<commit_msg>debug stuff removed<commit_after>package requestHandler

import (
	"encoding/json"
	"fmt"
	"github.com/zwirec/TGChatScanner/TGBotApi"
	"io/ioutil"
	"net/http"
	"regexp"
	"log"
)

func BotUpdateHanlder(w http.ResponseWriter, req *http.Request) {
	body, err := ioutil.ReadAll(req.Body)
	logger := req.Context().Value(loggerContextKey).(*log.Logger)
	if err != nil {
		logger.Printf("Error during handling request on %s : %s", req.URL.String(), err)
		return
	}

	var update TGBotApi.Update
	err = json.Unmarshal(body, &update)
	if err != nil {
		logger.Printf("Error during unmarshaling request on %s : %s", req.URL.String(), err)
		return
	}
	if pl := len(update.Message.Photo);pl != 0 {
		photo := update.Message.Photo[pl - 1]
		ctx := map[string]interface{}{}
		ctx["From"] = update.Message.From
		fb := &FileBasic{
			FileId:photo.FileId,
			Type:"photo",
			Context:ctx,
		}
		appContext.DownloadRequests <- fb
	} else if update.Message.Entities[0].Type == "bot_command" {
		if err := AddSubsription(update.Message, appContext.Cache); err != nil {
			logger.Println(err)
			return
		}
	}
	w.WriteHeader(http.StatusOK)
}

func AddSubsription(message TGBotApi.Message, cache *MemoryCache) error {
	r := regexp.MustCompile(`\/(startgroup|start)?\s+(?P<token>[[:alnum:]]+)`)
	command := r.FindStringSubmatch(message.Text)
	if len(command) == 0 {
		return fmt.Errorf("unexpected command %s", message.Text)
	}

	userKey := command[2]
	userId, ok := cache.Get(userKey)
	if !ok {
		return fmt.Errorf("user not found, key %s", userKey)
	}

	chatId := message.From.Id

	//TODO: store subscripton in database
	return fmt.Errorf("New subscription: %s, $s", userId, chatId)
}
<|endoftext|>


<commit_before>package rest

import (
	"bytes"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"text/template"
	"time"
)

// TODO Future improvements:
// * support %{strftime}t ?
// * support %{<header>}o to print headers

// AccessLogFormat defines the format of the access log record.
// This implementation is a subset of Apache mod_log_config.
// (See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html)
//
//   %b content length in bytes, - if 0
//   %B content length in bytes
//   %D response elapsed time in microseconds
//   %h remote address
//   %H server protocol
//   %l identd logname, not supported, -
//   %m http method
//   %P process id
//   %q query string
//   %r first line of the request
//   %s status code
//   %S status code preceeded by a terminal color
//   %t time of the request
//   %T response elapsed time in seconds, 3 decimals
//   %u remote user, - if missing
//   %{User-Agent}i user agent, - if missing
//   %{Referer}i referer, - is missing
//
// Some predefined formats are provided as contants.
type AccessLogFormat string

const (
	// CommonLogFormat is the Common Log Format (CLF).
	CommonLogFormat = "%h %l %u %t \"%r\" %s %b"

	// CombinedLogFormat is the NCSA extended/combined log format.
	CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""

	// DefaultLogFormat is the default format, colored output and response time, convenient for development.
	DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
)

// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that should be in the wrapped
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogApacheMiddleware struct {

	// Logger points to the logger object used by this middleware, it defaults to
	// log.New(os.Stderr, "", 0).
	Logger *log.Logger

	// Format defines the format of the access log record. See AccessLogFormat for the details.
	// It defaults to DefaultLogFormat.
	Format AccessLogFormat

	textTemplate *template.Template
}

// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

	// set the default Logger
	if mw.Logger == nil {
		mw.Logger = log.New(os.Stderr, "", 0)
	}

	// set default format
	if mw.Format == "" {
		mw.Format = DefaultLogFormat
	}

	mw.convertFormat()

	return func(w ResponseWriter, r *Request) {

		// call the handler
		h(w, r)

		util := &accessLogUtil{w, r}

		mw.Logger.Print(mw.executeTextTemplate(util))
	}
}

var apacheAdapter = strings.NewReplacer(
	"%b", "{{.BytesWritten | dashIf0}}",
	"%B", "{{.BytesWritten}}",
	"%D", "{{.ResponseTime | microseconds}}",
	"%h", "{{.ApacheRemoteAddr}}",
	"%H", "{{.R.Proto}}",
	"%l", "-",
	"%m", "{{.R.Method}}",
	"%P", "{{.Pid}}",
	"%q", "{{.ApacheQueryString}}",
	"%r", "{{.R.Method}} {{.R.URL.RequestURI}} {{.R.Proto}}",
	"%s", "{{.StatusCode}}",
	"%S", "\033[{{.StatusCode | statusCodeColor}}m{{.StatusCode}}",
	"%t", "{{if .StartTime}}{{.StartTime.Format \"02/Jan/2006:15:04:05 -0700\"}}{{end}}",
	"%T", "{{if .ResponseTime}}{{.ResponseTime.Seconds | printf \"%.3f\"}}{{end}}",
	"%u", "{{.RemoteUser | dashIfEmptyStr}}",
	"%{User-Agent}i", "{{.R.UserAgent | dashIfEmptyStr}}",
	"%{Referer}i", "{{.R.Referer | dashIfEmptyStr}}",
)

// Convert the Apache access log format into a text/template
func (mw *AccessLogApacheMiddleware) convertFormat() {

	tmplText := apacheAdapter.Replace(string(mw.Format))

	funcMap := template.FuncMap{
		"dashIfEmptyStr": func(value string) string {
			if value == "" {
				return "-"
			}
			return value
		},
		"dashIf0": func(value int64) string {
			if value == 0 {
				return "-"
			}
			return fmt.Sprintf("%d", value)
		},
		"microseconds": func(dur *time.Duration) string {
			return fmt.Sprintf("%d", dur.Nanoseconds()/1000)
		},
		"statusCodeColor": func(statusCode int) string {
			if statusCode >= 400 && statusCode < 500 {
				return "1;33"
			} else if statusCode >= 500 {
				return "0;31"
			}
			return "0;32"
		},
	}

	var err error
	mw.textTemplate, err = template.New("accessLog").Funcs(funcMap).Parse(tmplText)
	if err != nil {
		panic(err)
	}
}

// Execute the text template with the data derived from the request, and return a string.
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
	buf := bytes.NewBufferString("")
	err := mw.textTemplate.Execute(buf, util)
	if err != nil {
		panic(err)
	}
	return buf.String()
}

// accessLogUtil provides a collection of utility functions that devrive data from the Request object.
// This object is used to provide data to the Apache Style template and the the JSON log record.
type accessLogUtil struct {
	W ResponseWriter
	R *Request
}

// As stored by the auth middlewares.
func (u *accessLogUtil) RemoteUser() string {
	if u.R.Env["REMOTE_USER"] != nil {
		return u.R.Env["REMOTE_USER"].(string)
	}
	return ""
}

// If qs exists then return it with a leadin "?", apache log style.
func (u *accessLogUtil) ApacheQueryString() string {
	if u.R.URL.RawQuery != "" {
		return "?" + u.R.URL.RawQuery
	}
	return ""
}

// When the request entered the timer middleware.
func (u *accessLogUtil) StartTime() *time.Time {
	if u.R.Env["START_TIME"] != nil {
		return u.R.Env["START_TIME"].(*time.Time)
	}
	return nil
}

// If remoteAddr is set then return is without the port number, apache log style.
func (u *accessLogUtil) ApacheRemoteAddr() string {
	remoteAddr := u.R.RemoteAddr
	if remoteAddr != "" {
		if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
			return ip
		}
	}
	return ""
}

// As recorded by the recorder middleware.
func (u *accessLogUtil) StatusCode() int {
	if u.R.Env["STATUS_CODE"] != nil {
		return u.R.Env["STATUS_CODE"].(int)
	}
	return 0
}

// As mesured by the timer middleware.
func (u *accessLogUtil) ResponseTime() *time.Duration {
	if u.R.Env["ELAPSED_TIME"] != nil {
		return u.R.Env["ELAPSED_TIME"].(*time.Duration)
	}
	return nil
}

// Process id.
func (u *accessLogUtil) Pid() int {
	return os.Getpid()
}

// As recorded by the recorder middleware.
func (u *accessLogUtil) BytesWritten() int64 {
	if u.R.Env["BYTES_WRITTEN"] != nil {
		return u.R.Env["BYTES_WRITTEN"].(int64)
	}
	return 0
}
<commit_msg>Fix Apache microsecond logging<commit_after>package rest

import (
	"bytes"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"text/template"
	"time"
)

// TODO Future improvements:
// * support %{strftime}t ?
// * support %{<header>}o to print headers

// AccessLogFormat defines the format of the access log record.
// This implementation is a subset of Apache mod_log_config.
// (See http://httpd.apache.org/docs/2.0/mod/mod_log_config.html)
//
//   %b content length in bytes, - if 0
//   %B content length in bytes
//   %D response elapsed time in microseconds
//   %h remote address
//   %H server protocol
//   %l identd logname, not supported, -
//   %m http method
//   %P process id
//   %q query string
//   %r first line of the request
//   %s status code
//   %S status code preceeded by a terminal color
//   %t time of the request
//   %T response elapsed time in seconds, 3 decimals
//   %u remote user, - if missing
//   %{User-Agent}i user agent, - if missing
//   %{Referer}i referer, - is missing
//
// Some predefined formats are provided as contants.
type AccessLogFormat string

const (
	// CommonLogFormat is the Common Log Format (CLF).
	CommonLogFormat = "%h %l %u %t \"%r\" %s %b"

	// CombinedLogFormat is the NCSA extended/combined log format.
	CombinedLogFormat = "%h %l %u %t \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\""

	// DefaultLogFormat is the default format, colored output and response time, convenient for development.
	DefaultLogFormat = "%t %S\033[0m \033[36;1m%Dμs\033[0m \"%r\" \033[1;30m%u \"%{User-Agent}i\"\033[0m"
)

// AccessLogApacheMiddleware produces the access log following a format inspired by Apache
// mod_log_config. It depends on TimerMiddleware and RecorderMiddleware that should be in the wrapped
// middlewares. It also uses request.Env["REMOTE_USER"].(string) set by the auth middlewares.
type AccessLogApacheMiddleware struct {

	// Logger points to the logger object used by this middleware, it defaults to
	// log.New(os.Stderr, "", 0).
	Logger *log.Logger

	// Format defines the format of the access log record. See AccessLogFormat for the details.
	// It defaults to DefaultLogFormat.
	Format AccessLogFormat

	textTemplate *template.Template
}

// MiddlewareFunc makes AccessLogApacheMiddleware implement the Middleware interface.
func (mw *AccessLogApacheMiddleware) MiddlewareFunc(h HandlerFunc) HandlerFunc {

	// set the default Logger
	if mw.Logger == nil {
		mw.Logger = log.New(os.Stderr, "", 0)
	}

	// set default format
	if mw.Format == "" {
		mw.Format = DefaultLogFormat
	}

	mw.convertFormat()

	return func(w ResponseWriter, r *Request) {

		// call the handler
		h(w, r)

		util := &accessLogUtil{w, r}

		mw.Logger.Print(mw.executeTextTemplate(util))
	}
}

var apacheAdapter = strings.NewReplacer(
	"%b", "{{.BytesWritten | dashIf0}}",
	"%B", "{{.BytesWritten}}",
	"%D", "{{.ResponseTime | microseconds}}",
	"%h", "{{.ApacheRemoteAddr}}",
	"%H", "{{.R.Proto}}",
	"%l", "-",
	"%m", "{{.R.Method}}",
	"%P", "{{.Pid}}",
	"%q", "{{.ApacheQueryString}}",
	"%r", "{{.R.Method}} {{.R.URL.RequestURI}} {{.R.Proto}}",
	"%s", "{{.StatusCode}}",
	"%S", "\033[{{.StatusCode | statusCodeColor}}m{{.StatusCode}}",
	"%t", "{{if .StartTime}}{{.StartTime.Format \"02/Jan/2006:15:04:05 -0700\"}}{{end}}",
	"%T", "{{if .ResponseTime}}{{.ResponseTime.Seconds | printf \"%.3f\"}}{{end}}",
	"%u", "{{.RemoteUser | dashIfEmptyStr}}",
	"%{User-Agent}i", "{{.R.UserAgent | dashIfEmptyStr}}",
	"%{Referer}i", "{{.R.Referer | dashIfEmptyStr}}",
)

// Convert the Apache access log format into a text/template
func (mw *AccessLogApacheMiddleware) convertFormat() {

	tmplText := apacheAdapter.Replace(string(mw.Format))

	funcMap := template.FuncMap{
		"dashIfEmptyStr": func(value string) string {
			if value == "" {
				return "-"
			}
			return value
		},
		"dashIf0": func(value int64) string {
			if value == 0 {
				return "-"
			}
			return fmt.Sprintf("%d", value)
		},
		"microseconds": func(dur *time.Duration) string {
			if dur != nil {
				return fmt.Sprintf("%d", dur.Nanoseconds()/1000)
			}
			return ""
		},
		"statusCodeColor": func(statusCode int) string {
			if statusCode >= 400 && statusCode < 500 {
				return "1;33"
			} else if statusCode >= 500 {
				return "0;31"
			}
			return "0;32"
		},
	}

	var err error
	mw.textTemplate, err = template.New("accessLog").Funcs(funcMap).Parse(tmplText)
	if err != nil {
		panic(err)
	}
}

// Execute the text template with the data derived from the request, and return a string.
func (mw *AccessLogApacheMiddleware) executeTextTemplate(util *accessLogUtil) string {
	buf := bytes.NewBufferString("")
	err := mw.textTemplate.Execute(buf, util)
	if err != nil {
		panic(err)
	}
	return buf.String()
}

// accessLogUtil provides a collection of utility functions that devrive data from the Request object.
// This object is used to provide data to the Apache Style template and the the JSON log record.
type accessLogUtil struct {
	W ResponseWriter
	R *Request
}

// As stored by the auth middlewares.
func (u *accessLogUtil) RemoteUser() string {
	if u.R.Env["REMOTE_USER"] != nil {
		return u.R.Env["REMOTE_USER"].(string)
	}
	return ""
}

// If qs exists then return it with a leadin "?", apache log style.
func (u *accessLogUtil) ApacheQueryString() string {
	if u.R.URL.RawQuery != "" {
		return "?" + u.R.URL.RawQuery
	}
	return ""
}

// When the request entered the timer middleware.
func (u *accessLogUtil) StartTime() *time.Time {
	if u.R.Env["START_TIME"] != nil {
		return u.R.Env["START_TIME"].(*time.Time)
	}
	return nil
}

// If remoteAddr is set then return is without the port number, apache log style.
func (u *accessLogUtil) ApacheRemoteAddr() string {
	remoteAddr := u.R.RemoteAddr
	if remoteAddr != "" {
		if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
			return ip
		}
	}
	return ""
}

// As recorded by the recorder middleware.
func (u *accessLogUtil) StatusCode() int {
	if u.R.Env["STATUS_CODE"] != nil {
		return u.R.Env["STATUS_CODE"].(int)
	}
	return 0
}

// As mesured by the timer middleware.
func (u *accessLogUtil) ResponseTime() *time.Duration {
	if u.R.Env["ELAPSED_TIME"] != nil {
		return u.R.Env["ELAPSED_TIME"].(*time.Duration)
	}
	return nil
}

// Process id.
func (u *accessLogUtil) Pid() int {
	return os.Getpid()
}

// As recorded by the recorder middleware.
func (u *accessLogUtil) BytesWritten() int64 {
	if u.R.Env["BYTES_WRITTEN"] != nil {
		return u.R.Env["BYTES_WRITTEN"].(int64)
	}
	return 0
}
<|endoftext|>


<commit_before>package robots

import (
	"errors"
	"fmt"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/pivotal"
	"github.com/gistia/slackbot/robots"
	"github.com/gistia/slackbot/utils"
)

type bot struct {
	handler utils.SlackHandler
}

func init() {
	handler := utils.NewSlackHandler("Pivotal", ":triangular_ruler:")
	s := &bot{handler: handler}
	robots.RegisterRobot("pvt", s)
}

func (r bot) Run(p *robots.Payload) string {
	go r.DeferredAction(p)
	return ""
}

func (r bot) DeferredAction(p *robots.Payload) {
	ch := utils.NewCmdHandler(p, r.handler, "pvt")
	ch.Handle("projects", r.sendProjects)
	ch.Handle("stories", r.sendStories)
	ch.Handle("mystories", r.sendMyStories)
	ch.Handle("auth", r.sendAuth)
	ch.Handle("users", r.users)

	cmds := []string{"start", "unstart", "finish", "accept", "reject", "deliver"}
	ch.HandleMany(cmds, r.setStoryState)

	ch.Process(p.Text)
}

func (r bot) users(p *robots.Payload, cmd utils.Command) error {
	projectId := cmd.Arg(0)
	if projectId == "" {
		r.handler.Send(p, "Missing project id. Use !pvt users <project-id>")
		return nil
	}

	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	project, err := pvt.GetProject(projectId)
	if err != nil {
		return err
	}
	if project == nil {
		r.handler.Send(p, "Project with id "+projectId+" doesn't exist.")
		return nil
	}

	memberships, err := pvt.GetProjectMemberships(projectId)
	if err != nil {
		return err
	}

	s := "Current users for project *" + project.Name + "*:\n"
	for _, m := range memberships {
		pp := m.Person
		s += fmt.Sprintf("%d - %s (%s)\n", pp.Id, pp.Name, pp.Email)
	}

	r.handler.Send(p, s)
	return nil
}

func (r bot) sendProjects(payload *robots.Payload, cmd utils.Command) error {
	var ps []pivotal.Project
	var err error

	term := cmd.Arg(0)

	pvt, err := conn(payload.UserName)
	if err != nil {
		return err
	}
	s := "Projects"

	if len(term) > 0 {
		fmt.Printf("Retrieving projects with term \"%s\"...\n\n", term)
		s += fmt.Sprintf(" matching '%s':\n", term)
		// ps, err = pvt.SearchProject(term)
	} else {
		s += ":\n"
		fmt.Println("Retrieving projects...\n")
		ps, err = pvt.Projects()
	}

	if err != nil {
		msg := fmt.Sprintf("Error: %s", err.Error())
		r.handler.Send(payload, msg)
		return nil
	}

	r.handler.Send(payload, s+projectTable(ps))
	return nil
}

func (r bot) sendStories(p *robots.Payload, cmd utils.Command) error {
	project := cmd.Arg(0)
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	stories, err := pvt.Stories(project)
	if err != nil {
		return err
	}

	str := ""
	for _, s := range stories {
		str += fmt.Sprintf("%d - %s\n", s.Id, s.Name)
	}

	r.handler.Send(p, str)
	return nil
}

func (r bot) sendMyStories(p *robots.Payload, cmd utils.Command) error {
	args, err := cmd.ParseArgs("pvt-project-id")
	if err != nil {
		return err
	}

	project := args[0]
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	user, err := db.GetUserByName(p.UserName)
	if err != nil {
		return err
	}

	filter := map[string]string{
		"owned_by": user.StrPivotalId(),
		"state":    "started,finished,delivered",
	}
	stories, err := pvt.FilteredStories(project, filter)
	if err != nil {
		return err
	}

	if len(stories) < 1 {
		r.handler.Send(p, "No open stories for *"+p.UserName+"*")
		return nil
	}

	str := "Current stories for *" + p.UserName + "*:\n"
	for _, s := range stories {
		str += fmt.Sprintf("%d - %s\n", s.Id, s.Name)
	}

	r.handler.Send(p, str)
	return nil
}

func (r bot) setStoryState(p *robots.Payload, cmd utils.Command) error {
	state := cmd.Command
	id := cmd.Arg(0)
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	state = fmt.Sprintf("%sed", state)
	story, err := pvt.SetStoryState(id, state)
	if err != nil {
		return err
	}

	r.handler.Send(p, fmt.Sprintf("Story %s - %s %s successfully",
		id, story.Name, state))
	return nil
}

func (r bot) sendAuth(p *robots.Payload, cmd utils.Command) error {
	s, err := db.GetSetting(p.UserName, "PIVOTAL_TOKEN")
	if err != nil {
		return err
	}

	if s != nil {
		r.handler.Send(p, "You are already connected with Pivotal.")
		return nil
	}

	msg := `*Authenticating with Pivotal Tracker*
1. Visit your profile here <https://www.pivotaltracker.com/profile>
2. Copy your API token at the bottom of the page
3. Run the command:
   ` + "`/store set PIVOTAL_TOKEN=<token>`"
	r.handler.Send(p, msg)
	return nil
}

func conn(user string) (*pivotal.Pivotal, error) {
	token, err := db.GetSetting(user, "PIVOTAL_TOKEN")
	if err != nil {
		return nil, err
	}
	if token == nil {
		return nil, errors.New("No PIVOTAL_TOKEN set for @" + user)
	}
	con := pivotal.NewPivotal(token.Value, false)
	return con, nil
}

func projectTable(ps []pivotal.Project) string {
	s := ""

	for _, p := range ps {
		s += fmt.Sprintf("%d - %s\n", p.Id, p.Name)
	}

	return s
}

func (r bot) Description() (description string) {
	return "Pivotal bot\n\tUsage: !pvt <command>\n"
}
<commit_msg>Added link to stories in mystories command<commit_after>package robots

import (
	"errors"
	"fmt"

	"github.com/gistia/slackbot/db"
	"github.com/gistia/slackbot/pivotal"
	"github.com/gistia/slackbot/robots"
	"github.com/gistia/slackbot/utils"
)

type bot struct {
	handler utils.SlackHandler
}

func init() {
	handler := utils.NewSlackHandler("Pivotal", ":triangular_ruler:")
	s := &bot{handler: handler}
	robots.RegisterRobot("pvt", s)
}

func (r bot) Run(p *robots.Payload) string {
	go r.DeferredAction(p)
	return ""
}

func (r bot) DeferredAction(p *robots.Payload) {
	ch := utils.NewCmdHandler(p, r.handler, "pvt")
	ch.Handle("projects", r.sendProjects)
	ch.Handle("stories", r.sendStories)
	ch.Handle("mystories", r.sendMyStories)
	ch.Handle("auth", r.sendAuth)
	ch.Handle("users", r.users)

	cmds := []string{"start", "unstart", "finish", "accept", "reject", "deliver"}
	ch.HandleMany(cmds, r.setStoryState)

	ch.Process(p.Text)
}

func (r bot) users(p *robots.Payload, cmd utils.Command) error {
	projectId := cmd.Arg(0)
	if projectId == "" {
		r.handler.Send(p, "Missing project id. Use !pvt users <project-id>")
		return nil
	}

	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	project, err := pvt.GetProject(projectId)
	if err != nil {
		return err
	}
	if project == nil {
		r.handler.Send(p, "Project with id "+projectId+" doesn't exist.")
		return nil
	}

	memberships, err := pvt.GetProjectMemberships(projectId)
	if err != nil {
		return err
	}

	s := "Current users for project *" + project.Name + "*:\n"
	for _, m := range memberships {
		pp := m.Person
		s += fmt.Sprintf("%d - %s (%s)\n", pp.Id, pp.Name, pp.Email)
	}

	r.handler.Send(p, s)
	return nil
}

func (r bot) sendProjects(payload *robots.Payload, cmd utils.Command) error {
	var ps []pivotal.Project
	var err error

	term := cmd.Arg(0)

	pvt, err := conn(payload.UserName)
	if err != nil {
		return err
	}
	s := "Projects"

	if len(term) > 0 {
		fmt.Printf("Retrieving projects with term \"%s\"...\n\n", term)
		s += fmt.Sprintf(" matching '%s':\n", term)
		// ps, err = pvt.SearchProject(term)
	} else {
		s += ":\n"
		fmt.Println("Retrieving projects...\n")
		ps, err = pvt.Projects()
	}

	if err != nil {
		msg := fmt.Sprintf("Error: %s", err.Error())
		r.handler.Send(payload, msg)
		return nil
	}

	r.handler.Send(payload, s+projectTable(ps))
	return nil
}

func (r bot) sendStories(p *robots.Payload, cmd utils.Command) error {
	project := cmd.Arg(0)
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	stories, err := pvt.Stories(project)
	if err != nil {
		return err
	}

	str := ""
	for _, s := range stories {
		str += fmt.Sprintf("%d - %s\n", s.Id, s.Name)
	}

	r.handler.Send(p, str)
	return nil
}

func (r bot) sendMyStories(p *robots.Payload, cmd utils.Command) error {
	args, err := cmd.ParseArgs("pvt-project-id")
	if err != nil {
		return err
	}

	project := args[0]
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	user, err := db.GetUserByName(p.UserName)
	if err != nil {
		return err
	}

	filter := map[string]string{
		"owned_by": user.StrPivotalId(),
		"state":    "started,finished,delivered",
	}
	stories, err := pvt.FilteredStories(project, filter)
	if err != nil {
		return err
	}

	if len(stories) < 1 {
		r.handler.Send(p, "No open stories for *"+p.UserName+"*")
		return nil
	}

	str := "Current stories for *" + p.UserName + "*:\n"
	atts := []robots.Attachment{}
	for _, s := range stories {
		fallback := fmt.Sprintf("%d - %s - %s\n", s.Id, s.Name, s.State)
		title := fmt.Sprintf("%d - %s\n", s.Id, s.Name)
		a := utils.FmtAttachment(fallback, title, s.Url, s.State)
		atts = append(atts, a)
	}

	r.handler.SendWithAttachments(p, str, atts)
	return nil
}

func (r bot) setStoryState(p *robots.Payload, cmd utils.Command) error {
	state := cmd.Command
	id := cmd.Arg(0)
	pvt, err := conn(p.UserName)
	if err != nil {
		return err
	}

	state = fmt.Sprintf("%sed", state)
	story, err := pvt.SetStoryState(id, state)
	if err != nil {
		return err
	}

	r.handler.Send(p, fmt.Sprintf("Story %s - %s %s successfully",
		id, story.Name, state))
	return nil
}

func (r bot) sendAuth(p *robots.Payload, cmd utils.Command) error {
	s, err := db.GetSetting(p.UserName, "PIVOTAL_TOKEN")
	if err != nil {
		return err
	}

	if s != nil {
		r.handler.Send(p, "You are already connected with Pivotal.")
		return nil
	}

	msg := `*Authenticating with Pivotal Tracker*
1. Visit your profile here <https://www.pivotaltracker.com/profile>
2. Copy your API token at the bottom of the page
3. Run the command:
   ` + "`/store set PIVOTAL_TOKEN=<token>`"
	r.handler.Send(p, msg)
	return nil
}

func conn(user string) (*pivotal.Pivotal, error) {
	token, err := db.GetSetting(user, "PIVOTAL_TOKEN")
	if err != nil {
		return nil, err
	}
	if token == nil {
		return nil, errors.New("No PIVOTAL_TOKEN set for @" + user)
	}
	con := pivotal.NewPivotal(token.Value, false)
	return con, nil
}

func projectTable(ps []pivotal.Project) string {
	s := ""

	for _, p := range ps {
		s += fmt.Sprintf("%d - %s\n", p.Id, p.Name)
	}

	return s
}

func (r bot) Description() (description string) {
	return "Pivotal bot\n\tUsage: !pvt <command>\n"
}
<|endoftext|>