<commit_before>package swagger

import (
	"fmt"

	"github.com/emicklei/go-restful"
	// "github.com/emicklei/hopwatch"
	"net/http"
	"reflect"
	"sort"
	"strings"

	"github.com/emicklei/go-restful/log"
)

type SwaggerService struct {
	config            Config
	apiDeclarationMap *ApiDeclarationList
}

func newSwaggerService(config Config) *SwaggerService {
	return &SwaggerService{
		config:            config,
		apiDeclarationMap: new(ApiDeclarationList)}
}

// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
var LogInfo = func(format string, v ...interface{}) {
	// use the restful package-wide logger
	log.Printf(format, v...)
}

// InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func InstallSwaggerService(aSwaggerConfig Config) {
	RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
}

// RegisterSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
	sws := newSwaggerService(config)
	ws := new(restful.WebService)
	ws.Path(config.ApiPath)
	ws.Produces(restful.MIME_JSON)
	if config.DisableCORS {
		ws.Filter(enableCORS)
	}
	ws.Route(ws.GET("/").To(sws.getListing))
	ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
	LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
	wsContainer.Add(ws)

	// Build all ApiDeclarations
	for _, each := range config.WebServices {
		rootPath := each.RootPath()
		// skip the api service itself
		if rootPath != config.ApiPath {
			if rootPath == "" || rootPath == "/" {
				// use routes
				for _, route := range each.Routes() {
					entry := staticPathFromRoute(route)
					_, exists := sws.apiDeclarationMap.At(entry)
					if !exists {
						sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
					}
				}
			} else { // use root path
				sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
			}
		}
	}

	// if specified then call the PostBuilderHandler
	if config.PostBuildHandler != nil {
		config.PostBuildHandler(sws.apiDeclarationMap)
	}

	// Check paths for UI serving
	if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
		swaggerPathSlash := config.SwaggerPath
		// path must end with slash /
		if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
			LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
			swaggerPathSlash += "/"
		}

		LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
		wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))

		//if we define a custom static handler use it
	} else if config.StaticHandler != nil && config.SwaggerPath != "" {
		swaggerPathSlash := config.SwaggerPath
		// path must end with slash /
		if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
			LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
			swaggerPathSlash += "/"

		}
		LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
		wsContainer.Handle(swaggerPathSlash, config.StaticHandler)

	} else {
		LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
	}
}

func staticPathFromRoute(r restful.Route) string {
	static := r.Path
	bracket := strings.Index(static, "{")
	if bracket <= 1 { // result cannot be empty
		return static
	}
	if bracket != -1 {
		static = r.Path[:bracket]
	}
	if strings.HasSuffix(static, "/") {
		return static[:len(static)-1]
	} else {
		return static
	}
}

func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
	if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
		// prevent duplicate header
		if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
			resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
		}
	}
	chain.ProcessFilter(req, resp)
}

func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
	listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion}
	sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
		ref := Resource{Path: k}
		if len(v.Apis) > 0 { // use description of first (could still be empty)
			ref.Description = v.Apis[0].Description
		}
		listing.Apis = append(listing.Apis, ref)
	})
	resp.WriteAsJson(listing)
}

func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
	decl, ok := sws.apiDeclarationMap.At(composeRootPath(req))
	if !ok {
		resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
		return
	}
	// unless WebServicesUrl is given
	if len(sws.config.WebServicesUrl) == 0 {
		// update base path from the actual request
		// TODO how to detect https? assume http for now
		var host string
		// X-Forwarded-Host or Host or Request.Host
		hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
		if !ok || len(hostvalues) == 0 {
			forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
			if !ok || len(forwarded) == 0 {
				// fallback to Host field
				host = req.Request.Host
			} else {
				host = forwarded[0]
			}
		} else {
			host = hostvalues[0]
		}
		(&decl).BasePath = fmt.Sprintf("http://%s", host)
	}
	resp.WriteAsJson(decl)
}

// composeDeclaration uses all routes and parameters to create a ApiDeclaration
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
	decl := ApiDeclaration{
		SwaggerVersion: swaggerVersion,
		BasePath:       sws.config.WebServicesUrl,
		ResourcePath:   pathPrefix,
		Models:         ModelList{},
		ApiVersion:     ws.Version()}

	// collect any path parameters
	rootParams := []Parameter{}
	for _, param := range ws.PathParameters() {
		rootParams = append(rootParams, asSwaggerParameter(param.Data()))
	}
	// aggregate by path
	pathToRoutes := newOrderedRouteMap()
	for _, other := range ws.Routes() {
		if strings.HasPrefix(other.Path, pathPrefix) {
			pathToRoutes.Add(other.Path, other)
		}
	}
	pathToRoutes.Do(func(path string, routes []restful.Route) {
		api := Api{Path: strings.TrimSuffix(path, "/"), Description: ws.Documentation()}
		for _, route := range routes {
			operation := Operation{
				Method:           route.Method,
				Summary:          route.Doc,
				Notes:            route.Notes,
				Type:             asDataType(route.WriteSample),
				Parameters:       []Parameter{},
				Nickname:         route.Operation,
				ResponseMessages: composeResponseMessages(route, &decl)}

			operation.Consumes = route.Consumes
			operation.Produces = route.Produces

			// share root params if any
			for _, swparam := range rootParams {
				operation.Parameters = append(operation.Parameters, swparam)
			}
			// route specific params
			for _, param := range route.ParameterDocs {
				operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
			}

			sws.addModelsFromRouteTo(&operation, route, &decl)
			api.Operations = append(api.Operations, operation)
		}
		decl.Apis = append(decl.Apis, api)
	})
	return decl
}

// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) {
	if route.ResponseErrors == nil {
		return messages
	}
	// sort by code
	codes := sort.IntSlice{}
	for code, _ := range route.ResponseErrors {
		codes = append(codes, code)
	}
	codes.Sort()
	for _, code := range codes {
		each := route.ResponseErrors[code]
		message := ResponseMessage{
			Code:    code,
			Message: each.Message,
		}
		if each.Model != nil {
			st := reflect.TypeOf(each.Model)
			isCollection, st := detectCollectionType(st)
			modelName := modelBuilder{}.keyFrom(st)
			if isCollection {
				modelName = "array[" + modelName + "]"
			}
			modelBuilder{&decl.Models}.addModel(st, "")
			// reference the model
			message.ResponseModel = modelName
		}
		messages = append(messages, message)
	}
	return
}

// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
	if route.ReadSample != nil {
		sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
	}
	if route.WriteSample != nil {
		sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
	}
}

func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
	isCollection := false
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		st = st.Elem()
		isCollection = true
	} else {
		if st.Kind() == reflect.Ptr {
			if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
				st = st.Elem().Elem()
				isCollection = true
			}
		}
	}
	return isCollection, st
}

// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
	st := reflect.TypeOf(sample)
	isCollection, st := detectCollectionType(st)
	modelName := modelBuilder{}.keyFrom(st)
	if isResponse {
		if isCollection {
			modelName = "array[" + modelName + "]"
		}
		operation.Type = modelName
	}
	modelBuilder{models}.addModelFrom(sample)
}

func asSwaggerParameter(param restful.ParameterData) Parameter {
	return Parameter{
		DataTypeFields: DataTypeFields{
			Type:         &param.DataType,
			Format:       asFormat(param.DataType),
			DefaultValue: Special(param.DefaultValue),
		},
		Name:        param.Name,
		Description: param.Description,
		ParamType:   asParamType(param.Kind),

		Required: param.Required}
}

// Between 1..7 path parameters is supported
func composeRootPath(req *restful.Request) string {
	path := "/" + req.PathParameter("a")
	b := req.PathParameter("b")
	if b == "" {
		return path
	}
	path = path + "/" + b
	c := req.PathParameter("c")
	if c == "" {
		return path
	}
	path = path + "/" + c
	d := req.PathParameter("d")
	if d == "" {
		return path
	}
	path = path + "/" + d
	e := req.PathParameter("e")
	if e == "" {
		return path
	}
	path = path + "/" + e
	f := req.PathParameter("f")
	if f == "" {
		return path
	}
	path = path + "/" + f
	g := req.PathParameter("g")
	if g == "" {
		return path
	}
	return path + "/" + g
}

func asFormat(name string) string {
	return "" // TODO
}

func asParamType(kind int) string {
	switch {
	case kind == restful.PathParameterKind:
		return "path"
	case kind == restful.QueryParameterKind:
		return "query"
	case kind == restful.BodyParameterKind:
		return "body"
	case kind == restful.HeaderParameterKind:
		return "header"
	case kind == restful.FormParameterKind:
		return "form"
	}
	return ""
}

func asDataType(any interface{}) string {
	if any == nil {
		return "void"
	}
	return reflect.TypeOf(any).Name()
}
<commit_msg>Detect https for basePath in Swagger<commit_after>package swagger

import (
	"fmt"

	"github.com/emicklei/go-restful"
	// "github.com/emicklei/hopwatch"
	"net/http"
	"reflect"
	"sort"
	"strings"

	"github.com/emicklei/go-restful/log"
)

type SwaggerService struct {
	config            Config
	apiDeclarationMap *ApiDeclarationList
}

func newSwaggerService(config Config) *SwaggerService {
	return &SwaggerService{
		config:            config,
		apiDeclarationMap: new(ApiDeclarationList)}
}

// LogInfo is the function that is called when this package needs to log. It defaults to log.Printf
var LogInfo = func(format string, v ...interface{}) {
	// use the restful package-wide logger
	log.Printf(format, v...)
}

// InstallSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func InstallSwaggerService(aSwaggerConfig Config) {
	RegisterSwaggerService(aSwaggerConfig, restful.DefaultContainer)
}

// RegisterSwaggerService add the WebService that provides the API documentation of all services
// conform the Swagger documentation specifcation. (https://github.com/wordnik/swagger-core/wiki).
func RegisterSwaggerService(config Config, wsContainer *restful.Container) {
	sws := newSwaggerService(config)
	ws := new(restful.WebService)
	ws.Path(config.ApiPath)
	ws.Produces(restful.MIME_JSON)
	if config.DisableCORS {
		ws.Filter(enableCORS)
	}
	ws.Route(ws.GET("/").To(sws.getListing))
	ws.Route(ws.GET("/{a}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}").To(sws.getDeclarations))
	ws.Route(ws.GET("/{a}/{b}/{c}/{d}/{e}/{f}/{g}").To(sws.getDeclarations))
	LogInfo("[restful/swagger] listing is available at %v%v", config.WebServicesUrl, config.ApiPath)
	wsContainer.Add(ws)

	// Build all ApiDeclarations
	for _, each := range config.WebServices {
		rootPath := each.RootPath()
		// skip the api service itself
		if rootPath != config.ApiPath {
			if rootPath == "" || rootPath == "/" {
				// use routes
				for _, route := range each.Routes() {
					entry := staticPathFromRoute(route)
					_, exists := sws.apiDeclarationMap.At(entry)
					if !exists {
						sws.apiDeclarationMap.Put(entry, sws.composeDeclaration(each, entry))
					}
				}
			} else { // use root path
				sws.apiDeclarationMap.Put(each.RootPath(), sws.composeDeclaration(each, each.RootPath()))
			}
		}
	}

	// if specified then call the PostBuilderHandler
	if config.PostBuildHandler != nil {
		config.PostBuildHandler(sws.apiDeclarationMap)
	}

	// Check paths for UI serving
	if config.StaticHandler == nil && config.SwaggerFilePath != "" && config.SwaggerPath != "" {
		swaggerPathSlash := config.SwaggerPath
		// path must end with slash /
		if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
			LogInfo("[restful/swagger] use corrected SwaggerPath ; must end with slash (/)")
			swaggerPathSlash += "/"
		}

		LogInfo("[restful/swagger] %v%v is mapped to folder %v", config.WebServicesUrl, swaggerPathSlash, config.SwaggerFilePath)
		wsContainer.Handle(swaggerPathSlash, http.StripPrefix(swaggerPathSlash, http.FileServer(http.Dir(config.SwaggerFilePath))))

		//if we define a custom static handler use it
	} else if config.StaticHandler != nil && config.SwaggerPath != "" {
		swaggerPathSlash := config.SwaggerPath
		// path must end with slash /
		if "/" != config.SwaggerPath[len(config.SwaggerPath)-1:] {
			LogInfo("[restful/swagger] use corrected SwaggerFilePath ; must end with slash (/)")
			swaggerPathSlash += "/"

		}
		LogInfo("[restful/swagger] %v%v is mapped to custom Handler %T", config.WebServicesUrl, swaggerPathSlash, config.StaticHandler)
		wsContainer.Handle(swaggerPathSlash, config.StaticHandler)

	} else {
		LogInfo("[restful/swagger] Swagger(File)Path is empty ; no UI is served")
	}
}

func staticPathFromRoute(r restful.Route) string {
	static := r.Path
	bracket := strings.Index(static, "{")
	if bracket <= 1 { // result cannot be empty
		return static
	}
	if bracket != -1 {
		static = r.Path[:bracket]
	}
	if strings.HasSuffix(static, "/") {
		return static[:len(static)-1]
	} else {
		return static
	}
}

func enableCORS(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) {
	if origin := req.HeaderParameter(restful.HEADER_Origin); origin != "" {
		// prevent duplicate header
		if len(resp.Header().Get(restful.HEADER_AccessControlAllowOrigin)) == 0 {
			resp.AddHeader(restful.HEADER_AccessControlAllowOrigin, origin)
		}
	}
	chain.ProcessFilter(req, resp)
}

func (sws SwaggerService) getListing(req *restful.Request, resp *restful.Response) {
	listing := ResourceListing{SwaggerVersion: swaggerVersion, ApiVersion: sws.config.ApiVersion}
	sws.apiDeclarationMap.Do(func(k string, v ApiDeclaration) {
		ref := Resource{Path: k}
		if len(v.Apis) > 0 { // use description of first (could still be empty)
			ref.Description = v.Apis[0].Description
		}
		listing.Apis = append(listing.Apis, ref)
	})
	resp.WriteAsJson(listing)
}

func (sws SwaggerService) getDeclarations(req *restful.Request, resp *restful.Response) {
	decl, ok := sws.apiDeclarationMap.At(composeRootPath(req))
	if !ok {
		resp.WriteErrorString(http.StatusNotFound, "ApiDeclaration not found")
		return
	}
	// unless WebServicesUrl is given
	if len(sws.config.WebServicesUrl) == 0 {
		// update base path from the actual request
		// TODO how to detect https? assume http for now
		var host string
		// X-Forwarded-Host or Host or Request.Host
		hostvalues, ok := req.Request.Header["X-Forwarded-Host"] // apache specific?
		if !ok || len(hostvalues) == 0 {
			forwarded, ok := req.Request.Header["Host"] // without reverse-proxy
			if !ok || len(forwarded) == 0 {
				// fallback to Host field
				host = req.Request.Host
			} else {
				host = forwarded[0]
			}
		} else {
			host = hostvalues[0]
		}
		// inspect Referer for the scheme (http vs https)
		scheme := "http"
		if referer := req.Request.Header["Referer"]; len(referer) > 0 {
			if strings.HasPrefix(referer[0], "https") {
				scheme = "https"
			}
		}
		(&decl).BasePath = fmt.Sprintf("%s://%s", scheme, host)
	}
	resp.WriteAsJson(decl)
}

// composeDeclaration uses all routes and parameters to create a ApiDeclaration
func (sws SwaggerService) composeDeclaration(ws *restful.WebService, pathPrefix string) ApiDeclaration {
	decl := ApiDeclaration{
		SwaggerVersion: swaggerVersion,
		BasePath:       sws.config.WebServicesUrl,
		ResourcePath:   pathPrefix,
		Models:         ModelList{},
		ApiVersion:     ws.Version()}

	// collect any path parameters
	rootParams := []Parameter{}
	for _, param := range ws.PathParameters() {
		rootParams = append(rootParams, asSwaggerParameter(param.Data()))
	}
	// aggregate by path
	pathToRoutes := newOrderedRouteMap()
	for _, other := range ws.Routes() {
		if strings.HasPrefix(other.Path, pathPrefix) {
			pathToRoutes.Add(other.Path, other)
		}
	}
	pathToRoutes.Do(func(path string, routes []restful.Route) {
		api := Api{Path: strings.TrimSuffix(path, "/"), Description: ws.Documentation()}
		for _, route := range routes {
			operation := Operation{
				Method:           route.Method,
				Summary:          route.Doc,
				Notes:            route.Notes,
				Type:             asDataType(route.WriteSample),
				Parameters:       []Parameter{},
				Nickname:         route.Operation,
				ResponseMessages: composeResponseMessages(route, &decl)}

			operation.Consumes = route.Consumes
			operation.Produces = route.Produces

			// share root params if any
			for _, swparam := range rootParams {
				operation.Parameters = append(operation.Parameters, swparam)
			}
			// route specific params
			for _, param := range route.ParameterDocs {
				operation.Parameters = append(operation.Parameters, asSwaggerParameter(param.Data()))
			}

			sws.addModelsFromRouteTo(&operation, route, &decl)
			api.Operations = append(api.Operations, operation)
		}
		decl.Apis = append(decl.Apis, api)
	})
	return decl
}

// composeResponseMessages takes the ResponseErrors (if any) and creates ResponseMessages from them.
func composeResponseMessages(route restful.Route, decl *ApiDeclaration) (messages []ResponseMessage) {
	if route.ResponseErrors == nil {
		return messages
	}
	// sort by code
	codes := sort.IntSlice{}
	for code, _ := range route.ResponseErrors {
		codes = append(codes, code)
	}
	codes.Sort()
	for _, code := range codes {
		each := route.ResponseErrors[code]
		message := ResponseMessage{
			Code:    code,
			Message: each.Message,
		}
		if each.Model != nil {
			st := reflect.TypeOf(each.Model)
			isCollection, st := detectCollectionType(st)
			modelName := modelBuilder{}.keyFrom(st)
			if isCollection {
				modelName = "array[" + modelName + "]"
			}
			modelBuilder{&decl.Models}.addModel(st, "")
			// reference the model
			message.ResponseModel = modelName
		}
		messages = append(messages, message)
	}
	return
}

// addModelsFromRoute takes any read or write sample from the Route and creates a Swagger model from it.
func (sws SwaggerService) addModelsFromRouteTo(operation *Operation, route restful.Route, decl *ApiDeclaration) {
	if route.ReadSample != nil {
		sws.addModelFromSampleTo(operation, false, route.ReadSample, &decl.Models)
	}
	if route.WriteSample != nil {
		sws.addModelFromSampleTo(operation, true, route.WriteSample, &decl.Models)
	}
}

func detectCollectionType(st reflect.Type) (bool, reflect.Type) {
	isCollection := false
	if st.Kind() == reflect.Slice || st.Kind() == reflect.Array {
		st = st.Elem()
		isCollection = true
	} else {
		if st.Kind() == reflect.Ptr {
			if st.Elem().Kind() == reflect.Slice || st.Elem().Kind() == reflect.Array {
				st = st.Elem().Elem()
				isCollection = true
			}
		}
	}
	return isCollection, st
}

// addModelFromSample creates and adds (or overwrites) a Model from a sample resource
func (sws SwaggerService) addModelFromSampleTo(operation *Operation, isResponse bool, sample interface{}, models *ModelList) {
	st := reflect.TypeOf(sample)
	isCollection, st := detectCollectionType(st)
	modelName := modelBuilder{}.keyFrom(st)
	if isResponse {
		if isCollection {
			modelName = "array[" + modelName + "]"
		}
		operation.Type = modelName
	}
	modelBuilder{models}.addModelFrom(sample)
}

func asSwaggerParameter(param restful.ParameterData) Parameter {
	return Parameter{
		DataTypeFields: DataTypeFields{
			Type:         &param.DataType,
			Format:       asFormat(param.DataType),
			DefaultValue: Special(param.DefaultValue),
		},
		Name:        param.Name,
		Description: param.Description,
		ParamType:   asParamType(param.Kind),

		Required: param.Required}
}

// Between 1..7 path parameters is supported
func composeRootPath(req *restful.Request) string {
	path := "/" + req.PathParameter("a")
	b := req.PathParameter("b")
	if b == "" {
		return path
	}
	path = path + "/" + b
	c := req.PathParameter("c")
	if c == "" {
		return path
	}
	path = path + "/" + c
	d := req.PathParameter("d")
	if d == "" {
		return path
	}
	path = path + "/" + d
	e := req.PathParameter("e")
	if e == "" {
		return path
	}
	path = path + "/" + e
	f := req.PathParameter("f")
	if f == "" {
		return path
	}
	path = path + "/" + f
	g := req.PathParameter("g")
	if g == "" {
		return path
	}
	return path + "/" + g
}

func asFormat(name string) string {
	return "" // TODO
}

func asParamType(kind int) string {
	switch {
	case kind == restful.PathParameterKind:
		return "path"
	case kind == restful.QueryParameterKind:
		return "query"
	case kind == restful.BodyParameterKind:
		return "body"
	case kind == restful.HeaderParameterKind:
		return "header"
	case kind == restful.FormParameterKind:
		return "form"
	}
	return ""
}

func asDataType(any interface{}) string {
	if any == nil {
		return "void"
	}
	return reflect.TypeOf(any).Name()
}
<|endoftext|>


<commit_before>package lib

import (
	"log"
	"strings"
	"text/scanner"
)

/**
 * Need to redo this so its more similar to how golang Flag works
 * there is a default global, but then you can make your own object if needed
 */

var replacer *strings.Replacer

func init() {
	replacer = strings.NewReplacer(dictWikipedia...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}

// Diff is datastructure showing what changed in a single line
type Diff struct {
	Filename  string
	Line      int
	Column    int
	Original  string
	Corrected string
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func commonPrefixWordLength(a, b string) int {
	// re-order so len(a) <= len(b) always
	if len(a) > len(b) {
		b, a = a, b
	}
	lastWhite := 0
	for i := 0; i < len(a); i++ {
		if a[i] == ' ' || a[i] == '\t' {
			lastWhite = i
		}
		if a[i] != b[i] {
			return min(lastWhite+1, len(a))
		}
	}
	return len(a)
}

// commonSuffixWordLength
func commonSuffixWordLength(a, b string) int {
	n := min(len(a), len(b))
	alen := len(a)
	blen := len(b)
	lastWhite := 0
	for i := 0; i < n; i++ {
		if a[alen-i-1] == ' ' || a[alen-i-1] == '\t' {
			lastWhite = i
		}
		if a[alen-i-1] != b[blen-i-1] {
			return min(lastWhite+1, n)
		}
	}
	return n
}

// Return one words that are corrected in a single line
//
//
func corrected(instr, outstr string) (orig, corrected string, column int) {
	prefixLen := commonPrefixWordLength(instr, outstr)
	suffixLen := commonSuffixWordLength(instr, outstr)

	a := instr[prefixLen : len(instr)-suffixLen]
	b := outstr[prefixLen : len(outstr)-suffixLen]

	// Normal, we found word and its correction with a sane size
	if len(a) < 30 && len(b) < 30 {
		return a, b, prefixLen
	}

	// some lines have no spaces and triggers a huge output
	// trim down
	var col int
	for i := 0; i < len(a); i++ {
		if i < len(b) && a[i] != b[i] {
			col = i - 10
			if col < 0 {
				col = 0
			}
			amax := i + 10
			if amax > len(a) {
				amax = len(a)
			}
			bmax := i + 10
			if bmax > len(b) {
				bmax = len(b)
			}
			return a[col:amax], b[col:bmax], col
		}
	}
	return a, b, col
}

// DiffLines produces a grep-like diff between two strings showing
// filename, linenum and change.  It is not meant to be a comprehensive diff.
func DiffLines(filename, input, output string) (string, []Diff) {
	var changes []Diff
	if output == input {
		return output, changes
	}
	count := 0
	// line by line to make nice output
	outlines := strings.Split(output, "\n")
	inlines := strings.Split(input, "\n")
	for i := 0; i < len(inlines); i++ {
		if inlines[i] == outlines[i] {
			continue
		}
		count++
		s1, s2, col := corrected(inlines[i], outlines[i])
		changes = append(changes, Diff{
			Filename:  filename,
			Line:      i + 1, // lines start at 1
			Column:    col,
			Original:  s1,
			Corrected: s2,
		})
	}
	return output, changes
}

// ReplaceDebug logs exactly what was matched and replaced for using
// in debugging
func ReplaceDebug(input string) string {
	for i := 0; i < len(dictWikipedia); i += 2 {
		idx := strings.Index(input, dictWikipedia[i])
		if idx != -1 {
			left := idx - 10
			if left < 0 {
				left = 0
			}
			right := idx + len(dictWikipedia[i]) + 10
			if right > len(input) {
				right = len(input)
			}
			snippet := input[left:right]
			log.Printf("Found %q in %q  (%q)", dictWikipedia[i], snippet, dictWikipedia[i+1])
		}
	}
	return Replace(input)
}

// Replace takes input string and does spelling corrections on
// commonly misspelled words.
func Replace(input string) string {
	// ok doesn't do much
	return replacer.Replace(input)
}

// ReplaceGo is a specialized routine for correcting Golang source
// files.  Currently only checks comments, not identifiers for
// spelling.
//
// Other items:
//   - check strings, but need to ignore
//      * import "statements" blocks
//      * import ( "blocks" )
//   - skip first comment (line 0) if build comment
//
func ReplaceGo(input string, debug bool) string {
	var s scanner.Scanner
	s.Init(strings.NewReader(input))
	s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
	lastPos := 0
	output := ""
	for {

		switch s.Scan() {
		case scanner.Comment:
			origComment := s.TokenText()

			var newComment string
			if debug {
				newComment = ReplaceDebug(origComment)
			} else {
				newComment = Replace(origComment)
			}

			if origComment != newComment {
				// s.Pos().Offset is the end of the current token
				//  subtract len(origComment) to get the start of token
				offset := s.Pos().Offset
				output = output + input[lastPos:offset-len(origComment)] + newComment
				lastPos = offset
			}
		case scanner.EOF:
			// no changes, no copies
			if lastPos == 0 {
				return input
			}
			if lastPos >= len(input) {
				return output
			}

			return output + input[lastPos:]
		}
	}
}

func inArray(haystack []string, needle string) bool {
	for _, word := range haystack {
		if needle == word {
			return true
		}
	}
	return false
}

// Ignore removes a correction rule
//   WARNING: multiple calls to this will unset the previous calls.
//    thats not so good.
func Ignore(words []string) {
	newwords := make([]string, 0, len(dictWikipedia))
	for i := 0; i < len(dictWikipedia); i += 2 {
		if inArray(words, dictWikipedia[i]) {
			continue
		}
		newwords = append(newwords, dictWikipedia[i])
		newwords = append(newwords, dictWikipedia[i+1])
	}
	replacer = strings.NewReplacer(newwords...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}
<commit_msg>simplification<commit_after>package lib

import (
	"log"
	"strings"
	"text/scanner"
)

/**
 * Need to redo this so its more similar to how golang Flag works
 * there is a default global, but then you can make your own object if needed
 */

var replacer *strings.Replacer

func init() {
	replacer = strings.NewReplacer(dictWikipedia...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}

// Diff is datastructure showing what changed in a single line
type Diff struct {
	Filename  string
	Line      int
	Column    int
	Original  string
	Corrected string
}

func min(x, y int) int {
	if x < y {
		return x
	}
	return y
}

func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

func commonPrefixWordLength(a, b string) int {
	// re-order so len(a) <= len(b) always
	if len(a) > len(b) {
		b, a = a, b
	}
	lastWhite := 0
	for i := 0; i < len(a); i++ {
		if a[i] == ' ' || a[i] == '\t' {
			lastWhite = i
		}
		if a[i] != b[i] {
			return min(lastWhite+1, len(a))
		}
	}
	return len(a)
}

// commonSuffixWordLength
func commonSuffixWordLength(a, b string) int {
	alen, blen := len(a), len(b)
	n := min(alen, blen)
	lastWhite := 0
	for i := 0; i < n; i++ {
		if a[alen-i-1] == ' ' || a[alen-i-1] == '\t' {
			lastWhite = i
		}
		if a[alen-i-1] != b[blen-i-1] {
			return min(lastWhite+1, n)
		}
	}
	return n
}

// Return one words that are corrected in a single line
//
//
func corrected(instr, outstr string) (orig, corrected string, column int) {
	prefixLen := commonPrefixWordLength(instr, outstr)
	suffixLen := commonSuffixWordLength(instr, outstr)

	a := instr[prefixLen : len(instr)-suffixLen]
	b := outstr[prefixLen : len(outstr)-suffixLen]

	// Normal, we found the right snippet and it seems sane
	if len(a) < 30 && len(b) < 30 {
		return a, b, prefixLen
	}

	// some lines have no spaces and triggers a huge output
	// trim down
	var col int
	for i := 0; i < len(a); i++ {
		if i < len(b) && a[i] != b[i] {
			col = i - 10
			if col < 0 {
				col = 0
			}
			amax := i + 10
			if amax > len(a) {
				amax = len(a)
			}
			bmax := i + 10
			if bmax > len(b) {
				bmax = len(b)
			}
			return a[col:amax], b[col:bmax], col
		}
	}
	return a, b, col
}

// DiffLines produces a grep-like diff between two strings showing
// filename, linenum and change.  It is not meant to be a comprehensive diff.
func DiffLines(filename, input, output string) (string, []Diff) {
	var changes []Diff
	if output == input {
		return output, changes
	}
	count := 0
	// line by line to make nice output
	outlines := strings.Split(output, "\n")
	inlines := strings.Split(input, "\n")
	for i := 0; i < len(inlines); i++ {
		if inlines[i] == outlines[i] {
			continue
		}
		count++
		s1, s2, col := corrected(inlines[i], outlines[i])
		changes = append(changes, Diff{
			Filename:  filename,
			Line:      i + 1, // lines start at 1
			Column:    col,
			Original:  s1,
			Corrected: s2,
		})
	}
	return output, changes
}

// ReplaceDebug logs exactly what was matched and replaced for using
// in debugging
func ReplaceDebug(input string) string {
	for i := 0; i < len(dictWikipedia); i += 2 {
		idx := strings.Index(input, dictWikipedia[i])
		if idx != -1 {
			left := idx - 10
			if left < 0 {
				left = 0
			}
			right := idx + len(dictWikipedia[i]) + 10
			if right > len(input) {
				right = len(input)
			}
			snippet := input[left:right]
			log.Printf("Found %q in %q  (%q)", dictWikipedia[i], snippet, dictWikipedia[i+1])
		}
	}
	return Replace(input)
}

// Replace takes input string and does spelling corrections on
// commonly misspelled words.
func Replace(input string) string {
	// ok doesn't do much
	return replacer.Replace(input)
}

// ReplaceGo is a specialized routine for correcting Golang source
// files.  Currently only checks comments, not identifiers for
// spelling.
//
// Other items:
//   - check strings, but need to ignore
//      * import "statements" blocks
//      * import ( "blocks" )
//   - skip first comment (line 0) if build comment
//
func ReplaceGo(input string, debug bool) string {
	var s scanner.Scanner
	s.Init(strings.NewReader(input))
	s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanChars | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments
	lastPos := 0
	output := ""
	for {

		switch s.Scan() {
		case scanner.Comment:
			origComment := s.TokenText()

			var newComment string
			if debug {
				newComment = ReplaceDebug(origComment)
			} else {
				newComment = Replace(origComment)
			}

			if origComment != newComment {
				// s.Pos().Offset is the end of the current token
				//  subtract len(origComment) to get the start of token
				offset := s.Pos().Offset
				output = output + input[lastPos:offset-len(origComment)] + newComment
				lastPos = offset
			}
		case scanner.EOF:
			// no changes, no copies
			if lastPos == 0 {
				return input
			}
			if lastPos >= len(input) {
				return output
			}

			return output + input[lastPos:]
		}
	}
}

func inArray(haystack []string, needle string) bool {
	for _, word := range haystack {
		if needle == word {
			return true
		}
	}
	return false
}

// Ignore removes a correction rule
//   WARNING: multiple calls to this will unset the previous calls.
//    thats not so good.
func Ignore(words []string) {
	newwords := make([]string, 0, len(dictWikipedia))
	for i := 0; i < len(dictWikipedia); i += 2 {
		if inArray(words, dictWikipedia[i]) {
			continue
		}
		newwords = append(newwords, dictWikipedia[i])
		newwords = append(newwords, dictWikipedia[i+1])
	}
	replacer = strings.NewReplacer(newwords...)
	if replacer == nil {
		panic("unable to create strings.Replacer")
	}
}
<|endoftext|>


<commit_before>package couch

import (
    "testing"
    "reflect"
)

type Record struct {
    Foo  int64
    Bars []string
}

type DBRecord struct {
    Id   string
    Rev  string
    Foo  int64
    Bars []string
}

// IMPORTANT! These must be set correctly for the tests to succeed.
const (
    TestDBHost = "localhost"
    TestDBPort = "5984"
    TestDBName = "testdb"
)

func init() {
    CouchDBHost = TestDBHost
    CouchDBPort = TestDBPort
    CouchDBName = TestDBName
}

type DatabaseInfo struct {
    Db_name string
}

func TestConnectivity(t *testing.T) {
    di := new(DatabaseInfo)
    if _, err := Retrieve("/", di); err != nil {
        t.Fatalf("error contacting %s DB (is CouchDB running?)", CouchDBName, err)
    }
    if di.Db_name != CouchDBName {
        t.Fatalf("error connecting to %s DB (did you create it?)", CouchDBName)
    }
}

func TestInsert(t *testing.T) {
    r := Record{12345, []string{"alpha", "beta", "delta"}}
    _, _, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
}

func TestRetrieve(t *testing.T) {
    r := Record{999, []string{"kappa", "gamma"}}
    id, _, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    db_r := new(DBRecord)
    rev, err := Retrieve(id, db_r)
    if err != nil {
        t.Fatalf("failed to retrieve record: %s", err)
    }
    if id != db_r.Id {
        t.Fatalf("id: expected %s, got %s", id, db_r.Id)
    }
    if rev != db_r.Rev {
        t.Fatalf("rev: expected %s, got %s", rev, db_r.Rev)
    }
    if db_r.Foo != r.Foo {
        t.Fatalf("foo: expected %d, got %d", r.Foo, db_r.Foo)
    }
    if !reflect.DeepEqual(db_r.Bars, r.Bars) {
        t.Fatalf("bars: expected %v, got %v", r.Bars, db_r.Bars)
    }
}

func TestEdit(t *testing.T) {
    r := Record{10101, []string{"iota", "omicron", "nu"}}
    id, _, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    db_r := new(DBRecord)
    if _, err := Retrieve(id, db_r); err != nil {
        t.Fatalf("failed to retrieve record: %s", err)
    }
    db_r.Foo = 1
    if _, err := Edit(db_r); err != nil {
        t.Fatalf("failed to edit record: %s", err)
    }
}

func TestDelete(t *testing.T) {
    r := Record{321, []string{"zeta", "phi"}}
    id, rev, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    if err := Delete(id, rev); err != nil {
        t.Fatalf("failed to delete record: %s", err)
    }
}
<commit_msg>Clean up some error messages<commit_after>package couch

import (
    "testing"
    "reflect"
)

type Record struct {
    Foo  int64
    Bars []string
}

type DBRecord struct {
    Id   string
    Rev  string
    Foo  int64
    Bars []string
}

// IMPORTANT! These must be set correctly for the tests to succeed.
const (
    TestDBHost = "localhost"
    TestDBPort = "5984"
    TestDBName = "testdb"
)

func init() {
    CouchDBHost = TestDBHost
    CouchDBPort = TestDBPort
    CouchDBName = TestDBName
}

type DatabaseInfo struct {
    Db_name string
}

func TestConnectivity(t *testing.T) {
    di := new(DatabaseInfo)
    if _, err := Retrieve("/", di); err != nil {
        t.Fatalf("error contacting %s DB (is CouchDB running?)", CouchDBName)
    }
    if di.Db_name != CouchDBName {
        t.Fatalf("error connecting to %s DB (did you create it?)", CouchDBName)
    }
}

func TestInsert(t *testing.T) {
    r := Record{12345, []string{"alpha", "beta", "delta"}}
    if _, _, err := Insert(r); err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
}

func TestRetrieve(t *testing.T) {
    r := Record{999, []string{"kappa", "gamma"}}
    id, _, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    db_r := new(DBRecord)
    rev, err := Retrieve(id, db_r)
    if err != nil {
        t.Fatalf("failed to retrieve record: %s", err)
    }
    if id != db_r.Id {
        t.Fatalf("id: expected %s, got %s", id, db_r.Id)
    }
    if rev != db_r.Rev {
        t.Fatalf("rev: expected %s, got %s", rev, db_r.Rev)
    }
    if db_r.Foo != r.Foo {
        t.Fatalf("foo: expected %d, got %d", r.Foo, db_r.Foo)
    }
    if !reflect.DeepEqual(db_r.Bars, r.Bars) {
        t.Fatalf("bars: expected %v, got %v", r.Bars, db_r.Bars)
    }
}

func TestEdit(t *testing.T) {
    r := Record{10101, []string{"iota", "omicron", "nu"}}
    id, _, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    db_r := new(DBRecord)
    if _, err := Retrieve(id, db_r); err != nil {
        t.Fatalf("failed to retrieve record: %s", err)
    }
    db_r.Foo = 1
    if _, err := Edit(db_r); err != nil {
        t.Fatalf("failed to edit record: %s", err)
    }
}

func TestDelete(t *testing.T) {
    r := Record{321, []string{"zeta", "phi"}}
    id, rev, err := Insert(r)
    if err != nil {
        t.Fatalf("failed to insert record: %s", err)
    }
    if err := Delete(id, rev); err != nil {
        t.Fatalf("failed to delete record: %s", err)
    }
}
<|endoftext|>


<commit_before>// Copyright 2018 The Prometheus 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.

// +build !windows

package sysfs

import (
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/prometheus/procfs/internal/util"
)

// PowerSupply contains info from files in /sys/class/power_supply for a single power supply.
type PowerSupply struct {
	Name                     string // Power Supply Name
	Authentic                *int64 `fileName:"authentic"`                   // /sys/class/power_suppy/<Name>/authentic
	Calibrate                *int64 `fileName:"calibrate"`                   // /sys/class/power_suppy/<Name>/calibrate
	Capacity                 *int64 `fileName:"capacity"`                    // /sys/class/power_suppy/<Name>/capacity
	CapacityAlertMax         *int64 `fileName:"capacity_alert_max"`          // /sys/class/power_suppy/<Name>/capacity_alert_max
	CapacityAlertMin         *int64 `fileName:"capacity_alert_min"`          // /sys/class/power_suppy/<Name>/capacity_alert_min
	CapacityLevel            string `fileName:"capacity_level"`              // /sys/class/power_suppy/<Name>/capacity_level
	ChargeAvg                *int64 `fileName:"charge_avg"`                  // /sys/class/power_suppy/<Name>/charge_avg
	ChargeControlLimit       *int64 `fileName:"charge_control_limit"`        // /sys/class/power_suppy/<Name>/charge_control_limit
	ChargeControlLimitMax    *int64 `fileName:"charge_control_limit_max"`    // /sys/class/power_suppy/<Name>/charge_control_limit_max
	ChargeCounter            *int64 `fileName:"charge_counter"`              // /sys/class/power_suppy/<Name>/charge_counter
	ChargeEmpty              *int64 `fileName:"charge_empty"`                // /sys/class/power_suppy/<Name>/charge_empty
	ChargeEmptyDesign        *int64 `fileName:"charge_empty_design"`         // /sys/class/power_suppy/<Name>/charge_empty_design
	ChargeFull               *int64 `fileName:"charge_full"`                 // /sys/class/power_suppy/<Name>/charge_full
	ChargeFullDesign         *int64 `fileName:"charge_full_design"`          // /sys/class/power_suppy/<Name>/charge_full_design
	ChargeNow                *int64 `fileName:"charge_now"`                  // /sys/class/power_suppy/<Name>/charge_now
	ChargeTermCurrent        *int64 `fileName:"charge_term_current"`         // /sys/class/power_suppy/<Name>/charge_term_current
	ChargeType               string `fileName:"charge_type"`                 // /sys/class/power_supply/<Name>/charge_type
	ConstantChargeCurrent    *int64 `fileName:"constant_charge_current"`     // /sys/class/power_suppy/<Name>/constant_charge_current
	ConstantChargeCurrentMax *int64 `fileName:"constant_charge_current_max"` // /sys/class/power_suppy/<Name>/constant_charge_current_max
	ConstantChargeVoltage    *int64 `fileName:"constant_charge_voltage"`     // /sys/class/power_suppy/<Name>/constant_charge_voltage
	ConstantChargeVoltageMax *int64 `fileName:"constant_charge_voltage_max"` // /sys/class/power_suppy/<Name>/constant_charge_voltage_max
	CurrentAvg               *int64 `fileName:"current_avg"`                 // /sys/class/power_suppy/<Name>/current_avg
	CurrentBoot              *int64 `fileName:"current_boot"`                // /sys/class/power_suppy/<Name>/current_boot
	CurrentMax               *int64 `fileName:"current_max"`                 // /sys/class/power_suppy/<Name>/current_max
	CurrentNow               *int64 `fileName:"current_now"`                 // /sys/class/power_suppy/<Name>/current_now
	CycleCount               *int64 `fileName:"cycle_count"`                 // /sys/class/power_suppy/<Name>/cycle_count
	EnergyAvg                *int64 `fileName:"energy_avg"`                  // /sys/class/power_supply/<Name>/energy_avg
	EnergyEmpty              *int64 `fileName:"energy_empty"`                // /sys/class/power_suppy/<Name>/energy_empty
	EnergyEmptyDesign        *int64 `fileName:"energy_empty_design"`         // /sys/class/power_suppy/<Name>/energy_empty_design
	EnergyFull               *int64 `fileName:"energy_full"`                 // /sys/class/power_suppy/<Name>/energy_full
	EnergyFullDesign         *int64 `fileName:"energy_full_design"`          // /sys/class/power_suppy/<Name>/energy_full_design
	EnergyNow                *int64 `fileName:"energy_now"`                  // /sys/class/power_supply/<Name>/energy_now
	Health                   string `fileName:"health"`                      // /sys/class/power_suppy/<Name>/health
	InputCurrentLimit        *int64 `fileName:"input_current_limit"`         // /sys/class/power_suppy/<Name>/input_current_limit
	Manufacturer             string `fileName:"manufacturer"`                // /sys/class/power_suppy/<Name>/manufacturer
	ModelName                string `fileName:"model_name"`                  // /sys/class/power_suppy/<Name>/model_name
	Online                   *int64 `fileName:"online"`                      // /sys/class/power_suppy/<Name>/online
	PowerAvg                 *int64 `fileName:"power_avg"`                   // /sys/class/power_suppy/<Name>/power_avg
	PowerNow                 *int64 `fileName:"power_now"`                   // /sys/class/power_suppy/<Name>/power_now
	PrechargeCurrent         *int64 `fileName:"precharge_current"`           // /sys/class/power_suppy/<Name>/precharge_current
	Present                  *int64 `fileName:"present"`                     // /sys/class/power_suppy/<Name>/present
	Scope                    *int64 `fileName:"scope"`                       // /sys/class/power_suppy/<Name>/scope
	SerialNumber             string `fileName:"serial_number"`               // /sys/class/power_suppy/<Name>/serial_number
	Status                   string `fileName:"status"`                      // /sys/class/power_supply/<Name>/status
	Technology               string `fileName:"technology"`                  // /sys/class/power_suppy/<Name>/technology
	Temp                     *int64 `fileName:"temp"`                        // /sys/class/power_suppy/<Name>/temp
	TempAlertMax             *int64 `fileName:"temp_alert_max"`              // /sys/class/power_suppy/<Name>/temp_alert_max
	TempAlertMin             *int64 `fileName:"temp_alert_min"`              // /sys/class/power_suppy/<Name>/temp_alert_min
	TempAmbient              *int64 `fileName:"temp_ambient"`                // /sys/class/power_suppy/<Name>/temp_ambient
	TempAmbientMax           *int64 `fileName:"temp_ambient_max"`            // /sys/class/power_suppy/<Name>/temp_ambient_max
	TempAmbientMin           *int64 `fileName:"temp_ambient_min"`            // /sys/class/power_suppy/<Name>/temp_ambient_min
	TempMax                  *int64 `fileName:"temp_max"`                    // /sys/class/power_suppy/<Name>/temp_max
	TempMin                  *int64 `fileName:"temp_min"`                    // /sys/class/power_suppy/<Name>/temp_min
	TimeToEmptyAvg           *int64 `fileName:"time_to_empty_avg"`           // /sys/class/power_suppy/<Name>/time_to_empty_avg
	TimeToEmptyNow           *int64 `fileName:"time_to_empty_now"`           // /sys/class/power_suppy/<Name>/time_to_empty_now
	TimeToFullAvg            *int64 `fileName:"time_to_full_avg"`            // /sys/class/power_suppy/<Name>/time_to_full_avg
	TimeToFullNow            *int64 `fileName:"time_to_full_now"`            // /sys/class/power_suppy/<Name>/time_to_full_now
	Type                     string `fileName:"type"`                        // /sys/class/power_supply/<Name>/type
	UsbType                  string `fileName:"usb_type"`                    // /sys/class/power_supply/<Name>/usb_type
	VoltageAvg               *int64 `fileName:"voltage_avg"`                 // /sys/class/power_supply/<Name>/voltage_avg
	VoltageBoot              *int64 `fileName:"voltage_boot"`                // /sys/class/power_suppy/<Name>/voltage_boot
	VoltageMax               *int64 `fileName:"voltage_max"`                 // /sys/class/power_suppy/<Name>/voltage_max
	VoltageMaxDesign         *int64 `fileName:"voltage_max_design"`          // /sys/class/power_suppy/<Name>/voltage_max_design
	VoltageMin               *int64 `fileName:"voltage_min"`                 // /sys/class/power_suppy/<Name>/voltage_min
	VoltageMinDesign         *int64 `fileName:"voltage_min_design"`          // /sys/class/power_suppy/<Name>/voltage_min_design
	VoltageNow               *int64 `fileName:"voltage_now"`                 // /sys/class/power_supply/<Name>/voltage_now
	VoltageOCV               *int64 `fileName:"voltage_ocv"`                 // /sys/class/power_suppy/<Name>/voltage_ocv
}

// PowerSupplyClass is a collection of every power supply in /sys/class/power_supply/.
// The map keys are the names of the power supplies.
type PowerSupplyClass map[string]PowerSupply

// NewPowerSupplyClass returns info for all power supplies read from /sys/class/power_supply/.
func NewPowerSupplyClass() (PowerSupplyClass, error) {
	fs, err := NewFS(DefaultMountPoint)
	if err != nil {
		return nil, err
	}

	return fs.NewPowerSupplyClass()
}

// NewPowerSupplyClass returns info for all power supplies read from /sys/class/power_supply/.
func (fs FS) NewPowerSupplyClass() (PowerSupplyClass, error) {
	path := fs.Path("class/power_supply")

	powerSupplyDirs, err := ioutil.ReadDir(path)
	if err != nil {
		return PowerSupplyClass{}, fmt.Errorf("cannot access %s dir %s", path, err)
	}

	powerSupplyClass := PowerSupplyClass{}
	for _, powerSupplyDir := range powerSupplyDirs {
		powerSupply, err := powerSupplyClass.parsePowerSupply(path + "/" + powerSupplyDir.Name())
		if err != nil {
			return nil, err
		}
		powerSupply.Name = powerSupplyDir.Name()
		powerSupplyClass[powerSupplyDir.Name()] = *powerSupply
	}
	return powerSupplyClass, nil
}

func (psc PowerSupplyClass) parsePowerSupply(powerSupplyPath string) (*PowerSupply, error) {
	powerSupply := PowerSupply{}
	powerSupplyElem := reflect.ValueOf(&powerSupply).Elem()
	powerSupplyType := reflect.TypeOf(powerSupply)

	//start from 1 - skip the Name field
	for i := 1; i < powerSupplyElem.NumField(); i++ {
		fieldType := powerSupplyType.Field(i)
		fieldValue := powerSupplyElem.Field(i)

		if fieldType.Tag.Get("fileName") == "" {
			panic(fmt.Errorf("field %s does not have a filename tag", fieldType.Name))
		}

		value, err := util.SysReadFile(powerSupplyPath + "/" + fieldType.Tag.Get("fileName"))

		if err != nil {
			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
				continue
			}
			return nil, fmt.Errorf("could not access file %s: %s", fieldType.Tag.Get("fileName"), err)
		}

		switch fieldValue.Kind() {
		case reflect.String:
			fieldValue.SetString(value)
		case reflect.Ptr:
			var int64ptr *int64
			switch fieldValue.Type() {
			case reflect.TypeOf(int64ptr):
				var intValue int64
				if strings.HasPrefix(value, "0x") {
					intValue, err = strconv.ParseInt(value[2:], 16, 64)
					if err != nil {
						return nil, fmt.Errorf("expected hex value for %s, got: %s", fieldType.Name, value)
					}
				} else {
					intValue, err = strconv.ParseInt(value, 10, 64)
					if err != nil {
						return nil, fmt.Errorf("expected Uint64 value for %s, got: %s", fieldType.Name, value)
					}
				}
				fieldValue.Set(reflect.ValueOf(&intValue))
			default:
				return nil, fmt.Errorf("unhandled pointer type %q", fieldValue.Type())
			}
		default:
			return nil, fmt.Errorf("unhandled type %q", fieldValue.Kind())
		}
	}

	return &powerSupply, nil
}
<commit_msg>sysfs: change power supply scope to string (#145)<commit_after>// Copyright 2018 The Prometheus 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.

// +build !windows

package sysfs

import (
	"fmt"
	"io/ioutil"
	"os"
	"reflect"
	"strconv"
	"strings"

	"github.com/prometheus/procfs/internal/util"
)

// PowerSupply contains info from files in /sys/class/power_supply for a single power supply.
type PowerSupply struct {
	Name                     string // Power Supply Name
	Authentic                *int64 `fileName:"authentic"`                   // /sys/class/power_suppy/<Name>/authentic
	Calibrate                *int64 `fileName:"calibrate"`                   // /sys/class/power_suppy/<Name>/calibrate
	Capacity                 *int64 `fileName:"capacity"`                    // /sys/class/power_suppy/<Name>/capacity
	CapacityAlertMax         *int64 `fileName:"capacity_alert_max"`          // /sys/class/power_suppy/<Name>/capacity_alert_max
	CapacityAlertMin         *int64 `fileName:"capacity_alert_min"`          // /sys/class/power_suppy/<Name>/capacity_alert_min
	CapacityLevel            string `fileName:"capacity_level"`              // /sys/class/power_suppy/<Name>/capacity_level
	ChargeAvg                *int64 `fileName:"charge_avg"`                  // /sys/class/power_suppy/<Name>/charge_avg
	ChargeControlLimit       *int64 `fileName:"charge_control_limit"`        // /sys/class/power_suppy/<Name>/charge_control_limit
	ChargeControlLimitMax    *int64 `fileName:"charge_control_limit_max"`    // /sys/class/power_suppy/<Name>/charge_control_limit_max
	ChargeCounter            *int64 `fileName:"charge_counter"`              // /sys/class/power_suppy/<Name>/charge_counter
	ChargeEmpty              *int64 `fileName:"charge_empty"`                // /sys/class/power_suppy/<Name>/charge_empty
	ChargeEmptyDesign        *int64 `fileName:"charge_empty_design"`         // /sys/class/power_suppy/<Name>/charge_empty_design
	ChargeFull               *int64 `fileName:"charge_full"`                 // /sys/class/power_suppy/<Name>/charge_full
	ChargeFullDesign         *int64 `fileName:"charge_full_design"`          // /sys/class/power_suppy/<Name>/charge_full_design
	ChargeNow                *int64 `fileName:"charge_now"`                  // /sys/class/power_suppy/<Name>/charge_now
	ChargeTermCurrent        *int64 `fileName:"charge_term_current"`         // /sys/class/power_suppy/<Name>/charge_term_current
	ChargeType               string `fileName:"charge_type"`                 // /sys/class/power_supply/<Name>/charge_type
	ConstantChargeCurrent    *int64 `fileName:"constant_charge_current"`     // /sys/class/power_suppy/<Name>/constant_charge_current
	ConstantChargeCurrentMax *int64 `fileName:"constant_charge_current_max"` // /sys/class/power_suppy/<Name>/constant_charge_current_max
	ConstantChargeVoltage    *int64 `fileName:"constant_charge_voltage"`     // /sys/class/power_suppy/<Name>/constant_charge_voltage
	ConstantChargeVoltageMax *int64 `fileName:"constant_charge_voltage_max"` // /sys/class/power_suppy/<Name>/constant_charge_voltage_max
	CurrentAvg               *int64 `fileName:"current_avg"`                 // /sys/class/power_suppy/<Name>/current_avg
	CurrentBoot              *int64 `fileName:"current_boot"`                // /sys/class/power_suppy/<Name>/current_boot
	CurrentMax               *int64 `fileName:"current_max"`                 // /sys/class/power_suppy/<Name>/current_max
	CurrentNow               *int64 `fileName:"current_now"`                 // /sys/class/power_suppy/<Name>/current_now
	CycleCount               *int64 `fileName:"cycle_count"`                 // /sys/class/power_suppy/<Name>/cycle_count
	EnergyAvg                *int64 `fileName:"energy_avg"`                  // /sys/class/power_supply/<Name>/energy_avg
	EnergyEmpty              *int64 `fileName:"energy_empty"`                // /sys/class/power_suppy/<Name>/energy_empty
	EnergyEmptyDesign        *int64 `fileName:"energy_empty_design"`         // /sys/class/power_suppy/<Name>/energy_empty_design
	EnergyFull               *int64 `fileName:"energy_full"`                 // /sys/class/power_suppy/<Name>/energy_full
	EnergyFullDesign         *int64 `fileName:"energy_full_design"`          // /sys/class/power_suppy/<Name>/energy_full_design
	EnergyNow                *int64 `fileName:"energy_now"`                  // /sys/class/power_supply/<Name>/energy_now
	Health                   string `fileName:"health"`                      // /sys/class/power_suppy/<Name>/health
	InputCurrentLimit        *int64 `fileName:"input_current_limit"`         // /sys/class/power_suppy/<Name>/input_current_limit
	Manufacturer             string `fileName:"manufacturer"`                // /sys/class/power_suppy/<Name>/manufacturer
	ModelName                string `fileName:"model_name"`                  // /sys/class/power_suppy/<Name>/model_name
	Online                   *int64 `fileName:"online"`                      // /sys/class/power_suppy/<Name>/online
	PowerAvg                 *int64 `fileName:"power_avg"`                   // /sys/class/power_suppy/<Name>/power_avg
	PowerNow                 *int64 `fileName:"power_now"`                   // /sys/class/power_suppy/<Name>/power_now
	PrechargeCurrent         *int64 `fileName:"precharge_current"`           // /sys/class/power_suppy/<Name>/precharge_current
	Present                  *int64 `fileName:"present"`                     // /sys/class/power_suppy/<Name>/present
	Scope                    string `fileName:"scope"`                       // /sys/class/power_suppy/<Name>/scope
	SerialNumber             string `fileName:"serial_number"`               // /sys/class/power_suppy/<Name>/serial_number
	Status                   string `fileName:"status"`                      // /sys/class/power_supply/<Name>/status
	Technology               string `fileName:"technology"`                  // /sys/class/power_suppy/<Name>/technology
	Temp                     *int64 `fileName:"temp"`                        // /sys/class/power_suppy/<Name>/temp
	TempAlertMax             *int64 `fileName:"temp_alert_max"`              // /sys/class/power_suppy/<Name>/temp_alert_max
	TempAlertMin             *int64 `fileName:"temp_alert_min"`              // /sys/class/power_suppy/<Name>/temp_alert_min
	TempAmbient              *int64 `fileName:"temp_ambient"`                // /sys/class/power_suppy/<Name>/temp_ambient
	TempAmbientMax           *int64 `fileName:"temp_ambient_max"`            // /sys/class/power_suppy/<Name>/temp_ambient_max
	TempAmbientMin           *int64 `fileName:"temp_ambient_min"`            // /sys/class/power_suppy/<Name>/temp_ambient_min
	TempMax                  *int64 `fileName:"temp_max"`                    // /sys/class/power_suppy/<Name>/temp_max
	TempMin                  *int64 `fileName:"temp_min"`                    // /sys/class/power_suppy/<Name>/temp_min
	TimeToEmptyAvg           *int64 `fileName:"time_to_empty_avg"`           // /sys/class/power_suppy/<Name>/time_to_empty_avg
	TimeToEmptyNow           *int64 `fileName:"time_to_empty_now"`           // /sys/class/power_suppy/<Name>/time_to_empty_now
	TimeToFullAvg            *int64 `fileName:"time_to_full_avg"`            // /sys/class/power_suppy/<Name>/time_to_full_avg
	TimeToFullNow            *int64 `fileName:"time_to_full_now"`            // /sys/class/power_suppy/<Name>/time_to_full_now
	Type                     string `fileName:"type"`                        // /sys/class/power_supply/<Name>/type
	UsbType                  string `fileName:"usb_type"`                    // /sys/class/power_supply/<Name>/usb_type
	VoltageAvg               *int64 `fileName:"voltage_avg"`                 // /sys/class/power_supply/<Name>/voltage_avg
	VoltageBoot              *int64 `fileName:"voltage_boot"`                // /sys/class/power_suppy/<Name>/voltage_boot
	VoltageMax               *int64 `fileName:"voltage_max"`                 // /sys/class/power_suppy/<Name>/voltage_max
	VoltageMaxDesign         *int64 `fileName:"voltage_max_design"`          // /sys/class/power_suppy/<Name>/voltage_max_design
	VoltageMin               *int64 `fileName:"voltage_min"`                 // /sys/class/power_suppy/<Name>/voltage_min
	VoltageMinDesign         *int64 `fileName:"voltage_min_design"`          // /sys/class/power_suppy/<Name>/voltage_min_design
	VoltageNow               *int64 `fileName:"voltage_now"`                 // /sys/class/power_supply/<Name>/voltage_now
	VoltageOCV               *int64 `fileName:"voltage_ocv"`                 // /sys/class/power_suppy/<Name>/voltage_ocv
}

// PowerSupplyClass is a collection of every power supply in /sys/class/power_supply/.
// The map keys are the names of the power supplies.
type PowerSupplyClass map[string]PowerSupply

// NewPowerSupplyClass returns info for all power supplies read from /sys/class/power_supply/.
func NewPowerSupplyClass() (PowerSupplyClass, error) {
	fs, err := NewFS(DefaultMountPoint)
	if err != nil {
		return nil, err
	}

	return fs.NewPowerSupplyClass()
}

// NewPowerSupplyClass returns info for all power supplies read from /sys/class/power_supply/.
func (fs FS) NewPowerSupplyClass() (PowerSupplyClass, error) {
	path := fs.Path("class/power_supply")

	powerSupplyDirs, err := ioutil.ReadDir(path)
	if err != nil {
		return PowerSupplyClass{}, fmt.Errorf("cannot access %s dir %s", path, err)
	}

	powerSupplyClass := PowerSupplyClass{}
	for _, powerSupplyDir := range powerSupplyDirs {
		powerSupply, err := powerSupplyClass.parsePowerSupply(path + "/" + powerSupplyDir.Name())
		if err != nil {
			return nil, err
		}
		powerSupply.Name = powerSupplyDir.Name()
		powerSupplyClass[powerSupplyDir.Name()] = *powerSupply
	}
	return powerSupplyClass, nil
}

func (psc PowerSupplyClass) parsePowerSupply(powerSupplyPath string) (*PowerSupply, error) {
	powerSupply := PowerSupply{}
	powerSupplyElem := reflect.ValueOf(&powerSupply).Elem()
	powerSupplyType := reflect.TypeOf(powerSupply)

	//start from 1 - skip the Name field
	for i := 1; i < powerSupplyElem.NumField(); i++ {
		fieldType := powerSupplyType.Field(i)
		fieldValue := powerSupplyElem.Field(i)

		if fieldType.Tag.Get("fileName") == "" {
			panic(fmt.Errorf("field %s does not have a filename tag", fieldType.Name))
		}

		value, err := util.SysReadFile(powerSupplyPath + "/" + fieldType.Tag.Get("fileName"))

		if err != nil {
			if os.IsNotExist(err) || err.Error() == "operation not supported" || err.Error() == "invalid argument" {
				continue
			}
			return nil, fmt.Errorf("could not access file %s: %s", fieldType.Tag.Get("fileName"), err)
		}

		switch fieldValue.Kind() {
		case reflect.String:
			fieldValue.SetString(value)
		case reflect.Ptr:
			var int64ptr *int64
			switch fieldValue.Type() {
			case reflect.TypeOf(int64ptr):
				var intValue int64
				if strings.HasPrefix(value, "0x") {
					intValue, err = strconv.ParseInt(value[2:], 16, 64)
					if err != nil {
						return nil, fmt.Errorf("expected hex value for %s, got: %s", fieldType.Name, value)
					}
				} else {
					intValue, err = strconv.ParseInt(value, 10, 64)
					if err != nil {
						return nil, fmt.Errorf("expected Uint64 value for %s, got: %s", fieldType.Name, value)
					}
				}
				fieldValue.Set(reflect.ValueOf(&intValue))
			default:
				return nil, fmt.Errorf("unhandled pointer type %q", fieldValue.Type())
			}
		default:
			return nil, fmt.Errorf("unhandled type %q", fieldValue.Kind())
		}
	}

	return &powerSupply, nil
}
<|endoftext|>


<commit_before>// +build linux,arm
package system

import (
	"syscall"
)

// Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(uid), 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}
<commit_msg>Use SYS_SETUID32 for system.Setuid() on Linux for ARM<commit_after>// +build linux,arm
package system

import (
	"syscall"
)

// Setuid sets the uid of the calling thread to the specified uid.
func Setuid(uid int) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_SETUID32, uintptr(uid), 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// Setgid sets the gid of the calling thread to the specified gid.
func Setgid(gid int) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_SETGID32, uintptr(gid), 0, 0)
	if e1 != 0 {
		err = e1
	}
	return
}
<|endoftext|>


<commit_before>package cpanel

import (
	"strconv"
	"strings"
	"time"

	"github.com/letsencrypt-cpanel/cpanelgo"
)

type CpanelSslCertificate struct {
	Domains      []string            `json:"domains"`
	CommonName   string              `json:"subject.commonName"`
	IsSelfSigned cpanelgo.MaybeInt64 `json:"is_self_signed"`
	Id           string              `json:"id"`
	NotAfter     cpanelgo.MaybeInt64 `json:"not_after"`
	OrgName      string              `json:"issuer.organizationName"`
}

func (s CpanelSslCertificate) Expiry() time.Time {
	return time.Unix(int64(s.NotAfter), 0)
}

type ListSSLKeysAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Created       cpanelgo.MaybeInt64 `json:"created"`
		Modulus       string              `json:"modulus"`
		Id            string              `json:"id"`
		FriendlyName  string              `json:"friendly_name"`
		ModulusLength int                 `json:"modulus_length"`
	} `json:"data"`
}

func (c CpanelApi) ListSSLKeys() (ListSSLKeysAPIResponse, error) {
	var out ListSSLKeysAPIResponse
	err := c.Gateway.UAPI("SSL", "list_keys", nil, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type ListSSLCertsAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data []CpanelSslCertificate `json:"data"`
}

func (c CpanelApi) ListSSLCerts() (ListSSLCertsAPIResponse, error) {
	var out ListSSLCertsAPIResponse
	err := c.Gateway.UAPI("SSL", "list_certs", nil, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type InstalledCertificate struct {
	Certificate     CpanelSslCertificate `json:"certificate"`
	CertificateText string               `json:"certificate_text"`
}

type InstalledHostsApiResponse struct {
	cpanelgo.BaseUAPIResponse
	Data []InstalledCertificate `json:"data"`
}

func (r InstalledHostsApiResponse) HasDomain(d string) bool {
	for _, h := range r.Data {
		if strings.ToLower(d) == strings.ToLower(h.Certificate.CommonName) {
			return true
		}
		for _, v := range h.Certificate.Domains {
			if strings.ToLower(d) == strings.ToLower(v) {
				return true
			}
		}
	}
	return false
}

func (r InstalledHostsApiResponse) HasValidDomain(d string, expiryCutoff time.Time) bool {
	for _, h := range r.Data {
		// Ignore self-signed and 'expiring'/expired certificates
		if h.Certificate.IsSelfSigned == 1 || h.Certificate.Expiry().Before(expiryCutoff) {
			continue
		}
		if strings.ToLower(d) == strings.ToLower(h.Certificate.CommonName) {
			return true
		}
		for _, v := range h.Certificate.Domains {
			if strings.ToLower(d) == strings.ToLower(v) {
				return true
			}
		}
	}
	return false
}

func (c CpanelApi) InstalledHosts() (InstalledHostsApiResponse, error) {
	var out InstalledHostsApiResponse
	err := c.Gateway.UAPI("SSL", "installed_hosts", nil, &out)
	return out, err
}

type GenerateSSLKeyAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Created       cpanelgo.MaybeInt64 `json:"created"`
		Modulus       string              `json:"modulus"`
		Text          string              `json:"text"`
		Id            string              `json:"id"`
		FriendlyName  string              `json:"friendly_name"`
		ModulusLength int                 `json:"modulus_length"`
	} `json:"data"`
}

func (c CpanelApi) GenerateSSLKey(keySize int, friendlyName string) (GenerateSSLKeyAPIResponse, error) {
	var out GenerateSSLKeyAPIResponse
	err := c.Gateway.UAPI("SSL", "generate_key", cpanelgo.Args{
		"key_size":      strconv.Itoa(keySize),
		"friendly_name": friendlyName,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type InstallSSLKeyAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Action                  string   `json:"action"`
		CertId                  string   `json:"cert_id"`
		Domain                  string   `json:"domain"`
		Html                    string   `json:"html"`
		Ip                      string   `json:"ip"`
		KeyId                   string   `json:"key_id"`
		Message                 string   `json:"message"`
		StatusMsg               string   `json:"statusmsg"`
		User                    string   `json:"user"`
		WarningDomains          []string `json:"warning_domains"`
		WorkingDomains          []string `json:"working_domains"`
		ExtraCertificateDomains []string `json:"extra_certificate_domains"`
	} `json:"data"`
}

func (c CpanelApi) InstallSSLKey(domain string, cert string, key string, cabundle string) (InstallSSLKeyAPIResponse, error) {
	var out InstallSSLKeyAPIResponse
	err := c.Gateway.UAPI("SSL", "install_ssl", cpanelgo.Args{
		"domain":   domain,
		"cert":     cert,
		"key":      key,
		"cabundle": cabundle,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteSSL(domain string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_ssl", cpanelgo.Args{
		"domain": domain,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteCert(certId string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_cert", cpanelgo.Args{
		"id": certId,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteKey(certId string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_key", cpanelgo.Args{
		"id": certId,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type EnableMailSNIAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		UpdatedDomains map[string]int         `json:"updated_domains"`
		FailedDomains  map[string]interface{} `json:"failed_domains"`
	} `json:"data"`
}

func (c CpanelApi) EnableMailSNI(domains ...string) (EnableMailSNIAPIResponse, error) {
	var out EnableMailSNIAPIResponse
	err := c.Gateway.UAPI("SSL", "enable_mail_sni", cpanelgo.Args{
		"domains": strings.Join(domains, "|"),
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type IsMailSNISupportedAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data int `json:"data"`
}

func (c CpanelApi) IsMailSNISupported() (IsMailSNISupportedAPIResponse, error) {
	var out IsMailSNISupportedAPIResponse
	err := c.Gateway.UAPI("SSL", "is_mail_sni_supported", cpanelgo.Args{}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type MailSNIStatusAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Enabled int `json:"enabled"`
	} `json:"data"`
}

func (c CpanelApi) MailSNIStatus(domain string) (MailSNIStatusAPIResponse, error) {
	var out MailSNIStatusAPIResponse
	err := c.Gateway.UAPI("SSL", "mail_sni_status", cpanelgo.Args{
		"domain": domain,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type RebuildMailSNIConfigAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Success int `json:"success"`
	}
}

func (c CpanelApi) RebuildMailSNIConfig() (RebuildMailSNIConfigAPIResponse, error) {
	var out RebuildMailSNIConfigAPIResponse
	err := c.Gateway.UAPI("SSL", "rebuild_mail_sni_config", cpanelgo.Args{
		"reload_dovecot": 1,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}
<commit_msg>Fix typo, certId -> keyId<commit_after>package cpanel

import (
	"strconv"
	"strings"
	"time"

	"github.com/letsencrypt-cpanel/cpanelgo"
)

type CpanelSslCertificate struct {
	Domains      []string            `json:"domains"`
	CommonName   string              `json:"subject.commonName"`
	IsSelfSigned cpanelgo.MaybeInt64 `json:"is_self_signed"`
	Id           string              `json:"id"`
	NotAfter     cpanelgo.MaybeInt64 `json:"not_after"`
	OrgName      string              `json:"issuer.organizationName"`
}

func (s CpanelSslCertificate) Expiry() time.Time {
	return time.Unix(int64(s.NotAfter), 0)
}

type ListSSLKeysAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Created       cpanelgo.MaybeInt64 `json:"created"`
		Modulus       string              `json:"modulus"`
		Id            string              `json:"id"`
		FriendlyName  string              `json:"friendly_name"`
		ModulusLength int                 `json:"modulus_length"`
	} `json:"data"`
}

func (c CpanelApi) ListSSLKeys() (ListSSLKeysAPIResponse, error) {
	var out ListSSLKeysAPIResponse
	err := c.Gateway.UAPI("SSL", "list_keys", nil, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type ListSSLCertsAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data []CpanelSslCertificate `json:"data"`
}

func (c CpanelApi) ListSSLCerts() (ListSSLCertsAPIResponse, error) {
	var out ListSSLCertsAPIResponse
	err := c.Gateway.UAPI("SSL", "list_certs", nil, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type InstalledCertificate struct {
	Certificate     CpanelSslCertificate `json:"certificate"`
	CertificateText string               `json:"certificate_text"`
}

type InstalledHostsApiResponse struct {
	cpanelgo.BaseUAPIResponse
	Data []InstalledCertificate `json:"data"`
}

func (r InstalledHostsApiResponse) HasDomain(d string) bool {
	for _, h := range r.Data {
		if strings.ToLower(d) == strings.ToLower(h.Certificate.CommonName) {
			return true
		}
		for _, v := range h.Certificate.Domains {
			if strings.ToLower(d) == strings.ToLower(v) {
				return true
			}
		}
	}
	return false
}

func (r InstalledHostsApiResponse) HasValidDomain(d string, expiryCutoff time.Time) bool {
	for _, h := range r.Data {
		// Ignore self-signed and 'expiring'/expired certificates
		if h.Certificate.IsSelfSigned == 1 || h.Certificate.Expiry().Before(expiryCutoff) {
			continue
		}
		if strings.ToLower(d) == strings.ToLower(h.Certificate.CommonName) {
			return true
		}
		for _, v := range h.Certificate.Domains {
			if strings.ToLower(d) == strings.ToLower(v) {
				return true
			}
		}
	}
	return false
}

func (c CpanelApi) InstalledHosts() (InstalledHostsApiResponse, error) {
	var out InstalledHostsApiResponse
	err := c.Gateway.UAPI("SSL", "installed_hosts", nil, &out)
	return out, err
}

type GenerateSSLKeyAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Created       cpanelgo.MaybeInt64 `json:"created"`
		Modulus       string              `json:"modulus"`
		Text          string              `json:"text"`
		Id            string              `json:"id"`
		FriendlyName  string              `json:"friendly_name"`
		ModulusLength int                 `json:"modulus_length"`
	} `json:"data"`
}

func (c CpanelApi) GenerateSSLKey(keySize int, friendlyName string) (GenerateSSLKeyAPIResponse, error) {
	var out GenerateSSLKeyAPIResponse
	err := c.Gateway.UAPI("SSL", "generate_key", cpanelgo.Args{
		"key_size":      strconv.Itoa(keySize),
		"friendly_name": friendlyName,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type InstallSSLKeyAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Action                  string   `json:"action"`
		CertId                  string   `json:"cert_id"`
		Domain                  string   `json:"domain"`
		Html                    string   `json:"html"`
		Ip                      string   `json:"ip"`
		KeyId                   string   `json:"key_id"`
		Message                 string   `json:"message"`
		StatusMsg               string   `json:"statusmsg"`
		User                    string   `json:"user"`
		WarningDomains          []string `json:"warning_domains"`
		WorkingDomains          []string `json:"working_domains"`
		ExtraCertificateDomains []string `json:"extra_certificate_domains"`
	} `json:"data"`
}

func (c CpanelApi) InstallSSLKey(domain string, cert string, key string, cabundle string) (InstallSSLKeyAPIResponse, error) {
	var out InstallSSLKeyAPIResponse
	err := c.Gateway.UAPI("SSL", "install_ssl", cpanelgo.Args{
		"domain":   domain,
		"cert":     cert,
		"key":      key,
		"cabundle": cabundle,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteSSL(domain string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_ssl", cpanelgo.Args{
		"domain": domain,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteCert(certId string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_cert", cpanelgo.Args{
		"id": certId,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

func (c CpanelApi) DeleteKey(keyId string) (cpanelgo.BaseUAPIResponse, error) {
	var out cpanelgo.BaseUAPIResponse
	err := c.Gateway.UAPI("SSL", "delete_key", cpanelgo.Args{
		"id": keyId,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type EnableMailSNIAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		UpdatedDomains map[string]int         `json:"updated_domains"`
		FailedDomains  map[string]interface{} `json:"failed_domains"`
	} `json:"data"`
}

func (c CpanelApi) EnableMailSNI(domains ...string) (EnableMailSNIAPIResponse, error) {
	var out EnableMailSNIAPIResponse
	err := c.Gateway.UAPI("SSL", "enable_mail_sni", cpanelgo.Args{
		"domains": strings.Join(domains, "|"),
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type IsMailSNISupportedAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data int `json:"data"`
}

func (c CpanelApi) IsMailSNISupported() (IsMailSNISupportedAPIResponse, error) {
	var out IsMailSNISupportedAPIResponse
	err := c.Gateway.UAPI("SSL", "is_mail_sni_supported", cpanelgo.Args{}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type MailSNIStatusAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Enabled int `json:"enabled"`
	} `json:"data"`
}

func (c CpanelApi) MailSNIStatus(domain string) (MailSNIStatusAPIResponse, error) {
	var out MailSNIStatusAPIResponse
	err := c.Gateway.UAPI("SSL", "mail_sni_status", cpanelgo.Args{
		"domain": domain,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}

type RebuildMailSNIConfigAPIResponse struct {
	cpanelgo.BaseUAPIResponse
	Data struct {
		Success int `json:"success"`
	}
}

func (c CpanelApi) RebuildMailSNIConfig() (RebuildMailSNIConfigAPIResponse, error) {
	var out RebuildMailSNIConfigAPIResponse
	err := c.Gateway.UAPI("SSL", "rebuild_mail_sni_config", cpanelgo.Args{
		"reload_dovecot": 1,
	}, &out)
	if err == nil {
		err = out.Error()
	}
	return out, err
}
<|endoftext|>


<commit_before>// Package redsync provides a Redis-based distributed mutual exclusion lock implementation as described in the blog post http://antirez.com/news/77.
//
// Values containing the types defined in this package should not be copied.
package redsync

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"net"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
)

const (
	// DefaultExpiry is used when Mutex Duration is 0
	DefaultExpiry = 8 * time.Second
	// DefaultTries is used when Mutex Duration is 0
	DefaultTries = 16
	// DefaultDelay is used when Mutex Delay is 0
	DefaultDelay = 512 * time.Millisecond
	// DefaultFactor is used when Mutex Factor is 0
	DefaultFactor = 0.01
)

var (
	// ErrFailed is returned when lock cannot be acquired
	ErrFailed = errors.New("failed to acquire lock")
)

// Locker interface with Lock returning an error when lock cannot be aquired
type Locker interface {
	Lock() error
	Touch() bool
	Unlock() bool
}

// Pool is a generic connection pool
type Pool interface {
	Get() redis.Conn
}

var _ = Pool(&redis.Pool{})

// A Mutex is a mutual exclusion lock.
//
// Fields of a Mutex must not be changed after first use.
type Mutex struct {
	Name   string        // Resouce name
	Expiry time.Duration // Duration for which the lock is valid, DefaultExpiry if 0

	Tries int           // Number of attempts to acquire lock before admitting failure, DefaultTries if 0
	Delay time.Duration // Delay between two attempts to acquire lock, DefaultDelay if 0

	Factor float64 // Drift factor, DefaultFactor if 0

	Quorum int // Quorum for the lock, set to len(addrs)/2+1 by NewMutex()

	value string
	until time.Time

	nodes []Pool
	nodem sync.Mutex
}

var _ = Locker(&Mutex{})

// NewMutex returns a new Mutex on a named resource connected to the Redis instances at given addresses.
func NewMutex(name string, addrs []net.Addr) (*Mutex, error) {
	if len(addrs) == 0 {
		panic("redsync: addrs is empty")
	}

	nodes := make([]Pool, len(addrs))
	for i, addr := range addrs {
		dialTo := addr
		node := &redis.Pool{
			MaxActive: 1,
			Wait:      true,
			Dial: func() (redis.Conn, error) {
				return redis.Dial("tcp", dialTo.String())
			},
		}
		nodes[i] = Pool(node)
	}

	return NewMutexWithGenericPool(name, nodes)
}

// NewMutexWithPool returns a new Mutex on a named resource connected to the Redis instances at given redis Pools.
func NewMutexWithPool(name string, nodes []*redis.Pool) (*Mutex, error) {
	if len(nodes) == 0 {
		panic("redsync: nodes is empty")
	}

	genericNodes := make([]Pool, len(nodes))
	for i, node := range nodes {
		genericNodes[i] = Pool(node)
	}

	return &Mutex{
		Name:   name,
		Quorum: len(genericNodes)/2 + 1,
		nodes:  genericNodes,
	}, nil
}

// NewMutexWithGenericPool returns a new Mutex on a named resource connected to the Redis instances at given generic Pools.
// different from NewMutexWithPool to maintain backwards compatibility
func NewMutexWithGenericPool(name string, genericNodes []Pool) (*Mutex, error) {
	if len(genericNodes) == 0 {
		panic("redsync: genericNodes is empty")
	}

	return &Mutex{
		Name:   name,
		Quorum: len(genericNodes)/2 + 1,
		nodes:  genericNodes,
	}, nil
}

// RedSync provides mutex handling via a multiple Redis connection pools.
type RedSync struct {
	pools []Pool
}

// New creates and returns a new RedSync instance from given network addresses.
func New(addrs []net.Addr) *RedSync {
	pools := make([]Pool, len(addrs))
	for i, addr := range addrs {
		dialTo := addr
		node := &redis.Pool{
			MaxActive: 1,
			Wait:      true,
			Dial: func() (redis.Conn, error) {
				return redis.Dial("tcp", dialTo.String())
			},
		}
		pools[i] = Pool(node)
	}

	return &RedSync{pools}
}

// NewWithGenericPool creates and returns a new RedSync instance from given generic Pools.
func NewWithGenericPool(genericNodes []Pool) *RedSync {
	if len(genericNodes) == 0 {
		panic("redsync: genericNodes is empty")
	}

	return &RedSync{
		pools: genericNodes,
	}
}

// NewMutex returns a new Mutex with the given name.
func (r *RedSync) NewMutex(name string) *Mutex {
	return &Mutex{
		Name:   name,
		Quorum: len(r.pools)/2 + 1,
		nodes:  r.pools,
	}
}

// Lock locks m.
// In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
func (m *Mutex) Lock() error {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		return err
	}
	value := base64.StdEncoding.EncodeToString(b)

	expiry := m.Expiry
	if expiry == 0 {
		expiry = DefaultExpiry
	}

	retries := m.Tries
	if retries == 0 {
		retries = DefaultTries
	}

	for i := 0; i < retries; i++ {
		n := 0
		start := time.Now()
		for _, node := range m.nodes {
			if node == nil {
				continue
			}

			conn := node.Get()
			reply, err := redis.String(conn.Do("set", m.Name, value, "nx", "px", int(expiry/time.Millisecond)))
			conn.Close()
			if err != nil {
				continue
			}
			if reply != "OK" {
				continue
			}
			n++
		}

		factor := m.Factor
		if factor == 0 {
			factor = DefaultFactor
		}

		until := time.Now().Add(expiry - time.Now().Sub(start) - time.Duration(int64(float64(expiry)*factor)) + 2*time.Millisecond)
		if n >= m.Quorum && time.Now().Before(until) {
			m.value = value
			m.until = until
			return nil
		}
		for _, node := range m.nodes {
			if node == nil {
				continue
			}

			conn := node.Get()
			_, err := delScript.Do(conn, m.Name, value)
			conn.Close()
			if err != nil {
				continue
			}
		}

		delay := m.Delay
		if delay == 0 {
			delay = DefaultDelay
		}
		time.Sleep(delay)
	}

	return ErrFailed
}

// Touch resets m's expiry to the expiry value.
// It is a run-time error if m is not locked on entry to Touch.
// It returns the status of the touch
func (m *Mutex) Touch() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value := m.value
	if value == "" {
		panic("redsync: touch of unlocked mutex")
	}

	expiry := m.Expiry
	if expiry == 0 {
		expiry = DefaultExpiry
	}
	reset := int(expiry / time.Millisecond)

	n := 0
	for _, node := range m.nodes {
		if node == nil {
			continue
		}

		conn := node.Get()
		reply, err := touchScript.Do(conn, m.Name, value, reset)
		conn.Close()
		if err != nil {
			continue
		}
		if reply != "OK" {
			continue
		}
		n++
	}
	if n >= m.Quorum {
		return true
	}
	return false
}

// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
// It returns the status of the unlock
func (m *Mutex) Unlock() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value := m.value
	if value == "" {
		panic("redsync: unlock of unlocked mutex")
	}

	m.value = ""
	m.until = time.Unix(0, 0)

	n := 0
	for _, node := range m.nodes {
		if node == nil {
			continue
		}

		conn := node.Get()
		status, err := delScript.Do(conn, m.Name, value)
		conn.Close()
		if err != nil {
			continue
		}
		if status == 0 {
			continue
		}
		n++
	}
	if n >= m.Quorum {
		return true
	}
	return false
}

var delScript = redis.NewScript(1, `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("del", KEYS[1])
else
	return 0
end`)

var touchScript = redis.NewScript(1, `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("set", KEYS[1], ARGV[1], "xx", "px", ARGV[2])
else
	return "ERR"
end`)
<commit_msg>Skip delay on last try to lock<commit_after>// Package redsync provides a Redis-based distributed mutual exclusion lock implementation as described in the blog post http://antirez.com/news/77.
//
// Values containing the types defined in this package should not be copied.
package redsync

import (
	"crypto/rand"
	"encoding/base64"
	"errors"
	"net"
	"sync"
	"time"

	"github.com/garyburd/redigo/redis"
)

const (
	// DefaultExpiry is used when Mutex Duration is 0
	DefaultExpiry = 8 * time.Second
	// DefaultTries is used when Mutex Duration is 0
	DefaultTries = 16
	// DefaultDelay is used when Mutex Delay is 0
	DefaultDelay = 512 * time.Millisecond
	// DefaultFactor is used when Mutex Factor is 0
	DefaultFactor = 0.01
)

var (
	// ErrFailed is returned when lock cannot be acquired
	ErrFailed = errors.New("failed to acquire lock")
)

// Locker interface with Lock returning an error when lock cannot be aquired
type Locker interface {
	Lock() error
	Touch() bool
	Unlock() bool
}

// Pool is a generic connection pool
type Pool interface {
	Get() redis.Conn
}

var _ = Pool(&redis.Pool{})

// A Mutex is a mutual exclusion lock.
//
// Fields of a Mutex must not be changed after first use.
type Mutex struct {
	Name   string        // Resouce name
	Expiry time.Duration // Duration for which the lock is valid, DefaultExpiry if 0

	Tries int           // Number of attempts to acquire lock before admitting failure, DefaultTries if 0
	Delay time.Duration // Delay between two attempts to acquire lock, DefaultDelay if 0

	Factor float64 // Drift factor, DefaultFactor if 0

	Quorum int // Quorum for the lock, set to len(addrs)/2+1 by NewMutex()

	value string
	until time.Time

	nodes []Pool
	nodem sync.Mutex
}

var _ = Locker(&Mutex{})

// NewMutex returns a new Mutex on a named resource connected to the Redis instances at given addresses.
func NewMutex(name string, addrs []net.Addr) (*Mutex, error) {
	if len(addrs) == 0 {
		panic("redsync: addrs is empty")
	}

	nodes := make([]Pool, len(addrs))
	for i, addr := range addrs {
		dialTo := addr
		node := &redis.Pool{
			MaxActive: 1,
			Wait:      true,
			Dial: func() (redis.Conn, error) {
				return redis.Dial("tcp", dialTo.String())
			},
		}
		nodes[i] = Pool(node)
	}

	return NewMutexWithGenericPool(name, nodes)
}

// NewMutexWithPool returns a new Mutex on a named resource connected to the Redis instances at given redis Pools.
func NewMutexWithPool(name string, nodes []*redis.Pool) (*Mutex, error) {
	if len(nodes) == 0 {
		panic("redsync: nodes is empty")
	}

	genericNodes := make([]Pool, len(nodes))
	for i, node := range nodes {
		genericNodes[i] = Pool(node)
	}

	return &Mutex{
		Name:   name,
		Quorum: len(genericNodes)/2 + 1,
		nodes:  genericNodes,
	}, nil
}

// NewMutexWithGenericPool returns a new Mutex on a named resource connected to the Redis instances at given generic Pools.
// different from NewMutexWithPool to maintain backwards compatibility
func NewMutexWithGenericPool(name string, genericNodes []Pool) (*Mutex, error) {
	if len(genericNodes) == 0 {
		panic("redsync: genericNodes is empty")
	}

	return &Mutex{
		Name:   name,
		Quorum: len(genericNodes)/2 + 1,
		nodes:  genericNodes,
	}, nil
}

// RedSync provides mutex handling via a multiple Redis connection pools.
type RedSync struct {
	pools []Pool
}

// New creates and returns a new RedSync instance from given network addresses.
func New(addrs []net.Addr) *RedSync {
	pools := make([]Pool, len(addrs))
	for i, addr := range addrs {
		dialTo := addr
		node := &redis.Pool{
			MaxActive: 1,
			Wait:      true,
			Dial: func() (redis.Conn, error) {
				return redis.Dial("tcp", dialTo.String())
			},
		}
		pools[i] = Pool(node)
	}

	return &RedSync{pools}
}

// NewWithGenericPool creates and returns a new RedSync instance from given generic Pools.
func NewWithGenericPool(genericNodes []Pool) *RedSync {
	if len(genericNodes) == 0 {
		panic("redsync: genericNodes is empty")
	}

	return &RedSync{
		pools: genericNodes,
	}
}

// NewMutex returns a new Mutex with the given name.
func (r *RedSync) NewMutex(name string) *Mutex {
	return &Mutex{
		Name:   name,
		Quorum: len(r.pools)/2 + 1,
		nodes:  r.pools,
	}
}

// Lock locks m.
// In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
func (m *Mutex) Lock() error {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	b := make([]byte, 16)
	_, err := rand.Read(b)
	if err != nil {
		return err
	}
	value := base64.StdEncoding.EncodeToString(b)

	expiry := m.Expiry
	if expiry == 0 {
		expiry = DefaultExpiry
	}

	retries := m.Tries
	if retries == 0 {
		retries = DefaultTries
	}

	for i := 0; i < retries; i++ {
		n := 0
		start := time.Now()
		for _, node := range m.nodes {
			if node == nil {
				continue
			}

			conn := node.Get()
			reply, err := redis.String(conn.Do("set", m.Name, value, "nx", "px", int(expiry/time.Millisecond)))
			conn.Close()
			if err != nil {
				continue
			}
			if reply != "OK" {
				continue
			}
			n++
		}

		factor := m.Factor
		if factor == 0 {
			factor = DefaultFactor
		}

		until := time.Now().Add(expiry - time.Now().Sub(start) - time.Duration(int64(float64(expiry)*factor)) + 2*time.Millisecond)
		if n >= m.Quorum && time.Now().Before(until) {
			m.value = value
			m.until = until
			return nil
		}
		for _, node := range m.nodes {
			if node == nil {
				continue
			}

			conn := node.Get()
			_, err := delScript.Do(conn, m.Name, value)
			conn.Close()
			if err != nil {
				continue
			}
		}

		// Have no delay on the last try so we can return ErrFailed sooner.
		if i == retries-1 {
			continue
		}

		delay := m.Delay
		if delay == 0 {
			delay = DefaultDelay
		}
		time.Sleep(delay)
	}

	return ErrFailed
}

// Touch resets m's expiry to the expiry value.
// It is a run-time error if m is not locked on entry to Touch.
// It returns the status of the touch
func (m *Mutex) Touch() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value := m.value
	if value == "" {
		panic("redsync: touch of unlocked mutex")
	}

	expiry := m.Expiry
	if expiry == 0 {
		expiry = DefaultExpiry
	}
	reset := int(expiry / time.Millisecond)

	n := 0
	for _, node := range m.nodes {
		if node == nil {
			continue
		}

		conn := node.Get()
		reply, err := touchScript.Do(conn, m.Name, value, reset)
		conn.Close()
		if err != nil {
			continue
		}
		if reply != "OK" {
			continue
		}
		n++
	}
	if n >= m.Quorum {
		return true
	}
	return false
}

// Unlock unlocks m.
// It is a run-time error if m is not locked on entry to Unlock.
// It returns the status of the unlock
func (m *Mutex) Unlock() bool {
	m.nodem.Lock()
	defer m.nodem.Unlock()

	value := m.value
	if value == "" {
		panic("redsync: unlock of unlocked mutex")
	}

	m.value = ""
	m.until = time.Unix(0, 0)

	n := 0
	for _, node := range m.nodes {
		if node == nil {
			continue
		}

		conn := node.Get()
		status, err := delScript.Do(conn, m.Name, value)
		conn.Close()
		if err != nil {
			continue
		}
		if status == 0 {
			continue
		}
		n++
	}
	if n >= m.Quorum {
		return true
	}
	return false
}

var delScript = redis.NewScript(1, `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("del", KEYS[1])
else
	return 0
end`)

var touchScript = redis.NewScript(1, `
if redis.call("get", KEYS[1]) == ARGV[1] then
	return redis.call("set", KEYS[1], ARGV[1], "xx", "px", ARGV[2])
else
	return "ERR"
end`)
<|endoftext|>