<commit_before>/*
Package bb8 contains the Gobot driver for the Sphero BB-8.

For more information refer to the BB-8 README:
https://gobot.io/x/gobot/blob/master/platforms/sphero/bb8/README.md
*/
package bb8 // import "gobot.io/x/gobot/platforms/sphero/bb8"
<commit_msg>docs: correct BB-8 README link<commit_after>/*
Package bb8 contains the Gobot driver for the Sphero BB-8.

For more information refer to the BB-8 README:
https://github.com/hybridgroup/gobot/blob/master/platforms/sphero/bb8/README.md
*/
package bb8 // import "gobot.io/x/gobot/platforms/sphero/bb8"
<|endoftext|>


<commit_before>package keeper

import (
	"context"

	"github.com/cosmos/cosmos-sdk/store/prefix"
	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/cosmos/cosmos-sdk/types/query"
	"github.com/zigbee-alliance/distributed-compliance-ledger/x/dclauth/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (k Keeper) RevokedAccountAll(c context.Context, req *types.QueryAllRevokedAccountRequest) (*types.QueryAllRevokedAccountResponse, error) {
	if req == nil {
		return nil, status.Error(codes.InvalidArgument, "invalid request")
	}

	var revokedAccounts []types.RevokedAccount
	ctx := sdk.UnwrapSDKContext(c)

	store := ctx.KVStore(k.storeKey)
	revokedAccountStore := prefix.NewStore(store, types.KeyPrefix(types.RevokedAccountKeyPrefix))

	pageRes, err := query.Paginate(revokedAccountStore, req.Pagination, func(key []byte, value []byte) error {
		var revokedAccount types.RevokedAccount
		if err := k.cdc.Unmarshal(value, &revokedAccount); err != nil {
			return err
		}

		revokedAccounts = append(revokedAccounts, revokedAccount)
		return nil
	})

	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	return &types.QueryAllRevokedAccountResponse{RevokedAccount: revokedAccounts, Pagination: pageRes}, nil
}

func (k Keeper) RevokedAccount(c context.Context, req *types.QueryGetRevokedAccountRequest) (*types.QueryGetRevokedAccountResponse, error) {
	if req == nil {
		return nil, status.Error(codes.NotFound, "not found")
	}
	ctx := sdk.UnwrapSDKContext(c)

	val, found := k.GetRevokedAccount(
		ctx,
		sdk.AccAddress(req.Address),
	)
	if !found {
		return nil, status.Error(codes.NotFound, "not found")
	}

	return &types.QueryGetRevokedAccountResponse{RevokedAccount: val}, nil
}
<commit_msg>Change implementation in RevokedAccount<commit_after>package keeper

import (
	"context"

	"github.com/cosmos/cosmos-sdk/store/prefix"
	sdk "github.com/cosmos/cosmos-sdk/types"
	"github.com/cosmos/cosmos-sdk/types/query"
	"github.com/zigbee-alliance/distributed-compliance-ledger/x/dclauth/types"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

func (k Keeper) RevokedAccountAll(c context.Context, req *types.QueryAllRevokedAccountRequest) (*types.QueryAllRevokedAccountResponse, error) {
	if req == nil {
		return nil, status.Error(codes.InvalidArgument, "invalid request")
	}

	var revokedAccounts []types.RevokedAccount
	ctx := sdk.UnwrapSDKContext(c)

	store := ctx.KVStore(k.storeKey)
	revokedAccountStore := prefix.NewStore(store, types.KeyPrefix(types.RevokedAccountKeyPrefix))

	pageRes, err := query.Paginate(revokedAccountStore, req.Pagination, func(key []byte, value []byte) error {
		var revokedAccount types.RevokedAccount
		if err := k.cdc.Unmarshal(value, &revokedAccount); err != nil {
			return err
		}

		revokedAccounts = append(revokedAccounts, revokedAccount)
		return nil
	})

	if err != nil {
		return nil, status.Error(codes.Internal, err.Error())
	}

	return &types.QueryAllRevokedAccountResponse{RevokedAccount: revokedAccounts, Pagination: pageRes}, nil
}

func (k Keeper) RevokedAccount(c context.Context, req *types.QueryGetRevokedAccountRequest) (*types.QueryGetRevokedAccountResponse, error) {
	if req == nil {
		return nil, status.Error(codes.NotFound, "not found")
	}
	ctx := sdk.UnwrapSDKContext(c)

	addr, err := sdk.AccAddressFromBech32(req.Address)
	if err != nil {
		return nil, err
	}

	val, found := k.GetRevokedAccount(
		ctx, addr,
	)
	if !found {
		return nil, status.Error(codes.NotFound, "not found")
	}

	return &types.QueryGetRevokedAccountResponse{RevokedAccount: val}, nil
}
<|endoftext|>


<commit_before>package guardian

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/guardian/transport"
	"github.com/scjalliance/resourceful/lease"
	"github.com/scjalliance/resourceful/lease/leaseutil"
	"github.com/scjalliance/resourceful/policy"
	"github.com/scjalliance/resourceful/strategy"
)

// ServerConfig is the configuration for a resourceful guardian server.
type ServerConfig struct {
	ListenSpec      string
	PolicyProvider  policy.Provider
	LeaseProvider   lease.Provider
	ShutdownTimeout time.Duration // Time allowed to the HTTP server to perform a graceful shutdown
	Logger          *log.Logger
}

// Server coordinates locks on finite resources.
/*
type Server struct {
}
*/

// Run will create and run a resourceful guardian server until the provided
// context is canceled.
func Run(ctx context.Context, cfg ServerConfig) (err error) {
	printf(cfg.Logger, "Starting HTTP listener on %s", cfg.ListenSpec)

	listener, err := net.Listen("tcp", cfg.ListenSpec)
	if err != nil {
		cfg.Logger.Printf("Error creating HTTP listener on %s: %v", cfg.ListenSpec, err)
		return
	}

	mux := http.NewServeMux()
	server := &http.Server{
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 16,
		Handler:        mux,
	}

	mux.Handle("/health", healthHandler(cfg))
	mux.Handle("/leases", leasesHandler(cfg))
	mux.Handle("/acquire", acquireHandler(cfg))
	mux.Handle("/release", releaseHandler(cfg))

	result := make(chan error)

	go func() {
		result <- server.Serve(listener)
		close(result)
	}()

	select {
	case err = <-result:
		printf(cfg.Logger, "Stopped HTTP listener on %s due to error: %v", cfg.ListenSpec, err)
		return
	case <-ctx.Done():
	}

	printf(cfg.Logger, "Stopping HTTP listener on %s due to shutdown signal", cfg.ListenSpec)
	shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
	defer cancel()
	server.Shutdown(shutdownCtx)

	err = <-result

	printf(cfg.Logger, "Stopped HTTP listener on %s", cfg.ListenSpec)
	return
}

// healthHandler will return the condition of the server.
func healthHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		response := transport.HealthResponse{OK: true}
		data, err := json.Marshal(response)
		if err != nil {
			http.Error(w, "Unable to marshal health response", http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, string(data))
	})
}

// leasesHandler will return the set of leases for a particular resource.
func leasesHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, err := parseRequest(r)
		if err != nil {
			err = fmt.Errorf("unable to parse request: %v", err)
			printf(cfg.Logger, "Bad leases request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		revision, leases, err := cfg.LeaseProvider.LeaseView(req.Resource)
		if err != nil {
			printf(cfg.Logger, "Lease retrieval failed: %v\n", err)
		}
		now := time.Now()
		tx := lease.NewTx(req.Resource, revision, leases)
		leaseutil.Refresh(tx, now)

		response := transport.LeasesResponse{
			Request: req,
			Leases:  tx.Leases(),
		}
		data, err := json.MarshalIndent(response, "", "\t")
		if err != nil {
			http.Error(w, "Unable to marshal health response", http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, string(data))
	})
}

// acquireHandler will attempt to acquire a lease for the specified resource.
func acquireHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, pol, err := initRequest(cfg, r)
		if err != nil {
			printf(cfg.Logger, "Bad acquire request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		prefix := fmt.Sprintf("%s %s", req.Resource, req.Consumer)

		printf(cfg.Logger, "%s: Lease acquisition requested\n", prefix)

		strat := pol.Strategy()
		limit := pol.Limit()
		duration := pol.Duration()
		decay := pol.Decay()
		refresh := pol.Refresh()

		var leases lease.Set
		var ls lease.Lease
		mode := "Creation" // Only used for logging

		for attempt := 0; attempt < 5; attempt++ {
			var revision uint64

			revision, leases, err = cfg.LeaseProvider.LeaseView(req.Resource)
			if err != nil {
				printf(cfg.Logger, "%s: Lease retrieval failed: %v\n", prefix, err)
			}
			now := time.Now()

			ls = lease.Lease{
				Resource:    req.Resource,
				Consumer:    req.Consumer,
				Instance:    req.Instance,
				Environment: req.Environment,
				Started:     now,
				Renewed:     now,
				Strategy:    strat,
				Limit:       limit,
				Duration:    duration,
				Decay:       decay,
				Refresh:     refresh,
			}

			if ls.Refresh.Active != 0 {
				if ls.Duration <= ls.Refresh.Active {
					printf(cfg.Logger, "%s: The lease policy specified an active refresh interval of %s for a lease with a duration of %s. The refresh interval will be overridden.\n", prefix, ls.Refresh.Active.String(), ls.Duration.String())
					ls.Refresh.Active = 0 // Use the default refresh rate instead of nonsense
				}
			}
			if ls.Refresh.Queued != 0 {
				if ls.Duration <= ls.Refresh.Queued {
					printf(cfg.Logger, "%s: The lease policy specified a queued refresh interval of %s for a lease with a duration of %s. The refresh interval will be overridden.\n", prefix, ls.Refresh.Queued.String(), ls.Duration.String())
					ls.Refresh.Queued = 0 // Use the default refresh rate instead of nonsense
				}
			}

			tx := lease.NewTx(req.Resource, revision, leases)

			acc := leaseutil.Refresh(tx, now)
			consumed := acc.Consumed(strat)
			released := acc.Released(req.Consumer)

			existing, found := tx.Instance(req.Consumer, req.Instance)
			if found {
				if existing.Status == lease.Released {
					// Renewal of a released lease, possibly because of timing skew
					// Because the lease has expired we treat this as a creation
					if consumed <= limit {
						ls.Status = lease.Active
					} else {
						ls.Status = lease.Queued
					}
					tx.Update(existing.Consumer, existing.Instance, ls)
				} else {
					// Renewal of active or queued lease
					mode = "Renewal"
					ls.Status = existing.Status
					ls.Started = existing.Started
					tx.Update(existing.Consumer, existing.Instance, ls)
				}
			} else {
				if released > 0 && consumed <= limit {
					// Lease replacement (for an expired or released lease previously
					// issued to the the same consumer, that's in a decaying state)
					replaceable := tx.Consumer(req.Consumer).Status(lease.Released)
					if uint(len(replaceable)) != released {
						panic("server: acquireHandler: accumulator returned a different count for relased leases than the transaction")
					}
					replaced := replaceable[released-1]
					ls.Status = lease.Active
					tx.Update(replaced.Consumer, replaced.Instance, ls)
				} else {
					// New lease
					if leaseutil.CanActivate(strat, acc.Active(req.Consumer), consumed, limit) {
						ls.Status = lease.Active
					} else {
						ls.Status = lease.Queued
					}
					tx.Create(ls)
				}
			}

			// Attempt to commit the transaction
			err = cfg.LeaseProvider.LeaseCommit(tx)
			if err == nil {
				leases = tx.Leases()
				break
			}

			printf(cfg.Logger, "%s: Lease acquisition failed: %v\n", prefix, err)
		}

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		summary := statsSummary(limit, leases.Stats(), strat)
		printf(cfg.Logger, "%s: %s of %s lease succeeded (%s)\n", prefix, mode, ls.Status, summary)

		response := transport.AcquireResponse{
			Request: req,
			Lease:   ls,
			Leases:  leases,
		}

		data, err := json.Marshal(response)
		if err != nil {
			printf(cfg.Logger, "%s: Failed to marshal response: %v\n", prefix, err)
			http.Error(w, "Failed to marshal response", http.StatusBadRequest)
			return
		}

		fmt.Fprintf(w, string(data))
	})
}

// releaseHandler will attempt to remove the lease for the given resource and
// consumer.
func releaseHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, pol, err := initRequest(cfg, r)
		if err != nil {
			printf(cfg.Logger, "Bad release request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		prefix := fmt.Sprintf("%s %s", req.Resource, req.Consumer)

		printf(cfg.Logger, "%s: Release requested\n", prefix)

		strat := pol.Strategy()
		limit := pol.Limit()

		var leases lease.Set
		var ls lease.Lease
		var found bool

		for attempt := 0; attempt < 5; attempt++ {
			var revision uint64
			revision, leases, err = cfg.LeaseProvider.LeaseView(req.Resource)
			if err != nil {
				printf(cfg.Logger, "%s: Release failed: %v\n", prefix, err)
				continue
			}

			// Prepare a delete transaction
			now := time.Now()
			tx := lease.NewTx(req.Resource, revision, leases)
			leaseutil.Refresh(tx, now) // Update stale values
			ls, found = tx.Instance(req.Consumer, req.Instance)
			tx.Release(req.Consumer, req.Instance, now)
			leaseutil.Refresh(tx, now) // Updates leases after release

			// Attempt to commit the transaction
			err = cfg.LeaseProvider.LeaseCommit(tx)
			if err == nil {
				leases = tx.Leases()
				break
			}

			printf(cfg.Logger, "%s: Release failed: %v\n", prefix, err)
		}

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		summary := statsSummary(limit, leases.Stats(), strat)
		if found {
			if ls.Status == lease.Released {
				printf(cfg.Logger, "%s: Release ignored because the lease had already been released (%s)\n", prefix, summary)
			} else {
				printf(cfg.Logger, "%s: Release of %s lease succeeded (%s)\n", prefix, ls.Status, summary)
			}
		} else {
			printf(cfg.Logger, "%s: Release ignored because the lease could not be found (%s)\n", prefix, summary)
		}

		response := transport.ReleaseResponse{
			Request: req,
			Success: true,
		}

		data, err := json.Marshal(response)
		if err != nil {
			printf(cfg.Logger, "%s: Failed to marshal response: %v\n", prefix, err)
			http.Error(w, "Failed to marshal response", http.StatusBadRequest)
			return
		}

		fmt.Fprintf(w, string(data))
	})
}

func initRequest(cfg ServerConfig, r *http.Request) (req transport.Request, policies policy.Set, err error) {
	req, err = parseRequest(r)
	if err != nil {
		err = fmt.Errorf("unable to parse request: %v", err)
		return
	}

	all, err := cfg.PolicyProvider.Policies()
	if err != nil {
		err = fmt.Errorf("unable to retrieve policies: %v", err)
		return
	}

	policies = all.Match(req.Resource, req.Consumer, req.Environment)

	resource := policies.Resource()
	if resource != "" {
		req.Resource = resource
	}
	req.Environment["resource.id"] = req.Resource

	consumer := policies.Consumer()
	if consumer != "" {
		req.Consumer = consumer
	}

	env := policies.Environment()
	if len(env) > 0 {
		req.Environment = environment.Merge(req.Environment, env)
	}

	if req.Resource == "" {
		err = errors.New("resource not specified or determinable")
	} else if req.Consumer == "" {
		err = errors.New("consumer not specified or determinable")
	}
	return
}

func parseRequest(r *http.Request) (req transport.Request, err error) {
	err = r.ParseForm()
	if err != nil {
		return
	}
	req.Environment = make(environment.Environment)
	for k, values := range r.Form {
		if len(values) == 0 {
			continue
		}
		value := values[0] // Ignore multiple values
		switch k {
		case "resource":
			req.Resource = value
		case "consumer":
			req.Consumer = value
		case "instance":
			req.Instance = value
		default:
			req.Environment[k] = value
		}
	}
	return
}

func statsSummary(limit uint, stats lease.Stats, strat strategy.Strategy) string {
	consumed := stats.Consumed(strat)
	active := stats.Active(strat)
	released := stats.Released(strat)
	queued := stats.Queued(strat)
	return fmt.Sprintf("alloc: %d/%d, active: %d, released: %d, queued: %d", consumed, limit, active, released, queued)
}

func printf(logger *log.Logger, format string, v ...interface{}) {
	if logger != nil {
		logger.Printf(format, v...)
	}
}
<commit_msg>guardian: Server will purge old data at startup and shutdown<commit_after>package guardian

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net"
	"net/http"
	"time"

	"github.com/scjalliance/resourceful/environment"
	"github.com/scjalliance/resourceful/guardian/transport"
	"github.com/scjalliance/resourceful/lease"
	"github.com/scjalliance/resourceful/lease/leaseutil"
	"github.com/scjalliance/resourceful/policy"
	"github.com/scjalliance/resourceful/strategy"
)

// ServerConfig is the configuration for a resourceful guardian server.
type ServerConfig struct {
	ListenSpec      string
	PolicyProvider  policy.Provider
	LeaseProvider   lease.Provider
	ShutdownTimeout time.Duration // Time allowed to the HTTP server to perform a graceful shutdown
	Logger          *log.Logger
}

// Server coordinates locks on finite resources.
/*
type Server struct {
}
*/

// Run will create and run a resourceful guardian server until the provided
// context is canceled.
func Run(ctx context.Context, cfg ServerConfig) (err error) {
	purge(cfg)
	defer purge(cfg)
	printf(cfg.Logger, "Starting HTTP listener on %s", cfg.ListenSpec)

	listener, err := net.Listen("tcp", cfg.ListenSpec)
	if err != nil {
		cfg.Logger.Printf("Error creating HTTP listener on %s: %v", cfg.ListenSpec, err)
		return
	}

	mux := http.NewServeMux()
	server := &http.Server{
		ReadTimeout:    60 * time.Second,
		WriteTimeout:   60 * time.Second,
		MaxHeaderBytes: 1 << 16,
		Handler:        mux,
	}

	mux.Handle("/health", healthHandler(cfg))
	mux.Handle("/leases", leasesHandler(cfg))
	mux.Handle("/acquire", acquireHandler(cfg))
	mux.Handle("/release", releaseHandler(cfg))

	result := make(chan error)

	go func() {
		result <- server.Serve(listener)
		close(result)
	}()

	select {
	case err = <-result:
		printf(cfg.Logger, "Stopped HTTP listener on %s due to error: %v", cfg.ListenSpec, err)
		return
	case <-ctx.Done():
	}

	printf(cfg.Logger, "Stopping HTTP listener on %s due to shutdown signal", cfg.ListenSpec)
	shutdownCtx, cancel := context.WithTimeout(context.Background(), cfg.ShutdownTimeout)
	defer cancel()
	server.Shutdown(shutdownCtx)

	err = <-result

	printf(cfg.Logger, "Stopped HTTP listener on %s", cfg.ListenSpec)
	return
}

// healthHandler will return the condition of the server.
func healthHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		response := transport.HealthResponse{OK: true}
		data, err := json.Marshal(response)
		if err != nil {
			http.Error(w, "Unable to marshal health response", http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, string(data))
	})
}

// leasesHandler will return the set of leases for a particular resource.
func leasesHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, err := parseRequest(r)
		if err != nil {
			err = fmt.Errorf("unable to parse request: %v", err)
			printf(cfg.Logger, "Bad leases request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		revision, leases, err := cfg.LeaseProvider.LeaseView(req.Resource)
		if err != nil {
			printf(cfg.Logger, "Lease retrieval failed: %v\n", err)
		}
		now := time.Now()
		tx := lease.NewTx(req.Resource, revision, leases)
		leaseutil.Refresh(tx, now)

		response := transport.LeasesResponse{
			Request: req,
			Leases:  tx.Leases(),
		}
		data, err := json.MarshalIndent(response, "", "\t")
		if err != nil {
			http.Error(w, "Unable to marshal health response", http.StatusBadRequest)
			return
		}
		fmt.Fprintf(w, string(data))
	})
}

// acquireHandler will attempt to acquire a lease for the specified resource.
func acquireHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, pol, err := initRequest(cfg, r)
		if err != nil {
			printf(cfg.Logger, "Bad acquire request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		prefix := fmt.Sprintf("%s %s", req.Resource, req.Consumer)

		printf(cfg.Logger, "%s: Lease acquisition requested\n", prefix)

		strat := pol.Strategy()
		limit := pol.Limit()
		duration := pol.Duration()
		decay := pol.Decay()
		refresh := pol.Refresh()

		var leases lease.Set
		var ls lease.Lease
		mode := "Creation" // Only used for logging

		for attempt := 0; attempt < 5; attempt++ {
			var revision uint64

			revision, leases, err = cfg.LeaseProvider.LeaseView(req.Resource)
			if err != nil {
				printf(cfg.Logger, "%s: Lease retrieval failed: %v\n", prefix, err)
			}
			now := time.Now()

			ls = lease.Lease{
				Resource:    req.Resource,
				Consumer:    req.Consumer,
				Instance:    req.Instance,
				Environment: req.Environment,
				Started:     now,
				Renewed:     now,
				Strategy:    strat,
				Limit:       limit,
				Duration:    duration,
				Decay:       decay,
				Refresh:     refresh,
			}

			if ls.Refresh.Active != 0 {
				if ls.Duration <= ls.Refresh.Active {
					printf(cfg.Logger, "%s: The lease policy specified an active refresh interval of %s for a lease with a duration of %s. The refresh interval will be overridden.\n", prefix, ls.Refresh.Active.String(), ls.Duration.String())
					ls.Refresh.Active = 0 // Use the default refresh rate instead of nonsense
				}
			}
			if ls.Refresh.Queued != 0 {
				if ls.Duration <= ls.Refresh.Queued {
					printf(cfg.Logger, "%s: The lease policy specified a queued refresh interval of %s for a lease with a duration of %s. The refresh interval will be overridden.\n", prefix, ls.Refresh.Queued.String(), ls.Duration.String())
					ls.Refresh.Queued = 0 // Use the default refresh rate instead of nonsense
				}
			}

			tx := lease.NewTx(req.Resource, revision, leases)

			acc := leaseutil.Refresh(tx, now)
			consumed := acc.Consumed(strat)
			released := acc.Released(req.Consumer)

			existing, found := tx.Instance(req.Consumer, req.Instance)
			if found {
				if existing.Status == lease.Released {
					// Renewal of a released lease, possibly because of timing skew
					// Because the lease has expired we treat this as a creation
					if consumed <= limit {
						ls.Status = lease.Active
					} else {
						ls.Status = lease.Queued
					}
					tx.Update(existing.Consumer, existing.Instance, ls)
				} else {
					// Renewal of active or queued lease
					mode = "Renewal"
					ls.Status = existing.Status
					ls.Started = existing.Started
					tx.Update(existing.Consumer, existing.Instance, ls)
				}
			} else {
				if released > 0 && consumed <= limit {
					// Lease replacement (for an expired or released lease previously
					// issued to the the same consumer, that's in a decaying state)
					replaceable := tx.Consumer(req.Consumer).Status(lease.Released)
					if uint(len(replaceable)) != released {
						panic("server: acquireHandler: accumulator returned a different count for relased leases than the transaction")
					}
					replaced := replaceable[released-1]
					ls.Status = lease.Active
					tx.Update(replaced.Consumer, replaced.Instance, ls)
				} else {
					// New lease
					if leaseutil.CanActivate(strat, acc.Active(req.Consumer), consumed, limit) {
						ls.Status = lease.Active
					} else {
						ls.Status = lease.Queued
					}
					tx.Create(ls)
				}
			}

			// Attempt to commit the transaction
			err = cfg.LeaseProvider.LeaseCommit(tx)
			if err == nil {
				leases = tx.Leases()
				break
			}

			printf(cfg.Logger, "%s: Lease acquisition failed: %v\n", prefix, err)
		}

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		summary := statsSummary(limit, leases.Stats(), strat)
		printf(cfg.Logger, "%s: %s of %s lease succeeded (%s)\n", prefix, mode, ls.Status, summary)

		response := transport.AcquireResponse{
			Request: req,
			Lease:   ls,
			Leases:  leases,
		}

		data, err := json.Marshal(response)
		if err != nil {
			printf(cfg.Logger, "%s: Failed to marshal response: %v\n", prefix, err)
			http.Error(w, "Failed to marshal response", http.StatusBadRequest)
			return
		}

		fmt.Fprintf(w, string(data))
	})
}

// releaseHandler will attempt to remove the lease for the given resource and
// consumer.
func releaseHandler(cfg ServerConfig) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		req, pol, err := initRequest(cfg, r)
		if err != nil {
			printf(cfg.Logger, "Bad release request: %v\n", err)
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		prefix := fmt.Sprintf("%s %s", req.Resource, req.Consumer)

		printf(cfg.Logger, "%s: Release requested\n", prefix)

		strat := pol.Strategy()
		limit := pol.Limit()

		var leases lease.Set
		var ls lease.Lease
		var found bool

		for attempt := 0; attempt < 5; attempt++ {
			var revision uint64
			revision, leases, err = cfg.LeaseProvider.LeaseView(req.Resource)
			if err != nil {
				printf(cfg.Logger, "%s: Release failed: %v\n", prefix, err)
				continue
			}

			// Prepare a delete transaction
			now := time.Now()
			tx := lease.NewTx(req.Resource, revision, leases)
			leaseutil.Refresh(tx, now) // Update stale values
			ls, found = tx.Instance(req.Consumer, req.Instance)
			tx.Release(req.Consumer, req.Instance, now)
			leaseutil.Refresh(tx, now) // Updates leases after release

			// Attempt to commit the transaction
			err = cfg.LeaseProvider.LeaseCommit(tx)
			if err == nil {
				leases = tx.Leases()
				break
			}

			printf(cfg.Logger, "%s: Release failed: %v\n", prefix, err)
		}

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		summary := statsSummary(limit, leases.Stats(), strat)
		if found {
			if ls.Status == lease.Released {
				printf(cfg.Logger, "%s: Release ignored because the lease had already been released (%s)\n", prefix, summary)
			} else {
				printf(cfg.Logger, "%s: Release of %s lease succeeded (%s)\n", prefix, ls.Status, summary)
			}
		} else {
			printf(cfg.Logger, "%s: Release ignored because the lease could not be found (%s)\n", prefix, summary)
		}

		response := transport.ReleaseResponse{
			Request: req,
			Success: true,
		}

		data, err := json.Marshal(response)
		if err != nil {
			printf(cfg.Logger, "%s: Failed to marshal response: %v\n", prefix, err)
			http.Error(w, "Failed to marshal response", http.StatusBadRequest)
			return
		}

		fmt.Fprintf(w, string(data))
	})
}

func purge(cfg ServerConfig) error {
	resources, err := cfg.LeaseProvider.LeaseResources()
	if err != nil {
		return err
	}
	for _, resource := range resources {
		for attempt := 0; attempt < 5; attempt++ {
			var (
				leases   lease.Set
				revision uint64
			)
			revision, leases, err = cfg.LeaseProvider.LeaseView(resource)
			if err != nil {
				printf(cfg.Logger, "Purge of \"%s\" failed: %v\n", resource, err)
				continue
			}

			// Prepare a purge transaction
			now := time.Now()
			tx := lease.NewTx(resource, revision, leases)
			leaseutil.Refresh(tx, now)
			if len(tx.Ops()) == 0 {
				break // Nothing to purge
			}

			// Attempt to commit the transaction
			err = cfg.LeaseProvider.LeaseCommit(tx)
			if err == nil {
				break
			}
			printf(cfg.Logger, "Purge of \"%s\" failed: %v\n", resource, err)
		}
		if err != nil {
			return err
		}
	}
	return nil
}

func initRequest(cfg ServerConfig, r *http.Request) (req transport.Request, policies policy.Set, err error) {
	req, err = parseRequest(r)
	if err != nil {
		err = fmt.Errorf("unable to parse request: %v", err)
		return
	}

	all, err := cfg.PolicyProvider.Policies()
	if err != nil {
		err = fmt.Errorf("unable to retrieve policies: %v", err)
		return
	}

	policies = all.Match(req.Resource, req.Consumer, req.Environment)

	resource := policies.Resource()
	if resource != "" {
		req.Resource = resource
	}
	req.Environment["resource.id"] = req.Resource

	consumer := policies.Consumer()
	if consumer != "" {
		req.Consumer = consumer
	}

	env := policies.Environment()
	if len(env) > 0 {
		req.Environment = environment.Merge(req.Environment, env)
	}

	if req.Resource == "" {
		err = errors.New("resource not specified or determinable")
	} else if req.Consumer == "" {
		err = errors.New("consumer not specified or determinable")
	}
	return
}

func parseRequest(r *http.Request) (req transport.Request, err error) {
	err = r.ParseForm()
	if err != nil {
		return
	}
	req.Environment = make(environment.Environment)
	for k, values := range r.Form {
		if len(values) == 0 {
			continue
		}
		value := values[0] // Ignore multiple values
		switch k {
		case "resource":
			req.Resource = value
		case "consumer":
			req.Consumer = value
		case "instance":
			req.Instance = value
		default:
			req.Environment[k] = value
		}
	}
	return
}

func statsSummary(limit uint, stats lease.Stats, strat strategy.Strategy) string {
	consumed := stats.Consumed(strat)
	active := stats.Active(strat)
	released := stats.Released(strat)
	queued := stats.Queued(strat)
	return fmt.Sprintf("alloc: %d/%d, active: %d, released: %d, queued: %d", consumed, limit, active, released, queued)
}

func printf(logger *log.Logger, format string, v ...interface{}) {
	if logger != nil {
		logger.Printf(format, v...)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {

	// Get options provided on the command line.
	var protocol string
	var rate uint64
	flag.StringVar(&protocol, "protocol", "tcp", "[tcp|udp]")
	flag.Uint64Var(&rate, "rate", 1000, "number of stats entries per second to generate")
	flag.Parse()

	// Validate the protocol and the rate.
	if protocol != "tcp" && protocol != "udp" {
		fmt.Printf("protocol must be \"tcp\" or \"udp\"\n")
		os.Exit(1)
	}
	if rate < 5 {
		fmt.Printf("rate must be greater than or equal to 5\n")
		os.Exit(1)
	}
	if rate%5 != 0 {
		fmt.Printf("rate must be divisible by 5\n")
		os.Exit(1)
	}

	// We emit 5 stats entries per tick, so factor the rate and calculate the tick interval.
	rate = rate / 5
	var tickInterval time.Duration
	var perTick int
	if rate >= 2000 {
		if rate%10 != 0 {
			fmt.Printf("rate must be divisible by 10\n")
			os.Exit(1)
		}
		rate = rate / 10
		perTick = 10
	} else {
		perTick = 1
	}
	tickInterval = time.Second / time.Duration(rate)

	// Respond gracefully to Ctrl+C.
	var sigterm = make(chan os.Signal, 1)
	signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)

	// Open a TCP connection to Cassabon.
	conn, err := net.Dial(protocol, "127.0.0.1:2003")
	if err != nil {
		fmt.Printf("%v\nDid you start Cassabon?\n", err)
		os.Exit(1)
	}
	defer conn.Close()

	// Set the tick frequency (smaller numbers == faster).
	statsTicker := time.NewTicker(tickInterval)
	defer statsTicker.Stop()

	// Emit 5 stats on every tick.
	var ts, counter float64
	var countUp bool = true
	for _ = range statsTicker.C {
		select {
		case <-sigterm:
			fmt.Printf("INTERRUPT\n")
			return
		default:

			// Use a common time for all entries in this tick.
			ts = float64(time.Now().Unix())

			for i := 0; i < perTick; i++ {
				// Counter varies from 1..100, alternating between counting up and down.
				// Note: this is floating point, don't attempt "==".
				if countUp {
					if counter > 98.0 {
						countUp = false
					}
					counter += 1.0
				} else {
					if counter < 3.0 {
						countUp = true
					}
					counter -= 1.0
				}

				// Exercise the different aggregation methods.
				fmt.Fprintf(conn, "foo.bar.baz.average %f %f\n", counter, ts)
				fmt.Fprintf(conn, "foo.bar.baz.max %f %f\n", counter, ts)
				fmt.Fprintf(conn, "foo.bar.baz.min %f %f\n", counter, ts)
				fmt.Fprintf(conn, "foo.bar.baz.sum %f %f\n", counter, ts)

				// Provide a true count of the number of stats emitted.
				fmt.Fprintf(conn, "foo.bar.baz.count %f %f\n", 5.0, ts)
			}
		}
	}
}
<commit_msg>Make stresstest app recover from broken connections and retry.<commit_after>// Generate dummy Carbon stats for exercising Cassabon.
package main

import (
	"flag"
	"fmt"
	"net"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {

	// Get options provided on the command line.
	var protocol string
	var rate uint64
	flag.StringVar(&protocol, "protocol", "tcp", "[tcp|udp]")
	flag.Uint64Var(&rate, "rate", 30, "number of stats entries per second to generate")
	flag.Parse()

	// Validate the protocol and the rate.
	if protocol != "tcp" && protocol != "udp" {
		fmt.Printf("protocol must be \"tcp\" or \"udp\"\n")
		os.Exit(1)
	}
	if rate < 5 {
		fmt.Printf("rate must be greater than or equal to 5\n")
		os.Exit(1)
	}
	if rate%5 != 0 {
		fmt.Printf("rate must be divisible by 5\n")
		os.Exit(1)
	}

	// We emit 5 stats entries per tick, so factor the rate and calculate the tick interval.
	rate = rate / 5
	var tickInterval time.Duration
	var perTick int
	if rate >= 2000 {
		// For high tick rates, increase accuracy by scaling down the rate and
		// doing more work per tick.
		if rate%10 != 0 {
			fmt.Printf("rate must be divisible by 10\n")
			os.Exit(1)
		}
		rate = rate / 10
		perTick = 10
	} else {
		perTick = 1
	}
	tickInterval = time.Second / time.Duration(rate)

	// Respond gracefully to Ctrl+C.
	var sigterm = make(chan os.Signal, 1)
	signal.Notify(sigterm, syscall.SIGINT, syscall.SIGTERM)

	pc := patientConn{}
	pc.Init()
	if protocol == "tcp" {
		// Open a TCP connection to Cassabon.
		if err := pc.OpenTCP("127.0.0.1", 2003); err != nil {
			fmt.Printf("%v\nDid you start Cassabon?\n", err)
			os.Exit(1)
		}
	} else {
		// Open a UDP connection to Cassabon (doesn't fail if Cassabon is not running).
		if err := pc.OpenUDP("127.0.0.1", 2003); err != nil {
			fmt.Printf("%v\n", err) // Must be some other error
			os.Exit(1)
		}
	}
	defer pc.Close()

	// Set the tick frequency (smaller numbers == faster).
	statsTicker := time.NewTicker(tickInterval)
	defer statsTicker.Stop()

	// Emit 5 stats on every tick.
	var ts, counter float64
	var countUp bool = true
	for _ = range statsTicker.C {
		select {
		case <-sigterm:
			// CTRL+C
			fmt.Printf("INTERRUPT\n")
			return
		default:

			// Use a common time for all entries in this tick.
			ts = float64(time.Now().Unix())

			for i := 0; i < perTick; i++ {
				// Counter varies from 1..100, alternating between counting up and down.
				// Note: this is floating point, don't attempt "==".
				if countUp {
					if counter > 98.0 {
						countUp = false
					}
					counter += 1.0
				} else {
					if counter < 3.0 {
						countUp = true
					}
					counter -= 1.0
				}

				// Exercise the different aggregation methods.
				if !pc.Emit("foo.bar.baz.average %f %f\n", counter, ts) {
					break
				}
				if !pc.Emit("foo.bar.baz.max %f %f\n", counter, ts) {
					break
				}
				if !pc.Emit("foo.bar.baz.min %f %f\n", counter, ts) {
					break
				}
				if !pc.Emit("foo.bar.baz.sum %f %f\n", counter, ts) {
					break
				}

				// Provide a true count of the number of stats emitted.
				if !pc.Emit("foo.bar.baz.count %f %f\n", 5.0, ts) {
					break
				}
			}
		}
	}
}

// patientConn detects write failures and retries the connection until it succeeds.
type patientConn struct {
	sigterm chan os.Signal
	proto   string
	tcp     struct {
		isOpen bool
		addr   *net.TCPAddr
		conn   *net.TCPConn
	}
	udp struct {
		isOpen bool
		addr   *net.UDPAddr
		conn   *net.UDPConn
	}
}

// Init performs one-time initialization.
func (pc *patientConn) Init() {
	pc.sigterm = make(chan os.Signal, 1)
	signal.Notify(pc.sigterm, syscall.SIGINT, syscall.SIGTERM)
}

func (pc *patientConn) internalOpenTCP() error {
	var err error
	if pc.tcp.conn, err = net.DialTCP("tcp4", nil, pc.tcp.addr); err == nil {
		pc.tcp.isOpen = true
	}
	return err
}

func (pc *patientConn) internalOpenUDP() error {
	var err error
	if pc.udp.conn, err = net.DialUDP("udp4", nil, pc.udp.addr); err == nil {
		pc.udp.isOpen = true
	}
	return err
}

// OpenTCP makes the initial TCP connection to Cassabon.
func (pc *patientConn) OpenTCP(host string, port int) error {
	fmt.Printf("Connecting to Cassabon at %s:%04d over TCP\n", host, port)
	pc.proto = "tcp"
	pc.tcp.addr = &net.TCPAddr{IP: net.ParseIP(host), Port: port}
	return pc.internalOpenTCP()
}

// OpenUDP makes the initial UDP connection to Cassabon.
// Note: This does NOT return an error if Cassabon isn't running.
func (pc *patientConn) OpenUDP(host string, port int) error {
	fmt.Printf("Connecting to Cassabon at %s:%04d over UDP\n", host, port)
	pc.proto = "udp"
	pc.udp.addr = &net.UDPAddr{IP: net.ParseIP(host), Port: port}
	return pc.internalOpenUDP()
}

// Close performs a guarded close of the connection.
func (pc *patientConn) Close() {
	if pc.tcp.isOpen {
		pc.tcp.conn.Close()
	}
	if pc.udp.isOpen {
		pc.udp.conn.Close()
	}
	pc.tcp.isOpen = false
	pc.udp.isOpen = false
}

// Emit closes the connection if a write fails, and attempts a re-open on each
// subsequent call. Returns FALSE if interrupted while retrying.
func (pc *patientConn) Emit(format string, a ...interface{}) bool {

	wait := false

	if pc.proto == "tcp" {
		if pc.tcp.isOpen {
			if _, err := fmt.Fprintf(pc.tcp.conn, format, a...); err != nil {
				fmt.Printf("%v\nRetrying connection...", err)
				pc.Close()
			}
		} else {
			if err := pc.internalOpenTCP(); err == nil {
				fmt.Printf(" connection resumed\n")
			} else {
				wait = true
			}
		}
	} else {
		if _, err := fmt.Fprintf(pc.udp.conn, format, a...); err != nil {
			wait = true // No recovery needed; simply avoid tight retry loop.
		}
	}

	if wait {
		select {
		case <-pc.sigterm:
			return false
		case <-time.After(time.Second):
		}
	}
	return true
}
<|endoftext|>


<commit_before>// Copyright 2013-2014 go-diameter authors.  All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package dict

import "testing"

func TestFindAVP(t *testing.T) {
	if _, err := Default.FindAVP(0, 263); err != nil {
		t.Fatal(err)
	}
}

func TestScanAVP(t *testing.T) {
	if avp, err := Default.ScanAVP("Session-Id"); err != nil {
		t.Error(err)
	} else if avp.Code != 263 {
		t.Fatalf("Unexpected code %d for Session-Id AVP", avp.Code)
	}
}

func TestFindCmd(t *testing.T) {
	if cmd, err := Default.FindCMD(0, 257); err != nil {
		t.Error(err)
	} else if cmd.Short != "CE" {
		t.Fatalf("Unexpected command: %#v", cmd)
	}
}

func TestEnum(t *testing.T) {
	if item, err := Default.Enum(0, 274, 1); err != nil {
		t.Fatal(err)
	} else if item.Name != "AUTHENTICATE_ONLY" {
		t.Errorf(
			"Unexpected value %s, expected AUTHENTICATE_ONLY",
			item.Name,
		)
	}
}

func TestRule(t *testing.T) {
	if rule, err := Default.Rule(0, 284, "Proxy-Host"); err != nil {
		t.Fatal(err)
	} else if !rule.Required {
		t.Errorf("Unexpected rule %#v", rule)
	}
}
<commit_msg>Added benchmarks<commit_after>// Copyright 2013-2014 go-diameter authors.  All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package dict

import "testing"

func TestFindAVP(t *testing.T) {
	if _, err := Default.FindAVP(0, 263); err != nil {
		t.Fatal(err)
	}
}

func TestScanAVP(t *testing.T) {
	if avp, err := Default.ScanAVP("Session-Id"); err != nil {
		t.Error(err)
	} else if avp.Code != 263 {
		t.Fatalf("Unexpected code %d for Session-Id AVP", avp.Code)
	}
}

func TestFindCmd(t *testing.T) {
	if cmd, err := Default.FindCMD(0, 257); err != nil {
		t.Error(err)
	} else if cmd.Short != "CE" {
		t.Fatalf("Unexpected command: %#v", cmd)
	}
}

func TestEnum(t *testing.T) {
	if item, err := Default.Enum(0, 274, 1); err != nil {
		t.Fatal(err)
	} else if item.Name != "AUTHENTICATE_ONLY" {
		t.Errorf(
			"Unexpected value %s, expected AUTHENTICATE_ONLY",
			item.Name,
		)
	}
}

func TestRule(t *testing.T) {
	if rule, err := Default.Rule(0, 284, "Proxy-Host"); err != nil {
		t.Fatal(err)
	} else if !rule.Required {
		t.Errorf("Unexpected rule %#v", rule)
	}
}

func BenchmarkFindAVPName(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Default.FindAVP(0, "Session-Id")
	}
}

func BenchmarkFindAVPCode(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Default.FindAVP(0, 263)
	}
}

func BenchmarkScanAVPName(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Default.ScanAVP("Session-Id")
	}
}

func BenchmarkScanAVPCode(b *testing.B) {
	for n := 0; n < b.N; n++ {
		Default.ScanAVP(263)
	}
}
<|endoftext|>


<commit_before>package strrand

import (
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// RandomString returns random string from pattern
func RandomString(pattern string) (string, error) {
	return New().Generate(pattern)
}

type picker interface {
	pick() string
}

func makeRange(from rune, to rune) (r []string) {
	for i := from; i <= to; i++ {
		r = append(r, string(i))
	}
	return r
}

func concat(ss ...[]string) (r []string) {
	for _, s := range ss {
		r = append(r, s...)
	}
	return r
}

type chrPicker []string

func (p chrPicker) pick() string {
	if len(p) < 1 {
		return ""
	}
	idx := rand.Intn(len(p))
	return p[idx]
}

type variantPicker struct {
	min    uint64
	max    uint64
	picker picker
}

func (vp variantPicker) pick() (str string) {
	num := vp.decidePickNum()
	for i := 0; i < num; i++ {
		str += vp.picker.pick()
	}
	return str
}

func (vp variantPicker) decidePickNum() int {
	if vp.max < vp.min {
		return 0
	}
	if vp.max == vp.min {
		return int(vp.min)
	}
	fluct := vp.max - vp.min

	return int(vp.min) + rand.Intn(int(fluct))
}

func init() {
	rand.Seed(time.Now().Unix())
}

var upper chrPicker = makeRange('A', 'Z')
var lower chrPicker = makeRange('a', 'z')
var digit chrPicker = makeRange('0', '9')

var punct chrPicker = concat(makeRange(33, 47), makeRange(58, 64), makeRange(91, 96), makeRange(123, 126))
var any chrPicker = concat(upper, lower, digit, punct)

var patterns = map[string]chrPicker{
	"d": chrPicker(digit),
	"D": chrPicker(concat(upper, lower, punct)),
	"w": chrPicker(concat(upper, lower, digit, []string{"_"})),
	// W: punct without '_'
	"W": chrPicker(concat(makeRange(33, 47), makeRange(58, 64), makeRange(91, 94), makeRange(96, 96), makeRange(123, 126))),
	"s": chrPicker([]string{" ", "\t"}),
	"S": chrPicker(concat(upper, lower, digit, punct)),

	// these are translated to their double quoted equivalents.
	"t": chrPicker([]string{"\t"}),
	"n": chrPicker([]string{"\n"}),
	"r": chrPicker([]string{"\r"}),
	"f": chrPicker([]string{"\f"}),
	"a": chrPicker([]string{"\a"}),
	"e": chrPicker([]string{string(27)}), // escape character
}

type strrand struct {
	Max uint
}

type generator interface {
	Generate() string
}

type pickers []picker

func (pis pickers) Generate() string {
	result := ""
	for _, p := range pis {
		result += p.pick()
	}
	return result
}

// New returns strrand struct
func New() *strrand {
	return &strrand{}
}

var defaultMax uint = 10

func (sr *strrand) max() uint {
	if sr.Max < 1 {
		return defaultMax
	}
	return sr.Max
}

// Generate generates random string
func (sr *strrand) Generate(pattern string) (string, error) {
	result := ""
	g, err := sr.CreateGenerator(pattern)
	if err != nil {
		return result, err
	}
	return g.Generate(), nil
}

// Creategenerator returns random string generator
func (sr *strrand) CreateGenerator(pattern string) (generator, error) {
	pis := pickers([]picker{})
	chars := func() *[]string {
		c := strings.Split(pattern, "")
		return &c
	}()

	for len(*chars) > 0 {
		chr := (*chars)[0]
		*chars = (*chars)[1:]

		switch chr {
		case "\\":
			p, err := sr.handleEscape(chars)
			if err != nil {
				return nil, err
			}
			pis = append(pis, p)
		case ".":
			pis = append(pis, any)
		case "[":
			p, err := sr.handleBracket(chars)
			if err != nil {
				return nil, err
			}
			pis = append(pis, p)
		case "*":
			*chars = append(strings.Split("{0,}", ""), (*chars)...)
		case "+":
			*chars = append(strings.Split("{1,}", ""), (*chars)...)
		case "?":
			*chars = append(strings.Split("{0,1}", ""), (*chars)...)
		case "{":
			if len(pis) < 1 {
				return pis, fmt.Errorf("invalid bracket")
			}
			pop := pis[len(pis)-1]
			pis = pis[:len(pis)-1]
			p, err := sr.handleBrace(chars, pop)
			if err != nil {
				return pis, err
			}
			pis = append(pis, p)
		default:
			pis = append(pis, chrPicker([]string{chr}))
		}
	}
	return pis, nil
}

func (sr *strrand) handleEscape(chars *[]string) (picker, error) {
	if len(*chars) < 1 {
		return chrPicker([]string{}), fmt.Errorf("regex not terminated")
	}
	chr := (*chars)[0]
	*chars = (*chars)[1:]

	if val, ok := patterns[chr]; ok {
		return val, nil
	}

	switch chr {
	case "x":
		if len(*chars) < 2 {
			return nil, fmt.Errorf("invalid hex format")
		}
		hex := (*chars)[0] + (*chars)[1]
		*chars = (*chars)[2:]

		c, err := strconv.ParseInt(hex, 16, 32)
		if err != nil {
			return nil, err
		}
		return chrPicker([]string{string(c)}), nil
	default:
		return chrPicker([]string{chr}), nil
	}
}

func (sr *strrand) handleBracket(chars *[]string) (picker, error) {
	p := chrPicker([]string{})
	escaped := false
	rangeJustFinished := false
	startChr := ""

	for {
		if len(*chars) < 1 {
			return nil, fmt.Errorf("unmatched []")
		}
		chr := (*chars)[0]
		*chars = (*chars)[1:]

		if !escaped {
			if chr == "]" {
				break
			}

			if chr == `\` {
				escaped = true
				continue
			}

			if chr == "-" && len(p) > 0 {
				if rangeJustFinished {
					return nil, fmt.Errorf("invalid range")
				}
				startChr = p[len(p)-1]
				p = p[:len(p)-1]
				continue
			}
		}

		escaped = false
		if startChr != "" {
			r := makeRange(([]rune(startChr))[0], ([]rune(chr))[0])
			p = append(p, r...)
			rangeJustFinished = true
			startChr = ""
			continue
		}
		rangeJustFinished = false
		p = append(p, chr)
	}
	return p, nil
}

var rangeChrRe = regexp.MustCompile(`[0-9,]`)
var rangeRe = regexp.MustCompile(`^([0-9]+),([0-9]+)?$`)

func (sr *strrand) handleBrace(chars *[]string, p picker) (picker, error) {
	hasComma := false
	buf := ""
	for {
		if len(*chars) < 1 {
			return nil, fmt.Errorf("bad range %s", buf)
		}
		chr := (*chars)[0]
		*chars = (*chars)[1:]
		if chr == "}" {
			break
		}
		if !rangeChrRe.MatchString(chr) {
			return nil, fmt.Errorf("'%s' inside {} not supported", chr)
		}
		if chr == "," {
			hasComma = true
		}
		buf += chr
	}
	var min, max uint64

	if hasComma {
		matches := rangeRe.FindStringSubmatch(buf)
		if len(matches) == 0 {
			return nil, fmt.Errorf("malformed range: {%s}", buf)
		}

		min, _ = strconv.ParseUint(matches[1], 10, 64)
		if matches[2] == "" {
			max = uint64(sr.max())
		} else {
			max, _ = strconv.ParseUint(matches[2], 10, 64)
		}
		if max < min {
			return nil, fmt.Errorf("bad range {%s}", buf)
		}
	} else {
		min, _ = strconv.ParseUint(buf, 10, 64)
		max = min
	}

	return variantPicker{
		max:    max,
		min:    min,
		picker: p,
	}, nil
}
<commit_msg>fix interface<commit_after>package strrand

import (
	"fmt"
	"math/rand"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// RandomString returns random string from pattern
func RandomString(pattern string) (string, error) {
	return New().Generate(pattern)
}

type picker interface {
	pick() string
}

func makeRange(from rune, to rune) (r []string) {
	for i := from; i <= to; i++ {
		r = append(r, string(i))
	}
	return r
}

func concat(ss ...[]string) (r []string) {
	for _, s := range ss {
		r = append(r, s...)
	}
	return r
}

type chrPicker []string

func (p chrPicker) pick() string {
	if len(p) < 1 {
		return ""
	}
	idx := rand.Intn(len(p))
	return p[idx]
}

type variantPicker struct {
	min    uint64
	max    uint64
	picker picker
}

func (vp variantPicker) pick() (str string) {
	num := vp.decidePickNum()
	for i := 0; i < num; i++ {
		str += vp.picker.pick()
	}
	return str
}

func (vp variantPicker) decidePickNum() int {
	if vp.max < vp.min {
		return 0
	}
	if vp.max == vp.min {
		return int(vp.min)
	}
	fluct := vp.max - vp.min

	return int(vp.min) + rand.Intn(int(fluct))
}

func init() {
	rand.Seed(time.Now().Unix())
}

var upper chrPicker = makeRange('A', 'Z')
var lower chrPicker = makeRange('a', 'z')
var digit chrPicker = makeRange('0', '9')

var punct chrPicker = concat(makeRange(33, 47), makeRange(58, 64), makeRange(91, 96), makeRange(123, 126))
var any chrPicker = concat(upper, lower, digit, punct)

var patterns = map[string]chrPicker{
	"d": chrPicker(digit),
	"D": chrPicker(concat(upper, lower, punct)),
	"w": chrPicker(concat(upper, lower, digit, []string{"_"})),
	// W: punct without '_'
	"W": chrPicker(concat(makeRange(33, 47), makeRange(58, 64), makeRange(91, 94), makeRange(96, 96), makeRange(123, 126))),
	"s": chrPicker([]string{" ", "\t"}),
	"S": chrPicker(concat(upper, lower, digit, punct)),

	// these are translated to their double quoted equivalents.
	"t": chrPicker([]string{"\t"}),
	"n": chrPicker([]string{"\n"}),
	"r": chrPicker([]string{"\r"}),
	"f": chrPicker([]string{"\f"}),
	"a": chrPicker([]string{"\a"}),
	"e": chrPicker([]string{string(27)}), // escape character
}

type Strrand struct {
	Max uint
}

type generator interface {
	Generate() string
}

type pickers []picker

func (pis pickers) Generate() string {
	result := ""
	for _, p := range pis {
		result += p.pick()
	}
	return result
}

// New returns Strrand struct
func New() *Strrand {
	return &Strrand{}
}

var defaultMax uint = 10

func (sr *Strrand) max() uint {
	if sr.Max < 1 {
		return defaultMax
	}
	return sr.Max
}

// Generate generates random string
func (sr *Strrand) Generate(pattern string) (string, error) {
	result := ""
	g, err := sr.CreateGenerator(pattern)
	if err != nil {
		return result, err
	}
	return g.Generate(), nil
}

// Creategenerator returns random string generator
func (sr *Strrand) CreateGenerator(pattern string) (generator, error) {
	pis := pickers([]picker{})
	chars := func() *[]string {
		c := strings.Split(pattern, "")
		return &c
	}()

	for len(*chars) > 0 {
		chr := (*chars)[0]
		*chars = (*chars)[1:]

		switch chr {
		case "\\":
			p, err := sr.handleEscape(chars)
			if err != nil {
				return nil, err
			}
			pis = append(pis, p)
		case ".":
			pis = append(pis, any)
		case "[":
			p, err := sr.handleBracket(chars)
			if err != nil {
				return nil, err
			}
			pis = append(pis, p)
		case "*":
			*chars = append(strings.Split("{0,}", ""), (*chars)...)
		case "+":
			*chars = append(strings.Split("{1,}", ""), (*chars)...)
		case "?":
			*chars = append(strings.Split("{0,1}", ""), (*chars)...)
		case "{":
			if len(pis) < 1 {
				return pis, fmt.Errorf("invalid bracket")
			}
			pop := pis[len(pis)-1]
			pis = pis[:len(pis)-1]
			p, err := sr.handleBrace(chars, pop)
			if err != nil {
				return pis, err
			}
			pis = append(pis, p)
		default:
			pis = append(pis, chrPicker([]string{chr}))
		}
	}
	return pis, nil
}

func (sr *Strrand) handleEscape(chars *[]string) (picker, error) {
	if len(*chars) < 1 {
		return chrPicker([]string{}), fmt.Errorf("regex not terminated")
	}
	chr := (*chars)[0]
	*chars = (*chars)[1:]

	if val, ok := patterns[chr]; ok {
		return val, nil
	}

	switch chr {
	case "x":
		if len(*chars) < 2 {
			return nil, fmt.Errorf("invalid hex format")
		}
		hex := (*chars)[0] + (*chars)[1]
		*chars = (*chars)[2:]

		c, err := strconv.ParseInt(hex, 16, 32)
		if err != nil {
			return nil, err
		}
		return chrPicker([]string{string(c)}), nil
	default:
		return chrPicker([]string{chr}), nil
	}
}

func (sr *Strrand) handleBracket(chars *[]string) (picker, error) {
	p := chrPicker([]string{})
	escaped := false
	rangeJustFinished := false
	startChr := ""

	for {
		if len(*chars) < 1 {
			return nil, fmt.Errorf("unmatched []")
		}
		chr := (*chars)[0]
		*chars = (*chars)[1:]

		if !escaped {
			if chr == "]" {
				break
			}

			if chr == `\` {
				escaped = true
				continue
			}

			if chr == "-" && len(p) > 0 {
				if rangeJustFinished {
					return nil, fmt.Errorf("invalid range")
				}
				startChr = p[len(p)-1]
				p = p[:len(p)-1]
				continue
			}
		}

		escaped = false
		if startChr != "" {
			r := makeRange(([]rune(startChr))[0], ([]rune(chr))[0])
			p = append(p, r...)
			rangeJustFinished = true
			startChr = ""
			continue
		}
		rangeJustFinished = false
		p = append(p, chr)
	}
	return p, nil
}

var rangeChrRe = regexp.MustCompile(`[0-9,]`)
var rangeRe = regexp.MustCompile(`^([0-9]+),([0-9]+)?$`)

func (sr *Strrand) handleBrace(chars *[]string, p picker) (picker, error) {
	hasComma := false
	buf := ""
	for {
		if len(*chars) < 1 {
			return nil, fmt.Errorf("bad range %s", buf)
		}
		chr := (*chars)[0]
		*chars = (*chars)[1:]
		if chr == "}" {
			break
		}
		if !rangeChrRe.MatchString(chr) {
			return nil, fmt.Errorf("'%s' inside {} not supported", chr)
		}
		if chr == "," {
			hasComma = true
		}
		buf += chr
	}
	var min, max uint64

	if hasComma {
		matches := rangeRe.FindStringSubmatch(buf)
		if len(matches) == 0 {
			return nil, fmt.Errorf("malformed range: {%s}", buf)
		}

		min, _ = strconv.ParseUint(matches[1], 10, 64)
		if matches[2] == "" {
			max = uint64(sr.max())
		} else {
			max, _ = strconv.ParseUint(matches[2], 10, 64)
		}
		if max < min {
			return nil, fmt.Errorf("bad range {%s}", buf)
		}
	} else {
		min, _ = strconv.ParseUint(buf, 10, 64)
		max = min
	}

	return variantPicker{
		max:    max,
		min:    min,
		picker: p,
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Belogik. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goes

import (
	"net/http"
	"net/url"
)

// Represents a Connection object to elasticsearch
type Connection struct {
	// The host to connect to
	Host string

	// The port to use
	Port string

	// Client is the http client used to make requests, allowing settings things
	// such as timeouts etc
	Client *http.Client
}

// Represents a Request to elasticsearch
type Request struct {
	// Which connection will be used
	Conn *Connection

	// A search query
	Query interface{}

	// Which index to search into
	IndexList []string

	// Which type to search into
	TypeList []string

	// HTTP Method to user (GET, POST ...)
	method string

	// Which api keyword (_search, _bulk, etc) to use
	api string

	// Bulk data
	bulkData []byte

	// Request body
	Body []byte

	// A list of extra URL arguments
	ExtraArgs url.Values

	// Used for the id field when indexing a document
	id string
}

// Represents a Response from elasticsearch
type Response struct {
	Acknowledged bool
	Error        string
	Errors       bool
	Status       uint64
	Took         uint64
	TimedOut     bool  `json:"timed_out"`
	Shards       Shard `json:"_shards"`
	Hits         Hits
	Index        string `json:"_index"`
	Id           string `json:"_id"`
	Type         string `json:"_type"`
	Version      int    `json:"_version"`
	Found        bool
	Count        int

	// Used by the _stats API
	All All `json:"_all"`

	// Used by the _bulk API
	Items []map[string]Item `json:"items,omitempty"`

	// Used by the GET API
	Source map[string]interface{} `json:"_source"`
	Fields map[string]interface{} `json:"fields"`

	// Used by the _status API
	Indices map[string]IndexStatus

	// Scroll id for iteration
	ScrollId string `json:"_scroll_id"`

	Aggregations map[string]Aggregation `json:"aggregations,omitempty"`

	Raw map[string]interface{}
}

// Represents an aggregation from response
type Aggregation map[string]interface{}

// Represents a bucket for aggregation
type Bucket map[string]interface{}

// Represents a document to send to elasticsearch
type Document struct {
	// XXX : interface as we can support nil values
	Index       interface{}
	Type        string
	Id          interface{}
	BulkCommand string
	Fields      interface{}
}

// Represents the "items" field in a _bulk response
type Item struct {
	Type    string `json:"_type"`
	Id      string `json:"_id"`
	Index   string `json:"_index"`
	Version int    `json:"_version"`
	Error   string `json:"error"`
	Status  uint64 `json:"status"`
}

// Represents the "_all" field when calling the _stats API
// This is minimal but this is what I only need
type All struct {
	Indices   map[string]StatIndex   `json:"indices"`
	Primaries map[string]StatPrimary `json:"primaries"`
}

type StatIndex struct {
	Primaries map[string]StatPrimary `json:"primaries"`
}

type StatPrimary struct {
	// primary/docs:
	Count   int
	Deleted int
}

// Represents the "shard" struct as returned by elasticsearch
type Shard struct {
	Total      uint64
	Successful uint64
	Failed     uint64
}

// Represent a hit returned by a search
type Hit struct {
	Index  string                 `json:"_index"`
	Type   string                 `json:"_type"`
	Id     string                 `json:"_id"`
	Score  float64                `json:"_score"`
	Source map[string]interface{} `json:"_source"`
	Fields map[string]interface{} `json:"fields"`
}

// Represent the hits structure as returned by elasticsearch
type Hits struct {
	Total uint64
	// max_score may contain the "null" value
	MaxScore interface{} `json:"max_score"`
	Hits     []Hit
}

type SearchError struct {
	Msg        string
	StatusCode uint64
}

// Represent the status for a given index for the _status command
type IndexStatus struct {
	// XXX : problem, int will be marshaled to a float64 which seems logical
	// XXX : is it better to use strings even for int values or to keep
	// XXX : interfaces and deal with float64 ?
	Index map[string]interface{}

	Translog map[string]uint64
	Docs     map[string]uint64
	Merges   map[string]interface{}
	Refresh  map[string]interface{}
	Flush    map[string]interface{}

	// TODO: add shards support later, we do not need it for the moment
}
<commit_msg>Add "Highlight" field to "Hit" structure.<commit_after>// Copyright 2013 Belogik. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goes

import (
	"net/http"
	"net/url"
)

// Represents a Connection object to elasticsearch
type Connection struct {
	// The host to connect to
	Host string

	// The port to use
	Port string

	// Client is the http client used to make requests, allowing settings things
	// such as timeouts etc
	Client *http.Client
}

// Represents a Request to elasticsearch
type Request struct {
	// Which connection will be used
	Conn *Connection

	// A search query
	Query interface{}

	// Which index to search into
	IndexList []string

	// Which type to search into
	TypeList []string

	// HTTP Method to user (GET, POST ...)
	method string

	// Which api keyword (_search, _bulk, etc) to use
	api string

	// Bulk data
	bulkData []byte

	// Request body
	Body []byte

	// A list of extra URL arguments
	ExtraArgs url.Values

	// Used for the id field when indexing a document
	id string
}

// Represents a Response from elasticsearch
type Response struct {
	Acknowledged bool
	Error        string
	Errors       bool
	Status       uint64
	Took         uint64
	TimedOut     bool  `json:"timed_out"`
	Shards       Shard `json:"_shards"`
	Hits         Hits
	Index        string `json:"_index"`
	Id           string `json:"_id"`
	Type         string `json:"_type"`
	Version      int    `json:"_version"`
	Found        bool
	Count        int

	// Used by the _stats API
	All All `json:"_all"`

	// Used by the _bulk API
	Items []map[string]Item `json:"items,omitempty"`

	// Used by the GET API
	Source map[string]interface{} `json:"_source"`
	Fields map[string]interface{} `json:"fields"`

	// Used by the _status API
	Indices map[string]IndexStatus

	// Scroll id for iteration
	ScrollId string `json:"_scroll_id"`

	Aggregations map[string]Aggregation `json:"aggregations,omitempty"`

	Raw map[string]interface{}
}

// Represents an aggregation from response
type Aggregation map[string]interface{}

// Represents a bucket for aggregation
type Bucket map[string]interface{}

// Represents a document to send to elasticsearch
type Document struct {
	// XXX : interface as we can support nil values
	Index       interface{}
	Type        string
	Id          interface{}
	BulkCommand string
	Fields      interface{}
}

// Represents the "items" field in a _bulk response
type Item struct {
	Type    string `json:"_type"`
	Id      string `json:"_id"`
	Index   string `json:"_index"`
	Version int    `json:"_version"`
	Error   string `json:"error"`
	Status  uint64 `json:"status"`
}

// Represents the "_all" field when calling the _stats API
// This is minimal but this is what I only need
type All struct {
	Indices   map[string]StatIndex   `json:"indices"`
	Primaries map[string]StatPrimary `json:"primaries"`
}

type StatIndex struct {
	Primaries map[string]StatPrimary `json:"primaries"`
}

type StatPrimary struct {
	// primary/docs:
	Count   int
	Deleted int
}

// Represents the "shard" struct as returned by elasticsearch
type Shard struct {
	Total      uint64
	Successful uint64
	Failed     uint64
}

// Represent a hit returned by a search
type Hit struct {
	Index     string                 `json:"_index"`
	Type      string                 `json:"_type"`
	Id        string                 `json:"_id"`
	Score     float64                `json:"_score"`
	Source    map[string]interface{} `json:"_source"`
	Highlight map[string]interface{} `json:"highlight"`
	Fields    map[string]interface{} `json:"fields"`
}

// Represent the hits structure as returned by elasticsearch
type Hits struct {
	Total uint64
	// max_score may contain the "null" value
	MaxScore interface{} `json:"max_score"`
	Hits     []Hit
}

type SearchError struct {
	Msg        string
	StatusCode uint64
}

// Represent the status for a given index for the _status command
type IndexStatus struct {
	// XXX : problem, int will be marshaled to a float64 which seems logical
	// XXX : is it better to use strings even for int values or to keep
	// XXX : interfaces and deal with float64 ?
	Index map[string]interface{}

	Translog map[string]uint64
	Docs     map[string]uint64
	Merges   map[string]interface{}
	Refresh  map[string]interface{}
	Flush    map[string]interface{}

	// TODO: add shards support later, we do not need it for the moment
}
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"time"

	"github.com/fweikert/continuous-integration/metrics/metrics"
	"github.com/fweikert/continuous-integration/metrics/publishers"
)

type metricJob struct {
	ticker     *time.Ticker
	metric     metrics.Metric
	publishers []publishers.Publisher
}

func (job *metricJob) start(handler ErrorHandler) {
	name := job.metric.Name()
	err := job.initialize()
	if err != nil {
		handler(name, err)
		return
	}

	go func() {
		for range job.ticker.C {
			newData, err := job.metric.Collect()
			if err != nil {
				handler(name, fmt.Errorf("Collection failed': %v", err))
				return
			}
			for _, p := range job.publishers {
				err = p.Publish(name, newData)
				if err != nil {
					handler(name, fmt.Errorf("Publishing to %s failed': %v", p.Name(), err))
				}
			}
		}
	}()
}

func (job *metricJob) initialize() error {
	for _, publisher := range job.publishers {
		err := publisher.RegisterMetric(job.metric)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *metricJob) stop() {
	job.ticker.Stop()
}

func createJob(metric metrics.Metric, updateIntervalSeconds uint, publisherInstances []publishers.Publisher) metricJob {
	return metricJob{metric: metric, ticker: time.NewTicker(time.Duration(updateIntervalSeconds) * time.Second), publishers: publisherInstances}
}

type ErrorHandler func(string, error)

type MetricService struct {
	jobs    map[string]metricJob
	handler ErrorHandler
}

func CreateService(handler ErrorHandler) *MetricService {
	return &MetricService{jobs: make(map[string]metricJob), handler: handler}
}

func (srv *MetricService) AddMetric(metric metrics.Metric, updateIntervalSeconds uint, publisherInstances ...publishers.Publisher) error {
	name := metric.Name()
	if _, ok := srv.jobs[name]; ok {
		return fmt.Errorf("There is already a job for metric '%s'", name)
	}
	srv.jobs[name] = createJob(metric, updateIntervalSeconds, publisherInstances)
	return nil
}

func (srv *MetricService) Start() {
	for _, j := range srv.jobs {
		j.start(srv.handler)
	}
}

func (srv *MetricService) Stop() {
	for _, j := range srv.jobs {
		j.stop()
	}
}
<commit_msg>Add logging for metric service.<commit_after>package service

import (
	"fmt"
	"log"
	"time"

	"github.com/fweikert/continuous-integration/metrics/metrics"
	"github.com/fweikert/continuous-integration/metrics/publishers"
)

type metricJob struct {
	ticker     *time.Ticker
	metric     metrics.Metric
	publishers []publishers.Publisher
}

func (job *metricJob) start(handler ErrorHandler) {
	name := job.metric.Name()
	err := job.initialize()
	if err != nil {
		handler(name, err)
		return
	}

	go func() {
		for range job.ticker.C {
			log.Printf("Collecting data for metric %s\n", job.metric.Name())
			newData, err := job.metric.Collect()
			if err != nil {
				handler(name, fmt.Errorf("Collection failed': %v", err))
				return
			}
			for _, p := range job.publishers {
				log.Printf("Publishing data for metric %s to %s\n", job.metric.Name(), p.Name())
				err = p.Publish(name, newData)
				if err != nil {
					handler(name, fmt.Errorf("Publishing to %s failed': %v", p.Name(), err))
				}
			}
		}
	}()
}

func (job *metricJob) initialize() error {
	for _, publisher := range job.publishers {
		err := publisher.RegisterMetric(job.metric)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *metricJob) stop() {
	job.ticker.Stop()
}

func createJob(metric metrics.Metric, updateIntervalSeconds uint, publisherInstances []publishers.Publisher) metricJob {
	return metricJob{metric: metric, ticker: time.NewTicker(time.Duration(updateIntervalSeconds) * time.Second), publishers: publisherInstances}
}

type ErrorHandler func(string, error)

type MetricService struct {
	jobs    map[string]metricJob
	handler ErrorHandler
}

func CreateService(handler ErrorHandler) *MetricService {
	return &MetricService{jobs: make(map[string]metricJob), handler: handler}
}

func (srv *MetricService) AddMetric(metric metrics.Metric, updateIntervalSeconds uint, publisherInstances ...publishers.Publisher) error {
	name := metric.Name()
	if _, ok := srv.jobs[name]; ok {
		return fmt.Errorf("There is already a job for metric '%s'", name)
	}
	srv.jobs[name] = createJob(metric, updateIntervalSeconds, publisherInstances)
	return nil
}

func (srv *MetricService) Start() {
	for _, j := range srv.jobs {
		j.start(srv.handler)
	}
}

func (srv *MetricService) Stop() {
	for _, j := range srv.jobs {
		j.stop()
	}
}
<|endoftext|>


<commit_before>package erasure_coding

import (
	"bytes"
	"fmt"
	"math/rand"
	"os"
	"testing"

	"github.com/chrislusf/seaweedfs/weed/storage/needle_map"
	"github.com/chrislusf/seaweedfs/weed/storage/types"
	"github.com/klauspost/reedsolomon"
)

const (
	largeBlockSize = 10000
	smallBlockSize = 100
)

func TestEncodingDecoding(t *testing.T) {
	bufferSize := 50
	baseFileName := "1"

	err := generateEcFiles(baseFileName, bufferSize, largeBlockSize, smallBlockSize)
	if err != nil {
		t.Logf("generateEcFiles: %v", err)
	}

	err = WriteSortedEcxFile(baseFileName)
	if err != nil {
		t.Logf("WriteSortedEcxFile: %v", err)
	}

	err = validateFiles(baseFileName)
	if err != nil {
		t.Logf("WriteSortedEcxFile: %v", err)
	}

	removeGeneratedFiles(baseFileName)

}

func validateFiles(baseFileName string) error {
	cm, err := readCompactMap(baseFileName)
	if err != nil {
		return fmt.Errorf("readCompactMap: %v", err)
	}

	datFile, err := os.OpenFile(baseFileName+".dat", os.O_RDONLY, 0)
	if err != nil {
		return fmt.Errorf("failed to open dat file: %v", err)
	}
	defer datFile.Close()

	fi, err := datFile.Stat()
	if err != nil {
		return fmt.Errorf("failed to stat dat file: %v", err)
	}

	ecFiles, err := openEcFiles(baseFileName, true)
	defer closeEcFiles(ecFiles)

	err = cm.AscendingVisit(func(value needle_map.NeedleValue) error {
		return assertSame(datFile, fi.Size(), ecFiles, value.Offset, value.Size)
	})
	if err != nil {
		return fmt.Errorf("failed to check ec files: %v", err)
	}
	return nil
}

func assertSame(datFile *os.File, datSize int64, ecFiles []*os.File, offset types.Offset, size uint32) error {

	data, err := readDatFile(datFile, offset, size)
	if err != nil {
		return fmt.Errorf("failed to read dat file: %v", err)
	}

	ecData, err := readEcFile(datSize, ecFiles, offset, size)
	if err != nil {
		return fmt.Errorf("failed to read ec file: %v", err)
	}

	if bytes.Compare(data, ecData) != 0 {
		return fmt.Errorf("unexpected data read")
	}

	return nil
}

func readDatFile(datFile *os.File, offset types.Offset, size uint32) ([]byte, error) {

	data := make([]byte, size)
	n, err := datFile.ReadAt(data, offset.ToAcutalOffset())
	if err != nil {
		return nil, fmt.Errorf("failed to ReadAt dat file: %v", err)
	}
	if n != int(size) {
		return nil, fmt.Errorf("unexpected read size %d, expected %d", n, size)
	}
	return data, nil
}

func readEcFile(datSize int64, ecFiles []*os.File, offset types.Offset, size uint32) (data []byte, err error) {

	intervals := LocateData(largeBlockSize, smallBlockSize, datSize, offset.ToAcutalOffset(), size)

	for i, interval := range intervals {
		if d, e := readOneInterval(interval, ecFiles); e != nil {
			return nil, e
		} else {
			if i == 0 {
				data = d
			} else {
				data = append(data, d...)
			}
		}
	}

	return data, nil
}

func readOneInterval(interval Interval, ecFiles []*os.File) (data []byte, err error) {

	ecFileIndex, ecFileOffset := interval.ToShardIdAndOffset(largeBlockSize, smallBlockSize)

	data = make([]byte, interval.Size)
	err = readFromFile(ecFiles[ecFileIndex], data, ecFileOffset)
	{ // do some ec testing
		ecData, err := readFromOtherEcFiles(ecFiles, int(ecFileIndex), ecFileOffset, interval.Size)
		if err != nil {
			return nil, fmt.Errorf("ec reconstruct error: %v", err)
		}
		if bytes.Compare(data, ecData) != 0 {
			return nil, fmt.Errorf("ec compare error")
		}
	}
	return
}

func readFromOtherEcFiles(ecFiles []*os.File, ecFileIndex int, ecFileOffset int64, size uint32) (data []byte, err error) {
	enc, err := reedsolomon.New(DataShardsCount, ParityShardsCount)
	if err != nil {
		return nil, fmt.Errorf("failed to create encoder: %v", err)
	}

	bufs := make([][]byte, TotalShardsCount)
	for i := 0; i < DataShardsCount; {
		n := int(rand.Int31n(TotalShardsCount))
		if n == ecFileIndex || bufs[n] != nil {
			continue
		}
		bufs[n] = make([]byte, size)
		i++
	}

	for i, buf := range bufs {
		if buf == nil {
			continue
		}
		err = readFromFile(ecFiles[i], buf, ecFileOffset)
		if err != nil {
			return
		}
	}

	if err = enc.ReconstructData(bufs); err != nil {
		return nil, err
	}

	return bufs[ecFileIndex], nil
}

func readFromFile(file *os.File, data []byte, ecFileOffset int64) (err error) {
	_, err = file.ReadAt(data, ecFileOffset)
	return
}

func removeGeneratedFiles(baseFileName string) {
	for i := 0; i < DataShardsCount+ParityShardsCount; i++ {
		fname := fmt.Sprintf("%s.ec%02d", baseFileName, i+1)
		os.Remove(fname)
	}
	os.Remove(baseFileName + ".ecx")
}

func TestLocateData(t *testing.T) {
	intervals := LocateData(largeBlockSize, smallBlockSize, DataShardsCount*largeBlockSize+1, DataShardsCount*largeBlockSize, 1)
	if len(intervals) != 1 {
		t.Errorf("unexpected interval size %d", len(intervals))
	}
	if !intervals[0].sameAs(Interval{0, 0, 1, false, 1}) {
		t.Errorf("unexpected interval %+v", intervals[0])
	}

	intervals = LocateData(largeBlockSize, smallBlockSize, DataShardsCount*largeBlockSize+1, DataShardsCount*largeBlockSize/2+100, DataShardsCount*largeBlockSize+1-DataShardsCount*largeBlockSize/2-100)
	fmt.Printf("%+v\n", intervals)
}

func (this Interval) sameAs(that Interval) bool {
	return this.IsLargeBlock == that.IsLargeBlock &&
		this.InnerBlockOffset == that.InnerBlockOffset &&
		this.BlockIndex == that.BlockIndex &&
		this.Size == that.Size
}
<commit_msg>clean up test files<commit_after>package erasure_coding

import (
	"bytes"
	"fmt"
	"math/rand"
	"os"
	"testing"

	"github.com/chrislusf/seaweedfs/weed/storage/needle_map"
	"github.com/chrislusf/seaweedfs/weed/storage/types"
	"github.com/klauspost/reedsolomon"
)

const (
	largeBlockSize = 10000
	smallBlockSize = 100
)

func TestEncodingDecoding(t *testing.T) {
	bufferSize := 50
	baseFileName := "1"

	err := generateEcFiles(baseFileName, bufferSize, largeBlockSize, smallBlockSize)
	if err != nil {
		t.Logf("generateEcFiles: %v", err)
	}

	err = WriteSortedEcxFile(baseFileName)
	if err != nil {
		t.Logf("WriteSortedEcxFile: %v", err)
	}

	err = validateFiles(baseFileName)
	if err != nil {
		t.Logf("WriteSortedEcxFile: %v", err)
	}

	removeGeneratedFiles(baseFileName)

}

func validateFiles(baseFileName string) error {
	cm, err := readCompactMap(baseFileName)
	if err != nil {
		return fmt.Errorf("readCompactMap: %v", err)
	}

	datFile, err := os.OpenFile(baseFileName+".dat", os.O_RDONLY, 0)
	if err != nil {
		return fmt.Errorf("failed to open dat file: %v", err)
	}
	defer datFile.Close()

	fi, err := datFile.Stat()
	if err != nil {
		return fmt.Errorf("failed to stat dat file: %v", err)
	}

	ecFiles, err := openEcFiles(baseFileName, true)
	defer closeEcFiles(ecFiles)

	err = cm.AscendingVisit(func(value needle_map.NeedleValue) error {
		return assertSame(datFile, fi.Size(), ecFiles, value.Offset, value.Size)
	})
	if err != nil {
		return fmt.Errorf("failed to check ec files: %v", err)
	}
	return nil
}

func assertSame(datFile *os.File, datSize int64, ecFiles []*os.File, offset types.Offset, size uint32) error {

	data, err := readDatFile(datFile, offset, size)
	if err != nil {
		return fmt.Errorf("failed to read dat file: %v", err)
	}

	ecData, err := readEcFile(datSize, ecFiles, offset, size)
	if err != nil {
		return fmt.Errorf("failed to read ec file: %v", err)
	}

	if bytes.Compare(data, ecData) != 0 {
		return fmt.Errorf("unexpected data read")
	}

	return nil
}

func readDatFile(datFile *os.File, offset types.Offset, size uint32) ([]byte, error) {

	data := make([]byte, size)
	n, err := datFile.ReadAt(data, offset.ToAcutalOffset())
	if err != nil {
		return nil, fmt.Errorf("failed to ReadAt dat file: %v", err)
	}
	if n != int(size) {
		return nil, fmt.Errorf("unexpected read size %d, expected %d", n, size)
	}
	return data, nil
}

func readEcFile(datSize int64, ecFiles []*os.File, offset types.Offset, size uint32) (data []byte, err error) {

	intervals := LocateData(largeBlockSize, smallBlockSize, datSize, offset.ToAcutalOffset(), size)

	for i, interval := range intervals {
		if d, e := readOneInterval(interval, ecFiles); e != nil {
			return nil, e
		} else {
			if i == 0 {
				data = d
			} else {
				data = append(data, d...)
			}
		}
	}

	return data, nil
}

func readOneInterval(interval Interval, ecFiles []*os.File) (data []byte, err error) {

	ecFileIndex, ecFileOffset := interval.ToShardIdAndOffset(largeBlockSize, smallBlockSize)

	data = make([]byte, interval.Size)
	err = readFromFile(ecFiles[ecFileIndex], data, ecFileOffset)
	{ // do some ec testing
		ecData, err := readFromOtherEcFiles(ecFiles, int(ecFileIndex), ecFileOffset, interval.Size)
		if err != nil {
			return nil, fmt.Errorf("ec reconstruct error: %v", err)
		}
		if bytes.Compare(data, ecData) != 0 {
			return nil, fmt.Errorf("ec compare error")
		}
	}
	return
}

func readFromOtherEcFiles(ecFiles []*os.File, ecFileIndex int, ecFileOffset int64, size uint32) (data []byte, err error) {
	enc, err := reedsolomon.New(DataShardsCount, ParityShardsCount)
	if err != nil {
		return nil, fmt.Errorf("failed to create encoder: %v", err)
	}

	bufs := make([][]byte, TotalShardsCount)
	for i := 0; i < DataShardsCount; {
		n := int(rand.Int31n(TotalShardsCount))
		if n == ecFileIndex || bufs[n] != nil {
			continue
		}
		bufs[n] = make([]byte, size)
		i++
	}

	for i, buf := range bufs {
		if buf == nil {
			continue
		}
		err = readFromFile(ecFiles[i], buf, ecFileOffset)
		if err != nil {
			return
		}
	}

	if err = enc.ReconstructData(bufs); err != nil {
		return nil, err
	}

	return bufs[ecFileIndex], nil
}

func readFromFile(file *os.File, data []byte, ecFileOffset int64) (err error) {
	_, err = file.ReadAt(data, ecFileOffset)
	return
}

func removeGeneratedFiles(baseFileName string) {
	for i := 0; i < DataShardsCount+ParityShardsCount; i++ {
		fname := fmt.Sprintf("%s.ec%02d", baseFileName, i)
		os.Remove(fname)
	}
	os.Remove(baseFileName + ".ecx")
}

func TestLocateData(t *testing.T) {
	intervals := LocateData(largeBlockSize, smallBlockSize, DataShardsCount*largeBlockSize+1, DataShardsCount*largeBlockSize, 1)
	if len(intervals) != 1 {
		t.Errorf("unexpected interval size %d", len(intervals))
	}
	if !intervals[0].sameAs(Interval{0, 0, 1, false, 1}) {
		t.Errorf("unexpected interval %+v", intervals[0])
	}

	intervals = LocateData(largeBlockSize, smallBlockSize, DataShardsCount*largeBlockSize+1, DataShardsCount*largeBlockSize/2+100, DataShardsCount*largeBlockSize+1-DataShardsCount*largeBlockSize/2-100)
	fmt.Printf("%+v\n", intervals)
}

func (this Interval) sameAs(that Interval) bool {
	return this.IsLargeBlock == that.IsLargeBlock &&
		this.InnerBlockOffset == that.InnerBlockOffset &&
		this.BlockIndex == that.BlockIndex &&
		this.Size == that.Size
}
<|endoftext|>


<commit_before>package storage

import (
	"io/ioutil"
	"math/rand"
	"testing"

	"github.com/chrislusf/seaweedfs/weed/glog"
	. "github.com/chrislusf/seaweedfs/weed/storage/types"
)

func TestFastLoadingNeedleMapMetrics(t *testing.T) {

	idxFile, _ := ioutil.TempFile("", "tmp.idx")
	nm := NewCompactNeedleMap(idxFile)

	for i := 0; i < 10000; i++ {
		nm.Put(Uint64ToNeedleId(uint64(i+1)), Uint32ToOffset(uint32(0)), Size(1))
		if rand.Float32() < 0.2 {
			nm.Delete(Uint64ToNeedleId(uint64(rand.Int63n(int64(i))+1)), Uint32ToOffset(uint32(0)))
		}
	}

	mm, _ := newNeedleMapMetricFromIndexFile(idxFile)

	glog.V(0).Infof("FileCount expected %d actual %d", nm.FileCount(), mm.FileCount())
	glog.V(0).Infof("DeletedSize expected %d actual %d", nm.DeletedSize(), mm.DeletedSize())
	glog.V(0).Infof("ContentSize expected %d actual %d", nm.ContentSize(), mm.ContentSize())
	glog.V(0).Infof("DeletedCount expected %d actual %d", nm.DeletedCount(), mm.DeletedCount())
	glog.V(0).Infof("MaxFileKey expected %d actual %d", nm.MaxFileKey(), mm.MaxFileKey())
}
<commit_msg>fix test<commit_after>package storage

import (
	"io/ioutil"
	"math/rand"
	"testing"

	"github.com/chrislusf/seaweedfs/weed/glog"
	. "github.com/chrislusf/seaweedfs/weed/storage/types"
)

func TestFastLoadingNeedleMapMetrics(t *testing.T) {

	idxFile, _ := ioutil.TempFile("", "tmp.idx")
	nm := NewCompactNeedleMap(idxFile)

	for i := 0; i < 10000; i++ {
		nm.Put(Uint64ToNeedleId(uint64(i+1)), Uint32ToOffset(uint32(0)), Size(1))
		if rand.Float32() < 0.2 {
			nm.Delete(Uint64ToNeedleId(uint64(rand.Int63n(int64(i))+1)))
		}
	}

	mm, _ := newNeedleMapMetricFromIndexFile(idxFile)

	glog.V(0).Infof("FileCount expected %d actual %d", nm.FileCount(), mm.FileCount())
	glog.V(0).Infof("DeletedSize expected %d actual %d", nm.DeletedSize(), mm.DeletedSize())
	glog.V(0).Infof("ContentSize expected %d actual %d", nm.ContentSize(), mm.ContentSize())
	glog.V(0).Infof("DeletedCount expected %d actual %d", nm.DeletedCount(), mm.DeletedCount())
	glog.V(0).Infof("MaxFileKey expected %d actual %d", nm.MaxFileKey(), mm.MaxFileKey())
}
<|endoftext|>


<commit_before>package gogame

import (
	"fmt"
	"math"

	"github.com/gopherjs/gopherjs/js"
)

// Surface represents an image or drawable sureface.
type Surface interface {
	GetCanvas() *js.Object
	Blit(source Surface, x, y float64)
	BlitArea(source Surface, area *Rect, x, y float64)
	Fill(*FillStyle)
	Width() int
	Height() int
	//Copy() Surface
	//Scroll(dx, dy int)
	//GetAt(x, y int) Color
	//SetAt(x, y int, Color)
	//SetClip(Rect)
	//GetClip() Rect
	//GetSubsurface(Rect) Surface
	//GetParent() Surface
	// Scale() (x, y float64)
	// SetScale(x, y float64)
	// Rotation() float64
	// SetRotation(radians float64)
	// Transformed() Surface
	Scaled(x, y float64) Surface
	Rotated(radians float64) Surface
	GetRect() Rect
	DrawRect(*Rect, Styler)
	DrawCircle(posX, posY, radius float64, s Styler)
	DrawEllipse(*Rect, Styler)
	DrawArc(r *Rect, startRadians, stopRadians float64, s Styler)
	DrawLine(startX, startY, endX, endY float64, s Styler)
	DrawLines(pointList [][2]float64, s Styler)
	//DrawQuadraticCurve(startX, startY, endX, endY, cpX, cpY, float64, s Styler)
	//DrawQuadraticCurves(points [][2]float64, cPoints [][2]float64, s Styler)
	//DrawBezierCurve(startX, startY, endX, endY, cpStartX, cpStartY, cpEndX, cpEndY float64, s Styler)
	//DrawBezierCurves(points [][2]float64, cPoints [][2]float64, s Styler)
	DrawText(text string, x, y float64, s *FontStyler)
}

var _ Surface = &surface{}

type surface struct {
	canvas   *js.Object
	ctx      *js.Object
	rotation float64
	scaleX   float64
	scaleY   float64
	// if needed we can cache the Rect for GetRect, which would only need to change when
	// scaleX/Y or rotation change.
	// rect *Rect
}

// NewSurface creates a new Surface.
func NewSurface(width, height int) Surface {
	canvas := jq("<canvas>").Get(0)
	canvas.Set("width", width)
	canvas.Set("height", height)
	return &surface{
		canvas:   canvas,
		ctx:      canvas.Call("getContext", "2d"),
		rotation: 0,
		scaleX:   1,
		scaleY:   1,
	}
}

// GetCanvas returns the surface as an HTML canvas.
func (s *surface) GetCanvas() *js.Object {
	return s.canvas
}

// Blit draws the given surface to this one at the given position. Source's top-left corner
// (according to GetRect) fill be drawn at (x, y).
func (s *surface) Blit(source Surface, x, y float64) {
	// 	s.ctx.Call("save")

	// 	sx, sy := source.Scale()
	// 	s.ctx.Call("scale", sx, sy)

	// 	rect := source.GetRect()
	// 	rect.X = x
	// 	rect.Y = y
	// 	cx, cy := math.Floor(rect.CenterX()), math.Floor(rect.CenterY())
	// 	s.ctx.Call("translate", cx, cy)
	// 	s.ctx.Call("rotate", -source.Rotation())
	// 	s.ctx.Call("translate", -cx, -cy)

	s.ctx.Call("drawImage", source.GetCanvas(), math.Floor(x), math.Floor(y))

	// s.ctx.Call("restore")
}

// BlitArea draws the given portion of the source surface defined by the Rect to this
// one with its top-left corner (according to GetRect) at the given position.
func (s *surface) BlitArea(source Surface, area *Rect, x, y float64) {
	// s.ctx.Call("save")

	// sx, sy := source.Scale()
	// s.ctx.Call("scale", sx, sy)

	// rect := source.GetRect()
	// rect.X = x
	// rect.Y = y
	// cx, cy := math.Floor(rect.CenterX()), math.Floor(rect.CenterY())
	// s.ctx.Call("translate", cx, cy)
	// s.ctx.Call("rotate", -source.Rotation())
	// s.ctx.Call("translate", -cx, -cy)

	s.ctx.Call("drawImage", source.GetCanvas(), math.Floor(area.X), math.Floor(area.Y),
		math.Floor(area.W), math.Floor(area.H), math.Floor(x), math.Floor(y), math.Floor(area.W),
		math.Floor(area.H))

	// s.ctx.Call("restore")
}

// Fill fills the whole surface with the given style.
func (s *surface) Fill(style *FillStyle) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("fillRect", 0, 0, s.canvas.Get("width"), s.canvas.Get("height"))
	s.ctx.Call("restore")
}

// Width returns the unrotated, unscaled width of the surface in pixels. To get the width
// after scaling and rotating use GetRect.
func (s *surface) Width() int {
	return s.canvas.Get("width").Int()
}

// Height returns the unrotated, unscaled height of the surface in pixels. To get the height
// after scaling and rotating use GetRect.
func (s *surface) Height() int {
	return s.canvas.Get("height").Int()
}

// // Scale returns the x and y scales of the this surface.
// func (s *surface) Scale() (x, y float64) {
// 	return s.scaleX, s.scaleY
// }

// // SetScale sets the x and y scales of this surface.
// func (s *surface) SetScale(x, y float64) {
// 	s.scaleX = x
// 	s.scaleY = y
// }

// Scaled returns a new Surface that is equivalent to this one scaled by the given amount.
func (s *surface) Scaled(x, y float64) Surface {
	newS := NewSurface(int(float64(s.Width())*x), int(float64(s.Height())*y))
	ctx := newS.(*surface).ctx
	ctx.Call("save")
	ctx.Call("scale", x, y)
	ctx.Call("drawImage", s.canvas, 0, 0)
	ctx.Call("restore")
	return newS
}

// // Rotation returns the rotation (counter-clockwise) of this surface in radians.
// func (s *surface) Rotation() float64 {
// 	return s.rotation
// }

// // SetRotation sets the rotation (counter-clockwise) of this surface in radians.
// func (s *surface) SetRotation(radians float64) {
// 	s.rotation = radians
// }

// Rotated returns a new Surface that is equivalent to this one but rotated counter-clockwise
// by the given amount.
func (s *surface) Rotated(radians float64) Surface {
	newW, newH := s.getRotatedSize(radians)
	newS := NewSurface(newW, newH)
	ctx := newS.(*surface).ctx
	ctx.Call("save")
	cx, cy := newW/2, newH/2
	s.ctx.Call("translate", cx, cy)
	s.ctx.Call("rotate", -radians)
	s.ctx.Call("translate", -cx, -cy)
	ctx.Call("drawImage", s.canvas, 0, 0)
	ctx.Call("restore")
	return newS
}

// // Transformed returns a new Surface that is equivalent to this one but with the scale and
// // rotation trnsforms applied.
// func (s *surface) Transformed() Surface {
// 	rect := s.GetRect()
// 	newS := NewSurface(int(rect.W), int(rect.H))
// 	newS.Blit(s, 0, 0)
// 	return newS
// }

func (s *surface) getRotatedSize(radians float64) (w, h int) {
	width, height := float64(s.Width()), float64(s.Height())
	cx, cy := width/2, height/2
	cos, sin := math.Cos(s.rotation), math.Sin(s.rotation)
	corners := [][2]float64{
		{0.0, 0.0},
		{width, 0.0},
		{0.0, height},
		{width, height},
	}
	for _, corner := range corners {
		nx := cx + (corner[0]-cx)*cos + (corner[1]-cy)*sin
		ny := cy - (corner[0]-cx)*sin + (corner[1]-cy)*cos
		corner[0] = nx
		corner[1] = ny
	}
	minX, maxX, minY, maxY := corners[0][0], corners[0][0], corners[0][1], corners[0][1]
	for _, corner := range corners[1:] {
		if corner[0] < minX {
			minX = corner[0]
		}
		if corner[0] > maxX {
			maxX = corner[0]
		}
		if corner[1] < minY {
			minY = corner[1]
		}
		if corner[1] > maxY {
			maxY = corner[1]
		}
	}
	return int(maxX - minX), int(maxY - minY)
}

// GetRect returns the bouding rectangle for the surface, taking into acount rotation and scale.
func (s *surface) GetRect() Rect {
	// w := float64(s.Width()) * s.scaleX
	// h := float64(s.Height()) * s.scaleY
	// cx, cy := w/2, h/2
	// cos, sin := math.Cos(s.rotation), math.Sin(s.rotation)
	// corners := [][2]float64{
	// 	{0.0, 0.0},
	// 	{w, 0.0},
	// 	{0.0, h},
	// 	{w, h},
	// }
	// for _, corner := range corners {
	// 	nx := cx + (corner[0]-cx)*cos + (corner[1]-cy)*sin
	// 	ny := cy - (corner[0]-cx)*sin + (corner[1]-cy)*cos
	// 	corner[0] = nx
	// 	corner[1] = ny
	// }
	// minX, maxX, minY, maxY := corners[0][0], corners[0][0], corners[0][1], corners[0][1]
	// for _, corner := range corners[1:] {
	// 	if corner[0] < minX {
	// 		minX = corner[0]
	// 	}
	// 	if corner[0] > maxX {
	// 		maxX = corner[0]
	// 	}
	// 	if corner[1] < minY {
	// 		minY = corner[1]
	// 	}
	// 	if corner[1] > maxY {
	// 		maxY = corner[1]
	// 	}
	// }
	// return Rect{X: 0, Y: 0, W: maxX - minX, H: maxY - minY}
	return Rect{X: 0, Y: 0, W: float64(s.Width()), H: float64(s.Height())}
}

// DrawRect draws a rectangle on the surface.
func (s *surface) DrawRect(r *Rect, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call(fmt.Sprintf("%sRect", style.DrawType()), 0, 0, math.Floor(r.W), math.Floor(r.H))
	s.ctx.Call("restore")
}

// DrawCircle draws a circle on the surface.
func (s *surface) DrawCircle(posX, posY, radius float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(posX), math.Floor(posY))
	s.ctx.Call("beginPath")
	s.ctx.Call("arc", 0, 0, radius, 0, 2*math.Pi)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawEllipse draws an ellipse on the canvas within the given Rect.
func (s *surface) DrawEllipse(r *Rect, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call("beginPath")
	s.ctx.Call("ellipse", math.Floor(r.Width()/2), math.Floor(r.Height()/2), math.Floor(r.Width()/2),
		math.Floor(r.Height()/2), 0, 0, 2*math.Pi)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawArc draws an arc on the canvas within the given Rect between the given angles.
// Angles are counter-clockwise.
func (s *surface) DrawArc(r *Rect, startRadians, stopRadians float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call("beginPath")
	s.ctx.Call("ellipse", math.Floor(r.Width()/2), math.Floor(r.Height()/2), math.Floor(r.Width()/2),
		math.Floor(r.Height()/2), 0, 2*math.Pi-startRadians, 2*math.Pi-stopRadians, true)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawLine draws a line on the canvas.
func (s *surface) DrawLine(startX, startY, endX, endY float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", startX, startY)
	s.ctx.Call("beginPath")
	s.ctx.Call("moveTo", 0, 0)
	s.ctx.Call("lineTo", endX-startX, endY-startY)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawLines draws multiple connectd lines to the surface.
func (s *surface) DrawLines(pointList [][2]float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("beginPath")
	s.ctx.Call("moveTo", pointList[0][0], pointList[0][1])
	for _, p := range pointList[1:] {
		s.ctx.Call("lineTo", p[0], p[1])
	}
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawText draws the given text to the surface.
func (s *surface) DrawText(text string, x, y float64, style *FontStyler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(x), math.Floor(y))
	s.ctx.Call(fmt.Sprintf("%sText", style.DrawType()), text, 0, 0)
	s.ctx.Call("restore")
}
<commit_msg>Remove old transform code that was commented out.<commit_after>package gogame

import (
	"fmt"
	"math"

	"github.com/gopherjs/gopherjs/js"
)

// Surface represents an image or drawable sureface.
type Surface interface {
	GetCanvas() *js.Object
	Blit(source Surface, x, y float64)
	BlitArea(source Surface, area *Rect, x, y float64)
	Fill(*FillStyle)
	Width() int
	Height() int
	//Copy() Surface
	//Scroll(dx, dy int)
	//GetAt(x, y int) Color
	//SetAt(x, y int, Color)
	//SetClip(Rect)
	//GetClip() Rect
	//GetSubsurface(Rect) Surface
	//GetParent() Surface
	Scaled(x, y float64) Surface
	Rotated(radians float64) Surface
	GetRect() Rect
	DrawRect(*Rect, Styler)
	DrawCircle(posX, posY, radius float64, s Styler)
	DrawEllipse(*Rect, Styler)
	DrawArc(r *Rect, startRadians, stopRadians float64, s Styler)
	DrawLine(startX, startY, endX, endY float64, s Styler)
	DrawLines(pointList [][2]float64, s Styler)
	//DrawQuadraticCurve(startX, startY, endX, endY, cpX, cpY, float64, s Styler)
	//DrawQuadraticCurves(points [][2]float64, cPoints [][2]float64, s Styler)
	//DrawBezierCurve(startX, startY, endX, endY, cpStartX, cpStartY, cpEndX, cpEndY float64, s Styler)
	//DrawBezierCurves(points [][2]float64, cPoints [][2]float64, s Styler)
	DrawText(text string, x, y float64, s *FontStyler)
}

var _ Surface = &surface{}

type surface struct {
	canvas *js.Object
	ctx    *js.Object
}

// NewSurface creates a new Surface.
func NewSurface(width, height int) Surface {
	canvas := jq("<canvas>").Get(0)
	canvas.Set("width", width)
	canvas.Set("height", height)
	return &surface{
		canvas: canvas,
		ctx:    canvas.Call("getContext", "2d"),
	}
}

// GetCanvas returns the surface as an HTML canvas.
func (s *surface) GetCanvas() *js.Object {
	return s.canvas
}

// Blit draws the given surface to this one at the given position. Source's top-left corner
// (according to GetRect) fill be drawn at (x, y).
func (s *surface) Blit(source Surface, x, y float64) {
	s.ctx.Call("drawImage", source.GetCanvas(), math.Floor(x), math.Floor(y))
}

// BlitArea draws the given portion of the source surface defined by the Rect to this
// one with its top-left corner (according to GetRect) at the given position.
func (s *surface) BlitArea(source Surface, area *Rect, x, y float64) {
	s.ctx.Call("drawImage", source.GetCanvas(), math.Floor(area.X), math.Floor(area.Y),
		math.Floor(area.W), math.Floor(area.H), math.Floor(x), math.Floor(y), math.Floor(area.W),
		math.Floor(area.H))
}

// Fill fills the whole surface with the given style.
func (s *surface) Fill(style *FillStyle) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("fillRect", 0, 0, s.canvas.Get("width"), s.canvas.Get("height"))
	s.ctx.Call("restore")
}

// Width returns the unrotated, unscaled width of the surface in pixels. To get the width
// after scaling and rotating use GetRect.
func (s *surface) Width() int {
	return s.canvas.Get("width").Int()
}

// Height returns the unrotated, unscaled height of the surface in pixels. To get the height
// after scaling and rotating use GetRect.
func (s *surface) Height() int {
	return s.canvas.Get("height").Int()
}

// Scaled returns a new Surface that is equivalent to this one scaled by the given amount.
func (s *surface) Scaled(x, y float64) Surface {
	newS := NewSurface(int(float64(s.Width())*x), int(float64(s.Height())*y))
	ctx := newS.(*surface).ctx
	ctx.Call("save")
	ctx.Call("scale", x, y)
	ctx.Call("drawImage", s.canvas, 0, 0)
	ctx.Call("restore")
	return newS
}

// Rotated returns a new Surface that is equivalent to this one but rotated counter-clockwise
// by the given amount.
func (s *surface) Rotated(radians float64) Surface {
	newW, newH := s.getRotatedSize(radians)
	newS := NewSurface(newW, newH)
	ctx := newS.(*surface).ctx
	ctx.Call("save")
	cx, cy := newW/2, newH/2
	s.ctx.Call("translate", cx, cy)
	s.ctx.Call("rotate", -radians)
	s.ctx.Call("translate", -cx, -cy)
	ctx.Call("drawImage", s.canvas, 0, 0)
	ctx.Call("restore")
	return newS
}

func (s *surface) getRotatedSize(radians float64) (w, h int) {
	width, height := float64(s.Width()), float64(s.Height())
	cx, cy := width/2, height/2
	cos, sin := math.Cos(radians), math.Sin(radians)
	corners := [][2]float64{
		{0.0, 0.0},
		{width, 0.0},
		{0.0, height},
		{width, height},
	}
	for _, corner := range corners {
		nx := cx + (corner[0]-cx)*cos + (corner[1]-cy)*sin
		ny := cy - (corner[0]-cx)*sin + (corner[1]-cy)*cos
		corner[0] = nx
		corner[1] = ny
	}
	minX, maxX, minY, maxY := corners[0][0], corners[0][0], corners[0][1], corners[0][1]
	for _, corner := range corners[1:] {
		if corner[0] < minX {
			minX = corner[0]
		}
		if corner[0] > maxX {
			maxX = corner[0]
		}
		if corner[1] < minY {
			minY = corner[1]
		}
		if corner[1] > maxY {
			maxY = corner[1]
		}
	}
	return int(maxX - minX), int(maxY - minY)
}

// GetRect returns the bouding rectangle for the surface, taking into acount rotation and scale.
func (s *surface) GetRect() Rect {
	return Rect{X: 0, Y: 0, W: float64(s.Width()), H: float64(s.Height())}
}

// DrawRect draws a rectangle on the surface.
func (s *surface) DrawRect(r *Rect, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call(fmt.Sprintf("%sRect", style.DrawType()), 0, 0, math.Floor(r.W), math.Floor(r.H))
	s.ctx.Call("restore")
}

// DrawCircle draws a circle on the surface.
func (s *surface) DrawCircle(posX, posY, radius float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(posX), math.Floor(posY))
	s.ctx.Call("beginPath")
	s.ctx.Call("arc", 0, 0, radius, 0, 2*math.Pi)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawEllipse draws an ellipse on the canvas within the given Rect.
func (s *surface) DrawEllipse(r *Rect, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call("beginPath")
	s.ctx.Call("ellipse", math.Floor(r.Width()/2), math.Floor(r.Height()/2), math.Floor(r.Width()/2),
		math.Floor(r.Height()/2), 0, 0, 2*math.Pi)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawArc draws an arc on the canvas within the given Rect between the given angles.
// Angles are counter-clockwise.
func (s *surface) DrawArc(r *Rect, startRadians, stopRadians float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(r.X), math.Floor(r.Y))
	s.ctx.Call("beginPath")
	s.ctx.Call("ellipse", math.Floor(r.Width()/2), math.Floor(r.Height()/2), math.Floor(r.Width()/2),
		math.Floor(r.Height()/2), 0, 2*math.Pi-startRadians, 2*math.Pi-stopRadians, true)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawLine draws a line on the canvas.
func (s *surface) DrawLine(startX, startY, endX, endY float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", startX, startY)
	s.ctx.Call("beginPath")
	s.ctx.Call("moveTo", 0, 0)
	s.ctx.Call("lineTo", endX-startX, endY-startY)
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawLines draws multiple connectd lines to the surface.
func (s *surface) DrawLines(pointList [][2]float64, style Styler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("beginPath")
	s.ctx.Call("moveTo", pointList[0][0], pointList[0][1])
	for _, p := range pointList[1:] {
		s.ctx.Call("lineTo", p[0], p[1])
	}
	s.ctx.Call(string(style.DrawType()))
	s.ctx.Call("restore")
}

// DrawText draws the given text to the surface.
func (s *surface) DrawText(text string, x, y float64, style *FontStyler) {
	s.ctx.Call("save")
	style.Style(s.ctx)
	s.ctx.Call("translate", math.Floor(x), math.Floor(y))
	s.ctx.Call(fmt.Sprintf("%sText", style.DrawType()), text, 0, 0)
	s.ctx.Call("restore")
}
<|endoftext|>