<commit_before>package glacier

import (
	"crypto/sha256"
	"encoding/hex"
	"hash"
)

// MultiTreeHasher is used to calculate tree hashes for multi-part uploads
// Call Add sequentially on hashes you have calculated them for
// parts individually, and CreateHash to get the resulting root-level
// hash to use in a CompleteMultipart request.
type MultiTreeHasher struct {
	nodes [][sha256.Size]byte
}

// Add appends the hex-encoded hash to the treehash as a new node
// Add must be called sequentially on parts.
func (t *MultiTreeHasher) Add(hash string) {
	var b [sha256.Size]byte
	hex.Decode(b[:], []byte(hash))
	t.nodes = append(t.nodes, b)
}

// CreateHash returns the root-level hex-encoded hash to send in the
// CompleteMultipart request.
func (t *MultiTreeHasher) CreateHash() string {
	if len(t.nodes) == 0 {
		return ""
	}
	rootHash := treeHash(t.nodes)
	return hex.EncodeToString(rootHash[:])
}

// treeHash calculates the root-level treeHash given sequential
// leaf nodes.
func treeHash(nodes [][sha256.Size]byte) [sha256.Size]byte {
	var combine [sha256.Size * 2]byte
	for len(nodes) > 1 {
		for i := 0; i < len(nodes)/2; i++ {
			copy(combine[:sha256.Size], nodes[i*2][:])
			copy(combine[sha256.Size:], nodes[i*2+1][:])
			nodes[i] = sha256.Sum256(combine[:])
		}
		if len(nodes)%2 == 0 {
			nodes = nodes[:len(nodes)/2]
		} else {
			nodes[len(nodes)/2] = nodes[len(nodes)-1]
			nodes = nodes[:len(nodes)/2+1]
		}
	}
	return nodes[0]
}

// TreeHash is used to calculate the tree hash and regular sha256 hash of the
// data written to it. These values are needed when uploading an archive or
// verifying an aligned download. First each 1 MiB chunk of data is hashed.
// Second each consecutive child node's hashes are concatenated then hashed (if
// there is a single node left it is promoted to the next level). The second
// step is repeated until there is only a single node, this is the tree hash.
// See docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html
type TreeHash struct {
	remaining   []byte
	nodes       [][sha256.Size]byte
	runningHash hash.Hash         // linear
	treeHash    [sha256.Size]byte // computed
	linearHash  [sha256.Size]byte // computed
}

// NewTreeHash returns an new, initialized tree hasher.
func NewTreeHash() *TreeHash {
	result := &TreeHash{
		runningHash: sha256.New(),
		remaining:   make([]byte, 0, 1<<20),
	}
	result.Reset()
	return result
}

// Reset the tree hash's state allowing it to be reused.
func (th *TreeHash) Reset() {
	th.runningHash.Reset()
	th.remaining = th.remaining[:0]
	th.nodes = th.nodes[:0]
	th.treeHash = [sha256.Size]byte{}
	th.linearHash = [sha256.Size]byte{}
}

// Write writes all of p, storing every 1 MiB of data's hash.
func (th *TreeHash) Write2(p []byte) (n int, err error) {
	n = len(p)
	th.remaining = append(th.remaining, p...)

	// Append one-megabyte increments to the hashes.
	for len(th.remaining) >= (1 << 20) {
		th.nodes = append(th.nodes, sha256.Sum256(th.remaining[:1<<20]))
		th.runningHash.Write(th.remaining[:1<<20])
		th.remaining = th.remaining[1<<20:]
	}
	return
}

// Write writes all of p, storing every 1 MiB of data's hash.
func (th *TreeHash) Write(p []byte) (int, error) {
	n := len(p)

	// Not enough data to fill a 1 MB chunk.
	if len(th.remaining)+len(p) < 1<<20 {
		th.remaining = append(th.remaining, p...)
		return n, nil
	}

	// Move enough to fill th.remaining to 1 MB.
	fill := 1<<20 - len(th.remaining)
	th.remaining = append(th.remaining, p[:fill]...)
	p = p[fill:]

	// Append the 1 MB in th.remaining.
	th.nodes = append(th.nodes, sha256.Sum256(th.remaining))
	th.runningHash.Write(th.remaining)
	th.remaining = th.remaining[:0]

	// Append all 1M chunks remaining in p.
	for len(p) >= 1<<20 {
		th.nodes = append(th.nodes, sha256.Sum256(p[:1<<20]))
		th.runningHash.Write(p[:1<<20])
		p = p[1<<20:]
	}

	// Copy what remains in p to th.remaining.
	th.remaining = append(th.remaining, p...)

	return n, nil
}

// Close closes the the remaing chunks of data and then calculates the tree hash.
func (th *TreeHash) Close() error {
	// create last node; it is impossible that it has a size > 1 MB
	if len(th.remaining) > 0 {
		th.nodes = append(th.nodes, sha256.Sum256(th.remaining))
		th.runningHash.Write(th.remaining)
	}
	// Calculate the tree and linear hashes
	if len(th.nodes) > 0 {
		th.treeHash = treeHash(th.nodes)
	}
	th.runningHash.Sum(th.linearHash[:0])
	return nil
}

// TreeHash returns the root-level tree hash of everything written.
func (th *TreeHash) TreeHash() []byte {
	return th.treeHash[:]
}

// Hash returns the linear sha256 checksum of everything written.
func (th *TreeHash) Hash() []byte {
	return th.linearHash[:]
}
<commit_msg>Remove old Write method that should have been removed in 996ef14.<commit_after>package glacier

import (
	"crypto/sha256"
	"encoding/hex"
	"hash"
)

// MultiTreeHasher is used to calculate tree hashes for multi-part uploads
// Call Add sequentially on hashes you have calculated them for
// parts individually, and CreateHash to get the resulting root-level
// hash to use in a CompleteMultipart request.
type MultiTreeHasher struct {
	nodes [][sha256.Size]byte
}

// Add appends the hex-encoded hash to the treehash as a new node
// Add must be called sequentially on parts.
func (t *MultiTreeHasher) Add(hash string) {
	var b [sha256.Size]byte
	hex.Decode(b[:], []byte(hash))
	t.nodes = append(t.nodes, b)
}

// CreateHash returns the root-level hex-encoded hash to send in the
// CompleteMultipart request.
func (t *MultiTreeHasher) CreateHash() string {
	if len(t.nodes) == 0 {
		return ""
	}
	rootHash := treeHash(t.nodes)
	return hex.EncodeToString(rootHash[:])
}

// treeHash calculates the root-level treeHash given sequential
// leaf nodes.
func treeHash(nodes [][sha256.Size]byte) [sha256.Size]byte {
	var combine [sha256.Size * 2]byte
	for len(nodes) > 1 {
		for i := 0; i < len(nodes)/2; i++ {
			copy(combine[:sha256.Size], nodes[i*2][:])
			copy(combine[sha256.Size:], nodes[i*2+1][:])
			nodes[i] = sha256.Sum256(combine[:])
		}
		if len(nodes)%2 == 0 {
			nodes = nodes[:len(nodes)/2]
		} else {
			nodes[len(nodes)/2] = nodes[len(nodes)-1]
			nodes = nodes[:len(nodes)/2+1]
		}
	}
	return nodes[0]
}

// TreeHash is used to calculate the tree hash and regular sha256 hash of the
// data written to it. These values are needed when uploading an archive or
// verifying an aligned download. First each 1 MiB chunk of data is hashed.
// Second each consecutive child node's hashes are concatenated then hashed (if
// there is a single node left it is promoted to the next level). The second
// step is repeated until there is only a single node, this is the tree hash.
// See docs.aws.amazon.com/amazonglacier/latest/dev/checksum-calculations.html
type TreeHash struct {
	remaining   []byte
	nodes       [][sha256.Size]byte
	runningHash hash.Hash         // linear
	treeHash    [sha256.Size]byte // computed
	linearHash  [sha256.Size]byte // computed
}

// NewTreeHash returns an new, initialized tree hasher.
func NewTreeHash() *TreeHash {
	result := &TreeHash{
		runningHash: sha256.New(),
		remaining:   make([]byte, 0, 1<<20),
	}
	result.Reset()
	return result
}

// Reset the tree hash's state allowing it to be reused.
func (th *TreeHash) Reset() {
	th.runningHash.Reset()
	th.remaining = th.remaining[:0]
	th.nodes = th.nodes[:0]
	th.treeHash = [sha256.Size]byte{}
	th.linearHash = [sha256.Size]byte{}
}

// Write writes all of p, storing every 1 MiB of data's hash.
func (th *TreeHash) Write(p []byte) (int, error) {
	n := len(p)

	// Not enough data to fill a 1 MB chunk.
	if len(th.remaining)+len(p) < 1<<20 {
		th.remaining = append(th.remaining, p...)
		return n, nil
	}

	// Move enough to fill th.remaining to 1 MB.
	fill := 1<<20 - len(th.remaining)
	th.remaining = append(th.remaining, p[:fill]...)
	p = p[fill:]

	// Append the 1 MB in th.remaining.
	th.nodes = append(th.nodes, sha256.Sum256(th.remaining))
	th.runningHash.Write(th.remaining)
	th.remaining = th.remaining[:0]

	// Append all 1M chunks remaining in p.
	for len(p) >= 1<<20 {
		th.nodes = append(th.nodes, sha256.Sum256(p[:1<<20]))
		th.runningHash.Write(p[:1<<20])
		p = p[1<<20:]
	}

	// Copy what remains in p to th.remaining.
	th.remaining = append(th.remaining, p...)

	return n, nil
}

// Close closes the the remaing chunks of data and then calculates the tree hash.
func (th *TreeHash) Close() error {
	// create last node; it is impossible that it has a size > 1 MB
	if len(th.remaining) > 0 {
		th.nodes = append(th.nodes, sha256.Sum256(th.remaining))
		th.runningHash.Write(th.remaining)
	}
	// Calculate the tree and linear hashes
	if len(th.nodes) > 0 {
		th.treeHash = treeHash(th.nodes)
	}
	th.runningHash.Sum(th.linearHash[:0])
	return nil
}

// TreeHash returns the root-level tree hash of everything written.
func (th *TreeHash) TreeHash() []byte {
	return th.treeHash[:]
}

// Hash returns the linear sha256 checksum of everything written.
func (th *TreeHash) Hash() []byte {
	return th.linearHash[:]
}
<|endoftext|>


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

// Package gnmit is a single-target gNMI collector implementation that can be
// used as an on-device/fake device implementation. It supports the Subscribe RPC
// using the libraries from openconfig/gnmi.
package gnmit

import (
	"context"
	"fmt"
	"net"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/openconfig/gnmi/cache"
	"github.com/openconfig/gnmi/subscribe"
	"github.com/openconfig/ygot/ygot"
	"github.com/openconfig/ygot/ytypes"
	"golang.org/x/exp/slices"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"

	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

const (
	OpenconfigOrigin = "openconfig"
)

// GNMIMode indicates the mode in which the gNMI service operates.
type GNMIMode string

const (
	GNMIModeMetadataKey = "gnmi-mode"
	// ConfigMode indicates that the gNMI service will allow updates to
	// intended configuration, but not operational state values.
	ConfigMode GNMIMode = "config"
	// StateMode indicates that the gNMI service will allow updates to
	// operational state, but not intended configuration values.
	StateMode GNMIMode = "state"
)

// GNMIServer implements the gNMI server interface.
type GNMIServer struct {
	// The subscribe Server implements only Subscribe for gNMI.
	*subscribe.Server
	c *Collector

	configMu     sync.Mutex
	configSchema *ytypes.Schema

	stateMu     sync.Mutex
	stateSchema *ytypes.Schema
}

// New returns a new collector server implementation that can be registered on
// an existing gRPC server.
//
// - configSchema is the specification of the schema if gnmi.Set on config paths is used.
// - stateSchema is the specification of the schema if gnmi.Set on state paths is used.
// - targetName is the name of the target.
// - sendMeta indicates whether metadata should be sent
func New(ctx context.Context, configSchema *ytypes.Schema, stateSchema *ytypes.Schema, targetName string, sendMeta bool) (*Collector, *GNMIServer, error) {
	c := NewCollector(targetName)
	subscribeSrv, err := c.Start(ctx, sendMeta)
	if err != nil {
		return nil, nil, err
	}

	// Initialize the cache with the input schema root.
	if configSchema != nil {
		if err := SetupSchema(configSchema); err != nil {
			return nil, nil, err
		}
		if err := ygot.PruneConfigFalse(configSchema.RootSchema(), configSchema.Root); err != nil {
			return nil, nil, fmt.Errorf("gnmit: %v", err)
		}
		updateCache(c.cache, configSchema.Root, nil, targetName, OpenconfigOrigin, true)
	}

	if stateSchema != nil {
		if err := SetupSchema(stateSchema); err != nil {
			return nil, nil, err
		}
		updateCache(c.cache, stateSchema.Root, nil, targetName, OpenconfigOrigin, true)
	}

	gnmiserver := &GNMIServer{
		Server:       subscribeSrv, // use the 'subscribe' implementation.
		c:            c,
		configSchema: configSchema,
		stateSchema:  stateSchema,
	}

	return c, gnmiserver, nil
}

// Start starts the collector-backed gNMI server that listens on the specified
// addr (in the form host:port).
//
// It returns the address it is listening on in the form hostname:port or any
// errors encounted whilst setting it up.
func (s *GNMIServer) Start(ctx context.Context, addr string, opts ...grpc.ServerOption) (string, error) {
	// Start gNMI server.
	srv := grpc.NewServer(opts...)
	gpb.RegisterGNMIServer(srv, s)
	// Forward streaming updates to clients.
	// Register listening port and start serving.
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		return "", fmt.Errorf("failed to listen: %v", err)
	}

	go func() {
		if err := srv.Serve(lis); err != nil {
			log.Fatalf("Error while serving gnmi target: %v", err)
		}
	}()
	s.c.stopFn = func() {
		srv.GracefulStop()
	}
	return lis.Addr().String(), nil
}

type populateDefaultser interface {
	PopulateDefaults()
}

// SetupSchema takes in a ygot schema object which it assumes to be
// uninitialized. It initializes and validates it, returning any errors
// encountered.
func SetupSchema(schema *ytypes.Schema) error {
	if !schema.IsValid() {
		return fmt.Errorf("cannot obtain valid schema for GoStructs: %v", schema)
	}

	// Initialize the root with default values.
	schema.Root.(populateDefaultser).PopulateDefaults()
	if err := schema.Validate(); err != nil {
		return fmt.Errorf("default root of input schema fails validation: %v", err)
	}

	return nil
}

// updateCache updates the cache with the difference between the root ->
// dirtyRoot such that if the root represents the cache, then the dirtyRoot
// will represent the cache afterwards.
//
// If root is nil, then it is assumed the cache is empty, and the entirety of
// the dirtyRoot is put into the cache.
func updateCache(cache *cache.Cache, dirtyRoot, root ygot.GoStruct, target, origin string, preferShadowPath bool) error {
	var nos []*gpb.Notification
	if root == nil {
		var err error
		if nos, err = ygot.TogNMINotifications(dirtyRoot, time.Now().UnixNano(), ygot.GNMINotificationsConfig{
			UsePathElem: true,
		}); err != nil {
			return fmt.Errorf("gnmit: %v", err)
		}
	} else {
		n, err := ygot.Diff(root, dirtyRoot, &ygot.DiffPathOpt{PreferShadowPath: preferShadowPath})
		if err != nil {
			return fmt.Errorf("gnmit: error while creating update notification for Set: %v", err)
		}
		n.Timestamp = time.Now().UnixNano()
		nos = append(nos, n)
	}

	return updateCacheNotifs(cache, nos, target, origin)
}

// updateCacheNotifs updates the target cache with the given notifications.
func updateCacheNotifs(cache *cache.Cache, nos []*gpb.Notification, target, origin string) error {
	cacheTarget := cache.GetTarget(target)
	for _, n := range nos {
		n.Prefix = &gpb.Path{Origin: origin, Target: target}
		if n.Prefix.Origin == "" {
			n.Prefix.Origin = OpenconfigOrigin
		}

		var pathsForDelete []string
		for _, path := range n.Delete {
			p, err := ygot.PathToString(path)
			if err != nil {
				return fmt.Errorf("cannot convert deleted path to string: %v", err)
			}
			pathsForDelete = append(pathsForDelete, p)
		}
		log.V(1).Infof("datastore: updating the following values: %+v", n.Update)
		log.V(1).Infof("datastore: deleting the following paths: %+v", pathsForDelete)
		if err := cacheTarget.GnmiUpdate(n); err != nil {
			return err
		}
	}
	return nil
}

// set updates the datastore and intended configuration with the SetRequest,
// allowing read-only values to be updated.
//
// update indicates whether to update the cache with the values from the set
// request.
func set(schema *ytypes.Schema, cache *cache.Cache, target string, req *gpb.SetRequest, preferShadowPath bool) error {
	prevRoot, err := ygot.DeepCopy(schema.Root)
	if err != nil {
		return fmt.Errorf("gnmit: failed to ygot.DeepCopy the cached root object: %v", err)
	}

	success := false

	// Rollback function
	defer func() {
		if !success {
			log.V(1).Infof("Rolling back set request: %v", req)
			schema.Root = prevRoot
		}
	}()

	var unmarshalOpts []ytypes.UnmarshalOpt
	if preferShadowPath {
		unmarshalOpts = append(unmarshalOpts, &ytypes.PreferShadowPath{})
	}
	if err := ytypes.UnmarshalSetRequest(schema, req, unmarshalOpts...); err != nil {
		return fmt.Errorf("gnmit: %v", err)
	}

	if err := schema.Validate(); err != nil {
		return fmt.Errorf("gnmit: invalid SetRequest: %v", err)
	}

	success = true

	if err := updateCache(cache, schema.Root, prevRoot, target, req.Prefix.Origin, preferShadowPath); err != nil {
		return err
	}
	return nil
}

// Set is a prototype for a gNMI Set operation.
// TODO(wenbli): Add unit test.
func (s *GNMIServer) Set(ctx context.Context, req *gpb.SetRequest) (*gpb.SetResponse, error) {
	// Use ConfigMode by default so that external users don't need to set metadata.
	gnmiMode := ConfigMode
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		switch {
		case slices.Contains(md.Get(GNMIModeMetadataKey), string(ConfigMode)):
			gnmiMode = ConfigMode
		case slices.Contains(md.Get(GNMIModeMetadataKey), string(StateMode)):
			gnmiMode = StateMode
		}
	}

	switch gnmiMode {
	case ConfigMode:
		s.configMu.Lock()
		defer s.configMu.Unlock()

		log.V(1).Infof("config datastore service received SetRequest: %v", req)
		if s.configSchema == nil {
			return s.UnimplementedGNMIServer.Set(ctx, req)
		}

		// TODO(wenbli): Reject paths that try to modify read-only values.
		// TODO(wenbli): Question: what to do if there are operational-state values in a container that is specified to be replaced or deleted?
		err := set(s.configSchema, s.c.cache, s.c.name, req, true)

		// TODO(wenbli): Currently the SetResponse is not filled.
		return &gpb.SetResponse{
			Timestamp: time.Now().UnixNano(),
		}, err
	case StateMode:
		s.stateMu.Lock()
		defer s.stateMu.Unlock()

		log.V(1).Infof("operational state datastore service received SetRequest: %v", req)
		if s.stateSchema == nil {
			return s.UnimplementedGNMIServer.Set(ctx, req)
		}
		// TODO(wenbli): Reject values that modify config values. We only allow modifying state in this mode.
		if err := set(s.stateSchema, s.c.cache, s.c.name, req, false); err != nil {
			return &gpb.SetResponse{}, status.Errorf(codes.Aborted, "%v", err)
		}

		// This mode is intended to be used internally, and the SetResponse doesn't matter.
		return &gpb.SetResponse{}, nil
	default:
		return nil, status.Errorf(codes.Internal, "incoming gNMI SetRequest must specify a valid GNMIMode via context metadata: %v", md)
	}
}
<commit_msg>add missing comment<commit_after>// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package gnmit is a single-target gNMI collector implementation that can be
// used as an on-device/fake device implementation. It supports the Subscribe RPC
// using the libraries from openconfig/gnmi.
package gnmit

import (
	"context"
	"fmt"
	"net"
	"sync"
	"time"

	log "github.com/golang/glog"
	"github.com/openconfig/gnmi/cache"
	"github.com/openconfig/gnmi/subscribe"
	"github.com/openconfig/ygot/ygot"
	"github.com/openconfig/ygot/ytypes"
	"golang.org/x/exp/slices"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"

	gpb "github.com/openconfig/gnmi/proto/gnmi"
)

const (
	OpenconfigOrigin = "openconfig"
)

// GNMIMode indicates the mode in which the gNMI service operates.
type GNMIMode string

const (
	// GNMIModeMetadataKey is the context metadata key used to specify the
	// mode in which the gnmit gNMI server should operate.
	GNMIModeMetadataKey = "gnmi-mode"
	// ConfigMode indicates that the gNMI service will allow updates to
	// intended configuration, but not operational state values.
	ConfigMode GNMIMode = "config"
	// StateMode indicates that the gNMI service will allow updates to
	// operational state, but not intended configuration values.
	StateMode GNMIMode = "state"
)

// GNMIServer implements the gNMI server interface.
type GNMIServer struct {
	// The subscribe Server implements only Subscribe for gNMI.
	*subscribe.Server
	c *Collector

	configMu     sync.Mutex
	configSchema *ytypes.Schema

	stateMu     sync.Mutex
	stateSchema *ytypes.Schema
}

// New returns a new collector server implementation that can be registered on
// an existing gRPC server.
//
// - configSchema is the specification of the schema if gnmi.Set on config paths is used.
// - stateSchema is the specification of the schema if gnmi.Set on state paths is used.
// - targetName is the name of the target.
// - sendMeta indicates whether metadata should be sent
func New(ctx context.Context, configSchema *ytypes.Schema, stateSchema *ytypes.Schema, targetName string, sendMeta bool) (*Collector, *GNMIServer, error) {
	c := NewCollector(targetName)
	subscribeSrv, err := c.Start(ctx, sendMeta)
	if err != nil {
		return nil, nil, err
	}

	// Initialize the cache with the input schema root.
	if configSchema != nil {
		if err := SetupSchema(configSchema); err != nil {
			return nil, nil, err
		}
		if err := ygot.PruneConfigFalse(configSchema.RootSchema(), configSchema.Root); err != nil {
			return nil, nil, fmt.Errorf("gnmit: %v", err)
		}
		updateCache(c.cache, configSchema.Root, nil, targetName, OpenconfigOrigin, true)
	}

	if stateSchema != nil {
		if err := SetupSchema(stateSchema); err != nil {
			return nil, nil, err
		}
		updateCache(c.cache, stateSchema.Root, nil, targetName, OpenconfigOrigin, true)
	}

	gnmiserver := &GNMIServer{
		Server:       subscribeSrv, // use the 'subscribe' implementation.
		c:            c,
		configSchema: configSchema,
		stateSchema:  stateSchema,
	}

	return c, gnmiserver, nil
}

// Start starts the collector-backed gNMI server that listens on the specified
// addr (in the form host:port).
//
// It returns the address it is listening on in the form hostname:port or any
// errors encounted whilst setting it up.
func (s *GNMIServer) Start(ctx context.Context, addr string, opts ...grpc.ServerOption) (string, error) {
	// Start gNMI server.
	srv := grpc.NewServer(opts...)
	gpb.RegisterGNMIServer(srv, s)
	// Forward streaming updates to clients.
	// Register listening port and start serving.
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		return "", fmt.Errorf("failed to listen: %v", err)
	}

	go func() {
		if err := srv.Serve(lis); err != nil {
			log.Fatalf("Error while serving gnmi target: %v", err)
		}
	}()
	s.c.stopFn = func() {
		srv.GracefulStop()
	}
	return lis.Addr().String(), nil
}

type populateDefaultser interface {
	PopulateDefaults()
}

// SetupSchema takes in a ygot schema object which it assumes to be
// uninitialized. It initializes and validates it, returning any errors
// encountered.
func SetupSchema(schema *ytypes.Schema) error {
	if !schema.IsValid() {
		return fmt.Errorf("cannot obtain valid schema for GoStructs: %v", schema)
	}

	// Initialize the root with default values.
	schema.Root.(populateDefaultser).PopulateDefaults()
	if err := schema.Validate(); err != nil {
		return fmt.Errorf("default root of input schema fails validation: %v", err)
	}

	return nil
}

// updateCache updates the cache with the difference between the root ->
// dirtyRoot such that if the root represents the cache, then the dirtyRoot
// will represent the cache afterwards.
//
// If root is nil, then it is assumed the cache is empty, and the entirety of
// the dirtyRoot is put into the cache.
func updateCache(cache *cache.Cache, dirtyRoot, root ygot.GoStruct, target, origin string, preferShadowPath bool) error {
	var nos []*gpb.Notification
	if root == nil {
		var err error
		if nos, err = ygot.TogNMINotifications(dirtyRoot, time.Now().UnixNano(), ygot.GNMINotificationsConfig{
			UsePathElem: true,
		}); err != nil {
			return fmt.Errorf("gnmit: %v", err)
		}
	} else {
		n, err := ygot.Diff(root, dirtyRoot, &ygot.DiffPathOpt{PreferShadowPath: preferShadowPath})
		if err != nil {
			return fmt.Errorf("gnmit: error while creating update notification for Set: %v", err)
		}
		n.Timestamp = time.Now().UnixNano()
		nos = append(nos, n)
	}

	return updateCacheNotifs(cache, nos, target, origin)
}

// updateCacheNotifs updates the target cache with the given notifications.
func updateCacheNotifs(cache *cache.Cache, nos []*gpb.Notification, target, origin string) error {
	cacheTarget := cache.GetTarget(target)
	for _, n := range nos {
		n.Prefix = &gpb.Path{Origin: origin, Target: target}
		if n.Prefix.Origin == "" {
			n.Prefix.Origin = OpenconfigOrigin
		}

		var pathsForDelete []string
		for _, path := range n.Delete {
			p, err := ygot.PathToString(path)
			if err != nil {
				return fmt.Errorf("cannot convert deleted path to string: %v", err)
			}
			pathsForDelete = append(pathsForDelete, p)
		}
		log.V(1).Infof("datastore: updating the following values: %+v", n.Update)
		log.V(1).Infof("datastore: deleting the following paths: %+v", pathsForDelete)
		if err := cacheTarget.GnmiUpdate(n); err != nil {
			return err
		}
	}
	return nil
}

// set updates the datastore and intended configuration with the SetRequest,
// allowing read-only values to be updated.
//
// update indicates whether to update the cache with the values from the set
// request.
func set(schema *ytypes.Schema, cache *cache.Cache, target string, req *gpb.SetRequest, preferShadowPath bool) error {
	prevRoot, err := ygot.DeepCopy(schema.Root)
	if err != nil {
		return fmt.Errorf("gnmit: failed to ygot.DeepCopy the cached root object: %v", err)
	}

	success := false

	// Rollback function
	defer func() {
		if !success {
			log.V(1).Infof("Rolling back set request: %v", req)
			schema.Root = prevRoot
		}
	}()

	var unmarshalOpts []ytypes.UnmarshalOpt
	if preferShadowPath {
		unmarshalOpts = append(unmarshalOpts, &ytypes.PreferShadowPath{})
	}
	if err := ytypes.UnmarshalSetRequest(schema, req, unmarshalOpts...); err != nil {
		return fmt.Errorf("gnmit: %v", err)
	}

	if err := schema.Validate(); err != nil {
		return fmt.Errorf("gnmit: invalid SetRequest: %v", err)
	}

	success = true

	if err := updateCache(cache, schema.Root, prevRoot, target, req.Prefix.Origin, preferShadowPath); err != nil {
		return err
	}
	return nil
}

// Set is a prototype for a gNMI Set operation.
// TODO(wenbli): Add unit test.
func (s *GNMIServer) Set(ctx context.Context, req *gpb.SetRequest) (*gpb.SetResponse, error) {
	// Use ConfigMode by default so that external users don't need to set metadata.
	gnmiMode := ConfigMode
	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		switch {
		case slices.Contains(md.Get(GNMIModeMetadataKey), string(ConfigMode)):
			gnmiMode = ConfigMode
		case slices.Contains(md.Get(GNMIModeMetadataKey), string(StateMode)):
			gnmiMode = StateMode
		}
	}

	switch gnmiMode {
	case ConfigMode:
		s.configMu.Lock()
		defer s.configMu.Unlock()

		log.V(1).Infof("config datastore service received SetRequest: %v", req)
		if s.configSchema == nil {
			return s.UnimplementedGNMIServer.Set(ctx, req)
		}

		// TODO(wenbli): Reject paths that try to modify read-only values.
		// TODO(wenbli): Question: what to do if there are operational-state values in a container that is specified to be replaced or deleted?
		err := set(s.configSchema, s.c.cache, s.c.name, req, true)

		// TODO(wenbli): Currently the SetResponse is not filled.
		return &gpb.SetResponse{
			Timestamp: time.Now().UnixNano(),
		}, err
	case StateMode:
		s.stateMu.Lock()
		defer s.stateMu.Unlock()

		log.V(1).Infof("operational state datastore service received SetRequest: %v", req)
		if s.stateSchema == nil {
			return s.UnimplementedGNMIServer.Set(ctx, req)
		}
		// TODO(wenbli): Reject values that modify config values. We only allow modifying state in this mode.
		if err := set(s.stateSchema, s.c.cache, s.c.name, req, false); err != nil {
			return &gpb.SetResponse{}, status.Errorf(codes.Aborted, "%v", err)
		}

		// This mode is intended to be used internally, and the SetResponse doesn't matter.
		return &gpb.SetResponse{}, nil
	default:
		return nil, status.Errorf(codes.Internal, "incoming gNMI SetRequest must specify a valid GNMIMode via context metadata: %v", md)
	}
}
<|endoftext|>


<commit_before>package libkb

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"sync"
)

type GpgCLI struct {
	path    string
	options []string

	// Configuration --- cache the results
	configured     bool
	configExplicit bool
	configError    error

	mutex *sync.Mutex

	logUI LogUI
}

type GpgCLIArg struct {
	LogUI LogUI // If nil, use the global
}

func NewGpgCLI(arg GpgCLIArg) *GpgCLI {
	logUI := arg.LogUI
	if logUI == nil {
		logUI = G.Log
	}
	return &GpgCLI{
		configured: false,
		mutex:      new(sync.Mutex),
		logUI:      logUI,
	}
}

func (g *GpgCLI) Configure() (configExplicit bool, err error) {

	g.mutex.Lock()
	defer g.mutex.Unlock()

	if g.configured {
		configExplicit = g.configExplicit
		err = g.configError
		return
	}

	prog := G.Env.GetGpg()
	opts := G.Env.GetGpgOptions()

	// If we asked for any explicit GPG options
	configExplicit = (len(prog) > 0 || opts != nil)
	if len(prog) > 0 {
		err = canExec(prog)
	} else {
		prog, err = exec.LookPath("gpg2")
		if err != nil {
			prog, err = exec.LookPath("gpg")
		}
	}

	g.logUI.Debug("| configured GPG w/ path: %s", prog)

	g.path = prog
	g.options = opts
	g.configured = true
	g.configExplicit = configExplicit
	g.configError = err

	return
}

// CanExec returns true if a gpg executable exists.
func (g *GpgCLI) CanExec() (bool, error) {
	if _, err := g.Configure(); err != nil {
		if oerr, ok := err.(*exec.Error); ok {
			if oerr.Err == exec.ErrNotFound {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Path returns the path of the gpg executable.  Configure must be
// called before using this.
func (g *GpgCLI) Path() string {
	if !g.configured {
		panic("GpgCLI not configured")
	}
	return g.path
}

type RunGpgArg struct {
	Arguments []string
	Stdin     bool
	Stderr    io.WriteCloser
	Stdout    io.WriteCloser
}

type RunGpgRes struct {
	Stdin io.WriteCloser
	Err   error
	Wait  func() error
}

func (g *GpgCLI) ImportKey(secret bool, fp PGPFingerprint) (ret *PGPKeyBundle, err error) {
	var cmd string
	if secret {
		cmd = "--export-secret-key"
	} else {
		cmd = "--export"
	}

	arg := RunGpg2Arg{
		Arguments: []string{"--armor", cmd, fp.String()},
		Stdout:    true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return nil, res.Err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Stdout)
	armored := buf.String()

	if err = res.Wait(); err != nil {
		return nil, err
	}

	if len(armored) == 0 {
		return nil, NoKeyError{fmt.Sprintf("No key found for %s", fp)}
	}

	return ReadOneKeyFromString(armored)
}

func (g *GpgCLI) ExportKey(k PGPKeyBundle) (err error) {
	arg := RunGpg2Arg{
		Arguments: []string{"--import"},
		Stdin:     true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return res.Err
	}

	e1 := k.EncodeToStream(res.Stdin)
	e2 := res.Stdin.Close()
	e3 := res.Wait()
	return PickFirstError(e1, e2, e3)
}

type RunGpg2Arg struct {
	Arguments []string
	Stdin     bool
	Stderr    bool
	Stdout    bool
}

type RunGpg2Res struct {
	Stdin  io.WriteCloser
	Stdout io.ReadCloser
	Stderr io.ReadCloser
	Wait   func() error
	Err    error
}

func (g *GpgCLI) Run2(arg RunGpg2Arg) (res RunGpg2Res) {

	cmd := g.MakeCmd(arg.Arguments)

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}

	var stdout, stderr io.ReadCloser

	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waited := false
	out := 0
	ch := make(chan error)
	var fep FirstErrorPicker

	res.Wait = func() error {
		for out > 0 {
			fep.Push(<-ch)
			out--
		}
		if !waited {
			waited = true
			err := cmd.Wait()
			if err != nil {
				fep.Push(ErrorToGpgError(err))
			}
			return fep.Error()
		}
		return nil
	}

	if !arg.Stdout {
		out++
		go func() {
			ch <- DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stdout = stdout
	}

	if !arg.Stderr {
		out++
		go func() {
			ch <- DrainPipe(stderr, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stderr = stderr
	}

	return
}

func (g *GpgCLI) MakeCmd(args []string) *exec.Cmd {
	var nargs []string
	if g.options != nil {
		nargs = make([]string, len(g.options))
		copy(nargs, g.options)
		nargs = append(nargs, args...)
	} else {
		nargs = args
	}
	g.logUI.Debug("| running Gpg: %s %v", g.path, nargs)
	return exec.Command(g.path, nargs...)
}

func (g *GpgCLI) Run(arg RunGpgArg) (res RunGpgRes) {

	cmd := g.MakeCmd(arg.Arguments)

	waited := false

	var stdout, stderr io.ReadCloser

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}
	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waitfn := func() error {
		if !waited {
			waited = true
			return cmd.Wait()
		}
		return nil
	}

	if arg.Stdin {
		res.Wait = waitfn
	} else {
		defer waitfn()
	}

	var e1, e2, e3 error

	if arg.Stdout != nil {
		_, e1 = io.Copy(arg.Stdout, stdout)
	} else {
		e1 = DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
	}

	if arg.Stderr != nil {
		_, e2 = io.Copy(arg.Stderr, stderr)
	} else {
		e2 = DrainPipe(stderr, func(s string) { g.logUI.Warning(s) })
	}

	if !arg.Stdin {
		e3 = waitfn()
	}

	res.Err = PickFirstError(e1, e2, e3)
	return
}
<commit_msg>Don't cache gpg config, because we would also cache failures<commit_after>package libkb

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"sync"
)

type GpgCLI struct {
	path    string
	options []string

	// Configuration --- cache the results
	configured     bool
	configExplicit bool
	configError    error

	mutex *sync.Mutex

	logUI LogUI
}

type GpgCLIArg struct {
	LogUI LogUI // If nil, use the global
}

func NewGpgCLI(arg GpgCLIArg) *GpgCLI {
	logUI := arg.LogUI
	if logUI == nil {
		logUI = G.Log
	}
	return &GpgCLI{
		configured: false,
		mutex:      new(sync.Mutex),
		logUI:      logUI,
	}
}

func (g *GpgCLI) Configure() (configExplicit bool, err error) {

	g.mutex.Lock()
	defer g.mutex.Unlock()

	prog := G.Env.GetGpg()
	opts := G.Env.GetGpgOptions()

	// If we asked for any explicit GPG options
	configExplicit = (len(prog) > 0 || opts != nil)
	if len(prog) > 0 {
		err = canExec(prog)
	} else {
		prog, err = exec.LookPath("gpg2")
		if err != nil {
			prog, err = exec.LookPath("gpg")
		}
	}

	g.logUI.Debug("| configured GPG w/ path: %s", prog)

	g.path = prog
	g.options = opts

	return
}

// CanExec returns true if a gpg executable exists.
func (g *GpgCLI) CanExec() (bool, error) {
	if _, err := g.Configure(); err != nil {
		if oerr, ok := err.(*exec.Error); ok {
			if oerr.Err == exec.ErrNotFound {
				return false, nil
			}
		}
		return false, err
	}
	return true, nil
}

// Path returns the path of the gpg executable.  Configure must be
// called before using this.
func (g *GpgCLI) Path() string {
	if !g.configured {
		panic("GpgCLI not configured")
	}
	return g.path
}

type RunGpgArg struct {
	Arguments []string
	Stdin     bool
	Stderr    io.WriteCloser
	Stdout    io.WriteCloser
}

type RunGpgRes struct {
	Stdin io.WriteCloser
	Err   error
	Wait  func() error
}

func (g *GpgCLI) ImportKey(secret bool, fp PGPFingerprint) (ret *PGPKeyBundle, err error) {
	var cmd string
	if secret {
		cmd = "--export-secret-key"
	} else {
		cmd = "--export"
	}

	arg := RunGpg2Arg{
		Arguments: []string{"--armor", cmd, fp.String()},
		Stdout:    true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return nil, res.Err
	}

	buf := new(bytes.Buffer)
	buf.ReadFrom(res.Stdout)
	armored := buf.String()

	if err = res.Wait(); err != nil {
		return nil, err
	}

	if len(armored) == 0 {
		return nil, NoKeyError{fmt.Sprintf("No key found for %s", fp)}
	}

	return ReadOneKeyFromString(armored)
}

func (g *GpgCLI) ExportKey(k PGPKeyBundle) (err error) {
	arg := RunGpg2Arg{
		Arguments: []string{"--import"},
		Stdin:     true,
	}

	res := g.Run2(arg)
	if res.Err != nil {
		return res.Err
	}

	e1 := k.EncodeToStream(res.Stdin)
	e2 := res.Stdin.Close()
	e3 := res.Wait()
	return PickFirstError(e1, e2, e3)
}

type RunGpg2Arg struct {
	Arguments []string
	Stdin     bool
	Stderr    bool
	Stdout    bool
}

type RunGpg2Res struct {
	Stdin  io.WriteCloser
	Stdout io.ReadCloser
	Stderr io.ReadCloser
	Wait   func() error
	Err    error
}

func (g *GpgCLI) Run2(arg RunGpg2Arg) (res RunGpg2Res) {

	cmd := g.MakeCmd(arg.Arguments)

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}

	var stdout, stderr io.ReadCloser

	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waited := false
	out := 0
	ch := make(chan error)
	var fep FirstErrorPicker

	res.Wait = func() error {
		for out > 0 {
			fep.Push(<-ch)
			out--
		}
		if !waited {
			waited = true
			err := cmd.Wait()
			if err != nil {
				fep.Push(ErrorToGpgError(err))
			}
			return fep.Error()
		}
		return nil
	}

	if !arg.Stdout {
		out++
		go func() {
			ch <- DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stdout = stdout
	}

	if !arg.Stderr {
		out++
		go func() {
			ch <- DrainPipe(stderr, func(s string) { g.logUI.Info(s) })
		}()
	} else {
		res.Stderr = stderr
	}

	return
}

func (g *GpgCLI) MakeCmd(args []string) *exec.Cmd {
	var nargs []string
	if g.options != nil {
		nargs = make([]string, len(g.options))
		copy(nargs, g.options)
		nargs = append(nargs, args...)
	} else {
		nargs = args
	}
	g.logUI.Debug("| running Gpg: %s %v", g.path, nargs)
	return exec.Command(g.path, nargs...)
}

func (g *GpgCLI) Run(arg RunGpgArg) (res RunGpgRes) {

	cmd := g.MakeCmd(arg.Arguments)

	waited := false

	var stdout, stderr io.ReadCloser

	if arg.Stdin {
		if res.Stdin, res.Err = cmd.StdinPipe(); res.Err != nil {
			return
		}
	}
	if stdout, res.Err = cmd.StdoutPipe(); res.Err != nil {
		return
	}
	if stderr, res.Err = cmd.StderrPipe(); res.Err != nil {
		return
	}

	if res.Err = cmd.Start(); res.Err != nil {
		return
	}

	waitfn := func() error {
		if !waited {
			waited = true
			return cmd.Wait()
		}
		return nil
	}

	if arg.Stdin {
		res.Wait = waitfn
	} else {
		defer waitfn()
	}

	var e1, e2, e3 error

	if arg.Stdout != nil {
		_, e1 = io.Copy(arg.Stdout, stdout)
	} else {
		e1 = DrainPipe(stdout, func(s string) { g.logUI.Info(s) })
	}

	if arg.Stderr != nil {
		_, e2 = io.Copy(arg.Stderr, stderr)
	} else {
		e2 = DrainPipe(stderr, func(s string) { g.logUI.Warning(s) })
	}

	if !arg.Stdin {
		e3 = waitfn()
	}

	res.Err = PickFirstError(e1, e2, e3)
	return
}
<|endoftext|>


<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"fmt"
)

// NOTE: This file is the source of truth for our version number, but we have a
//       script that reads it at packaging/version.sh. If you refactor this
//       file, update that script.

// Version as MAJOR.MINOR.PATCH
const Version = "1.0.5"

// Build number
const Build = "4"

// VersionString returns semantic version string.
func VersionString() string {
	return fmt.Sprintf("%s-%s", Version, Build)
}
<commit_msg>Bump build number to 5 for 1.0.5<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package libkb

import (
	"fmt"
)

// NOTE: This file is the source of truth for our version number, but we have a
//       script that reads it at packaging/version.sh. If you refactor this
//       file, update that script.

// Version as MAJOR.MINOR.PATCH
const Version = "1.0.5"

// Build number
const Build = "5"

// VersionString returns semantic version string.
func VersionString() string {
	return fmt.Sprintf("%s-%s", Version, Build)
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"os/signal"
	"runtime"
	"syscall"

	"github.com/graffic/wanon/bot"
	"github.com/graffic/wanon/messages/ignorechat"
	"github.com/graffic/wanon/messages/quotes"
	"github.com/graffic/wanon/telegram"
	"github.com/op/go-logging"
)

var log = logging.MustGetLogger("wanon")

func initLogging() {
	backend := logging.NewLogBackend(os.Stderr, "", 0)
	format := logging.MustStringFormatter(
		"%{color}%{time:15:04:05.000} %{level:5.5s} %{module} >%{color:reset} %{message}",
	)
	formatter := logging.NewBackendFormatter(backend, format)

	// Set the backends to be used.
	logging.SetBackend(formatter)
	logging.SetLevel(logging.INFO, "wanon.telegram")
}

// checkFatal exits on fatal
func checkFatal(err error) {
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

func sigQuit() {
	go func() {
		sigs := make(chan os.Signal, 1)
		signal.Notify(sigs, syscall.SIGQUIT)
		buf := make([]byte, 1<<20)
		for {
			<-sigs
			runtime.Stack(buf, true)
			log.Warning("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf)
		}
	}()
}

func main() {
	initLogging()
	log.Debug("Wanon booting")

	context, err := bot.CreateContext("conf.yaml")
	checkFatal(err)

	log.Info("All systems nominal")

	channel := make(chan *telegram.Message)
	go context.API.ProcessUpdates(channel)

	router := bot.Router{}
	router.AddHandler(ignorechat.Create(context.Conf))
	router.AddHandler(quotes.CreateAddQuote())
	router.AddHandler(quotes.CreateRandomQuote())
	router.RouteMessages(channel, context)
}
<commit_msg>Enable sigquit<commit_after>package main

import (
	"os"
	"os/signal"
	"runtime"
	"syscall"

	"github.com/graffic/wanon/bot"
	"github.com/graffic/wanon/messages/ignorechat"
	"github.com/graffic/wanon/messages/quotes"
	"github.com/graffic/wanon/telegram"
	"github.com/op/go-logging"
)

var log = logging.MustGetLogger("wanon")

func initLogging() {
	backend := logging.NewLogBackend(os.Stderr, "", 0)
	format := logging.MustStringFormatter(
		"%{color}%{time:15:04:05.000} %{level:5.5s} %{module} >%{color:reset} %{message}",
	)
	formatter := logging.NewBackendFormatter(backend, format)

	// Set the backends to be used.
	logging.SetBackend(formatter)
	logging.SetLevel(logging.INFO, "wanon.telegram")
}

// checkFatal exits on fatal
func checkFatal(err error) {
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
}

func sigQuit() {
	go func() {
		sigs := make(chan os.Signal, 1)
		signal.Notify(sigs, syscall.SIGQUIT)
		buf := make([]byte, 1<<20)
		for {
			<-sigs
			runtime.Stack(buf, true)
			log.Warning("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf)
		}
	}()
}

func main() {
	sigQuit()
	initLogging()
	log.Debug("Wanon booting")

	context, err := bot.CreateContext("conf.yaml")
	checkFatal(err)

	log.Info("All systems nominal")

	channel := make(chan *telegram.Message)
	go context.API.ProcessUpdates(channel)

	router := bot.Router{}
	router.AddHandler(ignorechat.Create(context.Conf))
	router.AddHandler(quotes.CreateAddQuote())
	router.AddHandler(quotes.CreateRandomQuote())
	router.RouteMessages(channel, context)
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"time"

	"github.com/asim/micro-mq/go/client"
)

func main() {
	tick := time.NewTicker(time.Second)

	for _ = range tick.C {
		if err := client.Publish("foo", []byte(`bar`)); err != nil {
			log.Println(err)
			break
		}
	}
}
<commit_msg>Update pub.go<commit_after>package main

import (
	"log"
	"time"

	"github.com/asim/mq/go/client"
)

func main() {
	tick := time.NewTicker(time.Second)

	for _ = range tick.C {
		if err := client.Publish("foo", []byte(`bar`)); err != nil {
			log.Println(err)
			break
		}
	}
}
<|endoftext|>


<commit_before>package goFlags

import (
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"

	"flag"

	"github.com/crgimenes/goConfig/structTag"
)

type parameterMeta struct {
	Kind  reflect.Kind
	Value interface{}
	Tag   string
}

var parametersMetaMap map[*reflect.Value]parameterMeta
var visitedMap map[string]*flag.Flag

// Preserve disable default values and get only visited parameters thus preserving the values passed in the structure, default false
var Preserve bool

func init() {
	Setup()
}

// Setup maps and variables
func Setup() {
	parametersMetaMap = make(map[*reflect.Value]parameterMeta)
	visitedMap = make(map[string]*flag.Flag)

	SetTag("flag")
	SetTagDefault("flagDefault")

	structTag.ParseMap[reflect.Int] = reflectInt
	structTag.ParseMap[reflect.String] = reflectString
}

// SetTag set a new tag
func SetTag(tag string) {
	structTag.Tag = tag
}

// SetTagDefault set a new TagDefault to retorn default values
func SetTagDefault(tag string) {
	structTag.TagDefault = tag
}

// Parse configuration
func Parse(config interface{}) (err error) {

	err = structTag.Parse(config, "")
	if err != nil {
		return
	}

	flag.Parse()

	flag.Visit(loadVisit)

	for k, v := range parametersMetaMap {
		if _, ok := visitedMap[v.Tag]; !ok && Preserve {
			continue
		}
		switch v.Kind {
		case reflect.String:
			value := *v.Value.(*string)
			fmt.Printf("Parse %v = \"%v\"\n", v.Tag, value)
			k.SetString(value)
		case reflect.Int:
			value := *v.Value.(*int)
			fmt.Printf("Parse %v = \"%v\"\n", v.Tag, value)
			k.SetInt(int64(value))

		}
	}

	return
}

// Reset maps caling setup function
func Reset() {
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flag.Usage = nil

	structTag.Reset()
	Setup()
}

func loadVisit(f *flag.Flag) {
	fmt.Printf("name \"%v\"\n", f.Name)
	visitedMap[f.Name] = f
}

func reflectInt(field *reflect.StructField, value *reflect.Value, tag string) (err error) {
	var aux int
	var defaltValue string
	var defaltValueInt int

	defaltValue = field.Tag.Get(structTag.TagDefault)

	if defaltValue == "" || defaltValue == "0" {
		defaltValueInt = 0
	} else {
		defaltValueInt, err = strconv.Atoi(defaltValue)
		if err != nil {
			return
		}
	}

	meta := parameterMeta{}
	meta.Value = &aux
	meta.Tag = strings.ToLower(tag)
	meta.Kind = reflect.Int
	parametersMetaMap[value] = meta

	flag.IntVar(&aux, meta.Tag, defaltValueInt, "")

	fmt.Println(tag, defaltValue)

	return
}

func reflectString(field *reflect.StructField, value *reflect.Value, tag string) (err error) {

	var aux string
	var defaltValue string
	defaltValue = field.Tag.Get(structTag.TagDefault)

	meta := parameterMeta{}
	meta.Value = &aux
	meta.Tag = strings.ToLower(tag)
	meta.Kind = reflect.String
	parametersMetaMap[value] = meta

	flag.StringVar(&aux, meta.Tag, defaltValue, "")

	fmt.Println(tag, defaltValue)

	return
}
<commit_msg>remove setup from init<commit_after>package goFlags

import (
	"fmt"
	"os"
	"reflect"
	"strconv"
	"strings"

	"flag"

	"github.com/crgimenes/goConfig/structTag"
)

type parameterMeta struct {
	Kind  reflect.Kind
	Value interface{}
	Tag   string
}

var parametersMetaMap map[*reflect.Value]parameterMeta
var visitedMap map[string]*flag.Flag

// Preserve disable default values and get only visited parameters thus preserving the values passed in the structure, default false
var Preserve bool

// Setup maps and variables
func Setup() {
	parametersMetaMap = make(map[*reflect.Value]parameterMeta)
	visitedMap = make(map[string]*flag.Flag)

	structTag.Setup()
	SetTag("flag")
	SetTagDefault("flagDefault")

	structTag.ParseMap[reflect.Int] = reflectInt
	structTag.ParseMap[reflect.String] = reflectString
}

// SetTag set a new tag
func SetTag(tag string) {
	structTag.Tag = tag
}

// SetTagDefault set a new TagDefault to retorn default values
func SetTagDefault(tag string) {
	structTag.TagDefault = tag
}

// Parse configuration
func Parse(config interface{}) (err error) {

	err = structTag.Parse(config, "")
	if err != nil {
		return
	}

	flag.Parse()

	flag.Visit(loadVisit)

	for k, v := range parametersMetaMap {
		if _, ok := visitedMap[v.Tag]; !ok && Preserve {
			continue
		}
		switch v.Kind {
		case reflect.String:
			value := *v.Value.(*string)
			fmt.Printf("Parse %v = \"%v\"\n", v.Tag, value)
			k.SetString(value)
		case reflect.Int:
			value := *v.Value.(*int)
			fmt.Printf("Parse %v = \"%v\"\n", v.Tag, value)
			k.SetInt(int64(value))

		}
	}

	return
}

// Reset maps caling setup function
func Reset() {
	flag.CommandLine = flag.NewFlagSet(os.Args[0], flag.ContinueOnError)
	flag.Usage = nil

	structTag.Reset()
	Setup()
}

func loadVisit(f *flag.Flag) {
	visitedMap[f.Name] = f
}

func reflectInt(field *reflect.StructField, value *reflect.Value, tag string) (err error) {
	var aux int
	var defaltValue string
	var defaltValueInt int

	defaltValue = field.Tag.Get(structTag.TagDefault)

	if defaltValue == "" || defaltValue == "0" {
		defaltValueInt = 0
	} else {
		defaltValueInt, err = strconv.Atoi(defaltValue)
		if err != nil {
			return
		}
	}

	meta := parameterMeta{}
	meta.Value = &aux
	meta.Tag = strings.ToLower(tag)
	meta.Kind = reflect.Int
	parametersMetaMap[value] = meta

	flag.IntVar(&aux, meta.Tag, defaltValueInt, "")

	//fmt.Println(tag, defaltValue)

	return
}

func reflectString(field *reflect.StructField, value *reflect.Value, tag string) (err error) {

	var aux string
	var defaltValue string
	defaltValue = field.Tag.Get(structTag.TagDefault)

	meta := parameterMeta{}
	meta.Value = &aux
	meta.Tag = strings.ToLower(tag)
	meta.Kind = reflect.String
	parametersMetaMap[value] = meta

	flag.StringVar(&aux, meta.Tag, defaltValue, "")

	//fmt.Println(tag, defaltValue)

	return
}
<|endoftext|>


<commit_before>package godingtalk

import (
	"os"
	"testing"
	"time"
)

var c *DingTalkClient

func init() {
	c = NewDingTalkClient(os.Getenv("corpid"), os.Getenv("corpsecret"))
	err := c.RefreshAccessToken()
	if err != nil {
		panic(err)
	}
}

func TestCalendarListApi(t *testing.T) {
	from := time.Now().AddDate(0, 0, -1)
	to := time.Now().AddDate(0, 0, 1)
	events, err := c.ListEvents("0420506555", from, to)
	if err != nil {
		panic(err)
	}
	for _, event := range events {
		t.Logf("%v %v %v %v", event.Start, event.End, event.Summary, event.Description)
	}
}

func TestDepartmentApi(t *testing.T) {
	departments, err := c.DepartmentList()
	// t.Logf("%+v %+v", departments, err)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}
	d, err := c.DepartmentDetail(departments.Departments[0].Id)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}
	if d.Id != departments.Departments[0].Id {
		t.Error("DepartmentDetail error")
	}

	for _, department := range departments.Departments {
		t.Logf("dept: %v", department)
		list, err := c.UserList(department.Id, 0, 100)
		if err != nil {
			t.Error(err)
		}
		for _, user := range list.Userlist {
			t.Logf("\t\tuser: %v", user)
		}
	}
}

func TestJsAPITicket(t *testing.T) {
	ticket, err := c.GetJsAPITicket()
	if err != nil || ticket == "" {
		t.Error("JsAPITicket error", err)
	}
}

func TestCreateChat(t *testing.T) {
	// chatid, err := c.CreateChat("Test chat", "0420506555", []string{"0420506555"})
	// if err!=nil {
	//     t.Error(err)
	// }
	// t.Log("-----",chatid)
}

func TestSendAppMessageApi(t *testing.T) {
	err := c.SendAppMessage("22194403", "0420506555", "测试消息，请忽略") //@all
	if err != nil {
		t.Error(err)
	}
}

func TestTextMessage(t *testing.T) {
	data, err := c.SendTextMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "测试消息，来自双十一，请忽略")
	if err != nil {
		t.Error(err)
	} else {
		if data.MessageID == "" {
			t.Error("Message id is empty")
		}
	}
	data2, _ := c.GetMessageReadList(data.MessageID, 0, 10)
	if len(data2.ReadUserIdList) == 0 {
		t.Error("Message Read List should not be empty")
	}
}

func TestSendOAMessage(t *testing.T) {
	msg := OAMessage{}
	msg.URL = "http://www.google.com/"
	msg.Head.Text = "头部标题"
	msg.Head.BgColor = "FFBBBBBB"
	msg.Body.Title = "正文标题"
	msg.Body.Content = "test content"
	_, err := c.SendOAMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", msg)
	if err != nil {
		t.Error(err)
	}
}

func TestDownloadAndUploadImage(t *testing.T) {
	f, err := os.Create("lADOHrf_oVxc.jpg")
	if err == nil {
		err = c.DownloadMedia("@lADOHrf_oVxc", f)
	}
	if err != nil {
		t.Error(err)
	}
	f.Close()

	f, _ = os.Open("lADOHrf_oVxc.jpg")
	defer f.Close()
	media, err := c.UploadMedia("image", "myfile.jpg", f)
	if media.MediaID == "" {
		t.Error("Upload File Failed")
	}
	t.Log("uploaded file mediaid:", media.MediaID)
	if err != nil {
		t.Error(err)
	}
	_, err = c.SendImageMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lADOHrf_oVxc")
	if err != nil {
		t.Error(err)
	}
}

func TestVoiceMessage(t *testing.T) {
	// f, _ := os.Open("/Users/hugozhu/Downloads/BlackBerry_test2_AMR-NB_Mono_12.2kbps_8000Hz.amr")
	// defer f.Close()
	// media, err := c.UploadMedia("voice", "sample.amr", f)
	// if media.MediaID == "" {
	// 	t.Error("Upload File Failed")
	// }
	// t.Log("uploaded file mediaid:", media.MediaID)
	// if err != nil {
	// 	t.Error(err)
	// }
	_, err := c.SendVoiceMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lATOHr53E84DALnDzml4wS0", "10")
	if err != nil {
		t.Error(err)
	}
}

func TestRobotMessage(t *testing.T) {
	_, err := c.SendRobotTextMessage(os.Getenv("token"), "这是一条测试消息")
	if err != nil {
		t.Error(err)
	}

	_, err = c.SendRobotMarkdownMessage(os.Getenv("token"), "测试标题", "# 杭州天气\n这是一条测试消息\n"+
		"> 9度，西北风1级，空气良89，**相对温度**73%\n\n"+
		"> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n"+
		"> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n")
	if err != nil {
		t.Error(err)
	}
}

func TestRobotAtMessage(t *testing.T) {
	_, err := c.SendRobotTextAtMessage(os.Getenv("token"), "这是一条测试消息", &RobotAtList{
		IsAtAll: true,
	})
	if err != nil {
		t.Error(err)
	}
}
<commit_msg>removed calendar events<commit_after>package godingtalk

import (
	"os"
	"testing"
	"time"
)

var c *DingTalkClient

func init() {
	c = NewDingTalkClient(os.Getenv("corpid"), os.Getenv("corpsecret"))
	err := c.RefreshAccessToken()
	if err != nil {
		panic(err)
	}
}

func TestCalendarListApi(t *testing.T) {
	from := time.Now().AddDate(0, 0, -1)
	to := time.Now().AddDate(0, 0, 1)
	events, err := c.ListEvents("0420506555", from, to)
	if err != nil {
		panic(err)
	}
	//for _, event := range events {
	//	t.Logf("%v %v %v %v", event.Start, event.End, event.Summary, event.Description)
	//}
}

func TestDepartmentApi(t *testing.T) {
	departments, err := c.DepartmentList()
	// t.Logf("%+v %+v", departments, err)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}
	d, err := c.DepartmentDetail(departments.Departments[0].Id)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}
	if d.Id != departments.Departments[0].Id {
		t.Error("DepartmentDetail error")
	}

	for _, department := range departments.Departments {
		t.Logf("dept: %v", department)
		list, err := c.UserList(department.Id, 0, 100)
		if err != nil {
			t.Error(err)
		}
		for _, user := range list.Userlist {
			t.Logf("\t\tuser: %v", user)
		}
	}
}

func TestJsAPITicket(t *testing.T) {
	ticket, err := c.GetJsAPITicket()
	if err != nil || ticket == "" {
		t.Error("JsAPITicket error", err)
	}
}

func TestCreateChat(t *testing.T) {
	// chatid, err := c.CreateChat("Test chat", "0420506555", []string{"0420506555"})
	// if err!=nil {
	//     t.Error(err)
	// }
	// t.Log("-----",chatid)
}

func TestSendAppMessageApi(t *testing.T) {
	err := c.SendAppMessage("22194403", "0420506555", "测试消息，请忽略") //@all
	if err != nil {
		t.Error(err)
	}
}

func TestTextMessage(t *testing.T) {
	data, err := c.SendTextMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "测试消息，来自双十一，请忽略")
	if err != nil {
		t.Error(err)
	} else {
		if data.MessageID == "" {
			t.Error("Message id is empty")
		}
	}
	data2, _ := c.GetMessageReadList(data.MessageID, 0, 10)
	if len(data2.ReadUserIdList) == 0 {
		t.Error("Message Read List should not be empty")
	}
}

func TestSendOAMessage(t *testing.T) {
	msg := OAMessage{}
	msg.URL = "http://www.google.com/"
	msg.Head.Text = "头部标题"
	msg.Head.BgColor = "FFBBBBBB"
	msg.Body.Title = "正文标题"
	msg.Body.Content = "test content"
	_, err := c.SendOAMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", msg)
	if err != nil {
		t.Error(err)
	}
}

func TestDownloadAndUploadImage(t *testing.T) {
	f, err := os.Create("lADOHrf_oVxc.jpg")
	if err == nil {
		err = c.DownloadMedia("@lADOHrf_oVxc", f)
	}
	if err != nil {
		t.Error(err)
	}
	f.Close()

	f, _ = os.Open("lADOHrf_oVxc.jpg")
	defer f.Close()
	media, err := c.UploadMedia("image", "myfile.jpg", f)
	if media.MediaID == "" {
		t.Error("Upload File Failed")
	}
	t.Log("uploaded file mediaid:", media.MediaID)
	if err != nil {
		t.Error(err)
	}
	_, err = c.SendImageMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lADOHrf_oVxc")
	if err != nil {
		t.Error(err)
	}
}

func TestVoiceMessage(t *testing.T) {
	// f, _ := os.Open("/Users/hugozhu/Downloads/BlackBerry_test2_AMR-NB_Mono_12.2kbps_8000Hz.amr")
	// defer f.Close()
	// media, err := c.UploadMedia("voice", "sample.amr", f)
	// if media.MediaID == "" {
	// 	t.Error("Upload File Failed")
	// }
	// t.Log("uploaded file mediaid:", media.MediaID)
	// if err != nil {
	// 	t.Error(err)
	// }
	_, err := c.SendVoiceMessage("011217462940", "chat6a93bc1ee3b7d660d372b1b877a9de62", "@lATOHr53E84DALnDzml4wS0", "10")
	if err != nil {
		t.Error(err)
	}
}

func TestRobotMessage(t *testing.T) {
	_, err := c.SendRobotTextMessage(os.Getenv("token"), "这是一条测试消息")
	if err != nil {
		t.Error(err)
	}

	_, err = c.SendRobotMarkdownMessage(os.Getenv("token"), "测试标题", "# 杭州天气\n这是一条测试消息\n"+
		"> 9度，西北风1级，空气良89，**相对温度**73%\n\n"+
		"> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n"+
		"> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n")
	if err != nil {
		t.Error(err)
	}
}

func TestRobotAtMessage(t *testing.T) {
	_, err := c.SendRobotTextAtMessage(os.Getenv("token"), "这是一条测试消息", &RobotAtList{
		IsAtAll: true,
	})
	if err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>package godispatch_test

import (
	"fmt"
	dp "github.com/markus-wa/godispatch"
	"reflect"
	"testing"
)

type AB interface {
	foo() string
}

type A struct {
	val int
}

func (a A) foo() string {
	return string(a.val)
}

type B struct {
	val string
}

func (b B) foo() string {
	return b.val
}

type result struct {
	strVal  string
	intVal  int
	f32Val  float32
	f64Val  float64
	abVal   AB
	aVal    A
	bPtrVal *B
}

func (r result) equal(r2 result) bool {
	b := true
	b = b && r.strVal == r2.strVal
	b = b && r.intVal == r2.intVal
	b = b && r.f32Val == r2.f32Val
	b = b && r.f64Val == r2.f64Val
	if r.abVal != nil {
		if r2.abVal == nil {
			b = false
		} else {
			b = b && r.abVal.foo() == r2.abVal.foo()
		}
	}
	b = b && r.aVal.val == r2.aVal.val
	if r.bPtrVal != nil {
		b = b && r.bPtrVal == r2.bPtrVal
	}
	return b
}

func registerHandlers(d *dp.Dispatcher) *result {
	res := &result{}

	// string handler
	d.RegisterHandler(reflect.TypeOf(""), func(msg interface{}) {
		res.strVal = msg.(string)
		fmt.Println("handled string", res.strVal)
	})
	// int handler
	d.RegisterHandler(reflect.TypeOf(0), func(obj interface{}) {
		res.intVal = obj.(int)
		fmt.Println("handled int", res.intVal)
	})
	// float32 handler
	d.RegisterHandler(reflect.TypeOf(float32(0)), func(obj interface{}) {
		res.f32Val = obj.(float32)
		fmt.Println("handled float32", res.f32Val)
	})
	// float64 handler
	d.RegisterHandler(reflect.TypeOf(float64(0)), func(obj interface{}) {
		res.f64Val = obj.(float64)
		fmt.Println("handled float64", res.f64Val)
	})
	// interface handler
	d.RegisterHandler(reflect.TypeOf((*AB)(nil)).Elem(), func(obj interface{}) {
		res.abVal = obj.(AB)
		fmt.Println("handled AB", res.abVal)
	})
	// struct handler
	d.RegisterHandler(reflect.TypeOf((*A)(nil)).Elem(), func(obj interface{}) {
		res.aVal = obj.(A)
		fmt.Println("handled A", res.aVal)
	})
	// pointer handler
	d.RegisterHandler(reflect.TypeOf((*B)(nil)), func(obj interface{}) {
		res.bPtrVal = obj.(*B)
		fmt.Println("handled *B", res.bPtrVal)
	})
	return res
}

func TestString(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := "test"
	d.Dispatch(val)
	exp := result{strVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestInt(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := 13
	d.Dispatch(val)
	exp := result{intVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestFloat32(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := float32(1.23)
	d.Dispatch(val)
	exp := result{f32Val: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestFloat64(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := 9.87
	d.Dispatch(val)
	exp := result{f64Val: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructA(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := A{val: 17}
	d.Dispatch(val)
	exp := result{abVal: val, aVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructB(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := B{val: "bVal"}
	d.Dispatch(val)
	exp := result{abVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructPointer(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := &B{val: "bPtrVal"}
	d.Dispatch(val)
	exp := result{abVal: val, bPtrVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}
<commit_msg>Added benchmarks for reflect.TypeOf<commit_after>package godispatch_test

import (
	"fmt"
	dp "github.com/markus-wa/godispatch"
	"reflect"
	"testing"
)

type AB interface {
	foo() string
}

type A struct {
	val int
}

func (a A) foo() string {
	return string(a.val)
}

type B struct {
	val string
}

func (b B) foo() string {
	return b.val
}

type result struct {
	strVal  string
	intVal  int
	f32Val  float32
	f64Val  float64
	abVal   AB
	aVal    A
	bPtrVal *B
}

func (r result) equal(r2 result) bool {
	b := true
	b = b && r.strVal == r2.strVal
	b = b && r.intVal == r2.intVal
	b = b && r.f32Val == r2.f32Val
	b = b && r.f64Val == r2.f64Val
	if r.abVal != nil {
		if r2.abVal == nil {
			b = false
		} else {
			b = b && r.abVal.foo() == r2.abVal.foo()
		}
	}
	b = b && r.aVal.val == r2.aVal.val
	if r.bPtrVal != nil {
		b = b && r.bPtrVal == r2.bPtrVal
	}
	return b
}

func registerHandlers(d *dp.Dispatcher) *result {
	res := &result{}

	// string handler
	d.RegisterHandler(reflect.TypeOf((*string)(nil)).Elem(), func(msg interface{}) {
		res.strVal = msg.(string)
		fmt.Println("handled string", res.strVal)
	})
	// int handler
	d.RegisterHandler(reflect.TypeOf((*int)(nil)).Elem(), func(obj interface{}) {
		res.intVal = obj.(int)
		fmt.Println("handled int", res.intVal)
	})
	// float32 handler
	d.RegisterHandler(reflect.TypeOf((*float32)(nil)).Elem(), func(obj interface{}) {
		res.f32Val = obj.(float32)
		fmt.Println("handled float32", res.f32Val)
	})
	// float64 handler
	d.RegisterHandler(reflect.TypeOf((*float64)(nil)).Elem(), func(obj interface{}) {
		res.f64Val = obj.(float64)
		fmt.Println("handled float64", res.f64Val)
	})
	// interface handler
	d.RegisterHandler(reflect.TypeOf((*AB)(nil)).Elem(), func(obj interface{}) {
		res.abVal = obj.(AB)
		fmt.Println("handled AB", res.abVal)
	})
	// struct handler
	d.RegisterHandler(reflect.TypeOf((*A)(nil)).Elem(), func(obj interface{}) {
		res.aVal = obj.(A)
		fmt.Println("handled A", res.aVal)
	})
	// pointer handler (no reflect.Type.Elem() call)
	d.RegisterHandler(reflect.TypeOf((*B)(nil)), func(obj interface{}) {
		res.bPtrVal = obj.(*B)
		fmt.Println("handled *B", res.bPtrVal)
	})
	return res
}

func TestString(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := "test"
	d.Dispatch(val)
	exp := result{strVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestInt(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := 13
	d.Dispatch(val)
	exp := result{intVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestFloat32(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := float32(1.23)
	d.Dispatch(val)
	exp := result{f32Val: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestFloat64(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := 9.87
	d.Dispatch(val)
	exp := result{f64Val: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructA(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := A{val: 17}
	d.Dispatch(val)
	exp := result{abVal: val, aVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructB(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := B{val: "bVal"}
	d.Dispatch(val)
	exp := result{abVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

func TestStructPointer(t *testing.T) {
	d := dp.Dispatcher{}
	res := registerHandlers(&d)

	val := &B{val: "bPtrVal"}
	d.Dispatch(val)
	exp := result{abVal: val, bPtrVal: val}
	if !res.equal(exp) {
		fmt.Println("expected ", exp, "got", res)
		t.Fail()
	}
}

// Benchmarks showing that you should always use reflect.TypeOf((*type)(nil)).Elem() instead of creating instances
// It gives 2x-3x performance

func BenchmarkTypeOfEmptyString(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf("")
	}
}

func BenchmarkTypeOfStringPtrElem(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf((*string)(nil)).Elem()
	}
}

func BenchmarkTypeOfZeroInt(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf(0)
	}
}

func BenchmarkTypeOfIntPtrElem(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf((*int)(nil)).Elem()
	}
}

func BenchmarkTypeOfEmptyStruct(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf(A{})
	}
}

func BenchmarkTypeOfStructPtrElem(b *testing.B) {
	for i := 0; i < b.N; i++ {
		_ = reflect.TypeOf((*A)(nil)).Elem()
	}
}
<|endoftext|>


<commit_before>package golligator

import "testing"

func TestNewGolligator(t *testing.T) {
	config := &Config{Port: 8081, ServerUrl: "http://localhost:8081"}
	g := NewGolligator(config)
	if g.config.Port != config.Port {
		t.Fail()
	}
	if g.client.serverUrl != config.ServerUrl {
		t.Fail()
	}
	if g.inputEvents == nil {
		t.Fail()
	}
}

func TestListenAndSend(t *testing.T) {
	config := &Config{Port: 8081, ServerUrl: "http://localhost:8081"}
	g := NewGolligator(config)
	events := g.Listen()
	message := createAddFrameworkEvent("1", "user", "framework")
	g.Send(&Event{Type: "AddFramework", Message: message})
	event := <-events
	if event.Message.(*AddFramework).GetFrameworkId().GetValue() != "1" {
		t.Fail()
	}
}
<commit_msg>Add benchmark.<commit_after>package golligator

import (
	"os"
	"testing"
)

var events chan *Event
var g *Golligator

func TestMain(m *testing.M) {
	config := &Config{Port: 8081, ServerUrl: "http://localhost:8081"}
	g = NewGolligator(config)
	events = g.Listen()
	os.Exit(m.Run())
}

func TestNewGolligator(t *testing.T) {
	config := &Config{Port: 8081, ServerUrl: "http://localhost:8081"}
	g := NewGolligator(config)
	if g.config.Port != config.Port {
		t.Fail()
	}
	if g.client.serverUrl != config.ServerUrl {
		t.Fail()
	}
	if g.inputEvents == nil {
		t.Fail()
	}
}

func TestListenAndSend(t *testing.T) {
	message := createAddFrameworkEvent("1", "user", "framework")
	g.Send(&Event{Type: "AddFramework", Message: message})
	event := <-events
	if event.Message.(*AddFramework).GetFrameworkId().GetValue() != "1" {
		t.Fail()
	}
}

func BenchmarkSendAndReceive(b *testing.B) {
	message := createAddFrameworkEvent("1", "user", "framework")
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		g.Send(&Event{Type: "AddFramework", Message: message})
		<-events
	}
}
<|endoftext|>


<commit_before>package eyego

import (
	"net/http"
	"fmt"
	"encoding/json"
	"bytes"
	"io/ioutil"
	"errors"
)

var (
	LOC_BASE_URL = "https://www.googleapis.com/geolocation/v1/geolocate"
)

type WifiAccessPoints struct {
	AccessPoints []AccessPointSightingInfo `json:"wifiAccessPoints"`
}

type Location struct {
	Latitude float64 `json:"lat"`
	Longitude float64 `json:"lng"`
}

type LocationResult struct {
	Location Location `json:"location"`
	Accuracy float64 `json:"accuracy"`

}

func GPSCoordinates(aps []AccessPointSightingInfo) (lr LocationResult, err error) {

	req, err := json.Marshal(WifiAccessPoints{AccessPoints:aps})
	if err != nil { return }

	resp, err := http.Post(fmt.Sprintf("%s?key=%s", LOC_BASE_URL, Config().GoogleAPIKey), "application/json", bytes.NewBuffer(req))
	if err != nil { return }

	if resp.StatusCode != 200 {
		body, _ := ioutil.ReadAll(resp.Body)
		err = errors.New(fmt.Sprintf("Request failed: %s", body))
		return
	}

	defer resp.Body.Close()

	lr = LocationResult{}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil { return }

	err = json.Unmarshal(bytes, &lr)
	if err != nil { return }

	return
}
<commit_msg>added considerIp<commit_after>package eyego

import (
	"net/http"
	"fmt"
	"encoding/json"
	"bytes"
	"io/ioutil"
	"errors"
)

var (
	LOC_BASE_URL = "https://www.googleapis.com/geolocation/v1/geolocate"
)

type WifiAccessPoints struct {
	AccessPoints []AccessPointSightingInfo `json:"wifiAccessPoints"`
	ConsiderIp bool `json:"considerIp"`
}

type Location struct {
	Latitude float64 `json:"lat"`
	Longitude float64 `json:"lng"`
}

type LocationResult struct {
	Location Location `json:"location"`
	Accuracy float64 `json:"accuracy"`

}

func GPSCoordinates(aps []AccessPointSightingInfo) (lr LocationResult, err error) {

	req, err := json.Marshal(WifiAccessPoints{AccessPoints:aps,ConsiderIp:false})
	if err != nil { return }

	resp, err := http.Post(fmt.Sprintf("%s?key=%s", LOC_BASE_URL, Config().GoogleAPIKey), "application/json", bytes.NewBuffer(req))
	if err != nil { return }

	if resp.StatusCode != 200 {
		body, _ := ioutil.ReadAll(resp.Body)
		err = errors.New(fmt.Sprintf("Request failed: %s", body))
		return
	}

	defer resp.Body.Close()

	lr = LocationResult{}

	bytes, err := ioutil.ReadAll(resp.Body)
	if err != nil { return }

	err = json.Unmarshal(bytes, &lr)
	if err != nil { return }

	return
}
<|endoftext|>


<commit_before>package policy

import (
	"fmt"
	"strings"

	"code.cloudfoundry.org/lager"
	"github.com/jessevdk/go-flags"
)

const ActionUsingImage = "UsingImage"

type PolicyCheckNotPass struct{}

func (e PolicyCheckNotPass) Error() string {
	return "policy check not pass"
}

type Filter struct {
	HttpMethods   []string `long:"policy-check-filter-http-method" description:"API http method to go through policy check"`
	Actions       []string `long:"policy-check-filter-action" description:"Actions in the list will go through policy check"`
	ActionsToSkip []string `long:"policy-check-filter-action-skip" default:"UsingImage" description:"Actions the list will not go through policy check"`
}

func (f Filter) normalize() Filter {
	if len(f.HttpMethods) == 1 {
		f.HttpMethods = strings.Split(f.HttpMethods[0], ",")
	}

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

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

	return f
}

type PolicyCheckInput struct {
	Service        string      `json:"service"`
	ClusterName    string      `json:"cluster_name"`
	ClusterVersion string      `json:"cluster_version"`
	HttpMethod     string      `json:"http_method,omitempty"`
	Action         string      `json:"action"`
	User           string      `json:"user"`
	Team           string      `json:"team,omitempty"`
	Pipeline       string      `json:"pipeline,omitempty"`
	Data           interface{} `json:"data,omitempty"`
}

//go:generate counterfeiter . Agent

// Agent should be implemented by policy agents.
type Agent interface {
	// Check returns true if passes policy check. If not goes through policy
	// check, just return true.
	Check(PolicyCheckInput) (bool, error)
}

//go:generate counterfeiter . AgentFactory

type AgentFactory interface {
	Description() string
	IsConfigured() bool
	NewAgent(lager.Logger) (Agent, error)
}

var agentFactories []AgentFactory

func RegisterAgent(factory AgentFactory) {
	agentFactories = append(agentFactories, factory)
}

func WireCheckers(group *flags.Group) {
	for _, factory := range agentFactories {
		_, err := group.AddGroup(fmt.Sprintf("Policy Check Agent (%s)", factory.Description()), "", factory)
		if err != nil {
			panic(err)
		}
	}
}

var (
	clusterName    string
	clusterVersion string
)

func Initialize(logger lager.Logger, cluster string, version string, filter Filter) (*Checker, error) {
	logger.Debug("policy-checker-initialize")

	clusterName = cluster
	clusterVersion = version

	var checkerDescriptions []string
	for _, factory := range agentFactories {
		if factory.IsConfigured() {
			checkerDescriptions = append(checkerDescriptions, factory.Description())
		}
	}
	if len(checkerDescriptions) > 1 {
		return nil, fmt.Errorf("Multiple policy checker configured: %s", strings.Join(checkerDescriptions, ", "))
	}

	for _, factory := range agentFactories {
		if factory.IsConfigured() {
			agent, err := factory.NewAgent(logger.Session("policy-checker"))
			if err != nil {
				return nil, err
			}
			return &Checker{
				filter: filter.normalize(),
				agent:  agent,
			}, nil
		}
	}

	// No policy checker configured.
	return nil, nil
}

type Checker struct {
	filter Filter
	agent  Agent
}

func (c *Checker) ShouldCheckHttpMethod(method string) bool {
	return inArray(c.filter.HttpMethods, method)
}

func (c *Checker) ShouldCheckAction(action string) bool {
	return inArray(c.filter.Actions, action)
}

func (c *Checker) ShouldSkipAction(action string) bool {
	return inArray(c.filter.ActionsToSkip, action)
}

func inArray(array []string, target string) bool {
	found := false
	for _, ele := range array {
		if ele == target {
			found = true
			break
		}
	}
	return found
}

func (c *Checker) Check(input PolicyCheckInput) (bool, error) {
	input.Service = "concourse"
	input.ClusterName = clusterName
	input.ClusterVersion = clusterVersion
	return c.agent.Check(input)
}
<commit_msg>added an experiment warning log.<commit_after>package policy

import (
	"fmt"
	"strings"

	"code.cloudfoundry.org/lager"
	"github.com/jessevdk/go-flags"
)

const ActionUsingImage = "UsingImage"

type PolicyCheckNotPass struct{}

func (e PolicyCheckNotPass) Error() string {
	return "policy check not pass"
}

type Filter struct {
	HttpMethods   []string `long:"policy-check-filter-http-method" description:"API http method to go through policy check"`
	Actions       []string `long:"policy-check-filter-action" description:"Actions in the list will go through policy check"`
	ActionsToSkip []string `long:"policy-check-filter-action-skip" default:"UsingImage" description:"Actions the list will not go through policy check"`
}

func (f Filter) normalize() Filter {
	if len(f.HttpMethods) == 1 {
		f.HttpMethods = strings.Split(f.HttpMethods[0], ",")
	}

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

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

	return f
}

type PolicyCheckInput struct {
	Service        string      `json:"service"`
	ClusterName    string      `json:"cluster_name"`
	ClusterVersion string      `json:"cluster_version"`
	HttpMethod     string      `json:"http_method,omitempty"`
	Action         string      `json:"action"`
	User           string      `json:"user"`
	Team           string      `json:"team,omitempty"`
	Pipeline       string      `json:"pipeline,omitempty"`
	Data           interface{} `json:"data,omitempty"`
}

//go:generate counterfeiter . Agent

// Agent should be implemented by policy agents.
type Agent interface {
	// Check returns true if passes policy check. If not goes through policy
	// check, just return true.
	Check(PolicyCheckInput) (bool, error)
}

//go:generate counterfeiter . AgentFactory

type AgentFactory interface {
	Description() string
	IsConfigured() bool
	NewAgent(lager.Logger) (Agent, error)
}

var agentFactories []AgentFactory

func RegisterAgent(factory AgentFactory) {
	agentFactories = append(agentFactories, factory)
}

func WireCheckers(group *flags.Group) {
	for _, factory := range agentFactories {
		_, err := group.AddGroup(fmt.Sprintf("Policy Check Agent (%s)", factory.Description()), "", factory)
		if err != nil {
			panic(err)
		}
	}
}

var (
	clusterName    string
	clusterVersion string
)

func Initialize(logger lager.Logger, cluster string, version string, filter Filter) (*Checker, error) {
	logger.Debug("policy-checker-initialize")

	clusterName = cluster
	clusterVersion = version

	var checkerDescriptions []string
	for _, factory := range agentFactories {
		if factory.IsConfigured() {
			checkerDescriptions = append(checkerDescriptions, factory.Description())
		}
	}
	if len(checkerDescriptions) > 1 {
		return nil, fmt.Errorf("Multiple policy checker configured: %s", strings.Join(checkerDescriptions, ", "))
	}

	for _, factory := range agentFactories {
		if factory.IsConfigured() {
			agent, err := factory.NewAgent(logger.Session("policy-checker"))
			if err != nil {
				return nil, err
			}

			logger.Info("warning-experiment-policy-check",
				lager.Data{"rfc": "https://github.com/concourse/rfcs/pull/41"})

			return &Checker{
				filter: filter.normalize(),
				agent:  agent,
			}, nil
		}
	}

	// No policy checker configured.
	return nil, nil
}

type Checker struct {
	filter Filter
	agent  Agent
}

func (c *Checker) ShouldCheckHttpMethod(method string) bool {
	return inArray(c.filter.HttpMethods, method)
}

func (c *Checker) ShouldCheckAction(action string) bool {
	return inArray(c.filter.Actions, action)
}

func (c *Checker) ShouldSkipAction(action string) bool {
	return inArray(c.filter.ActionsToSkip, action)
}

func inArray(array []string, target string) bool {
	found := false
	for _, ele := range array {
		if ele == target {
			found = true
			break
		}
	}
	return found
}

func (c *Checker) Check(input PolicyCheckInput) (bool, error) {
	input.Service = "concourse"
	input.ClusterName = clusterName
	input.ClusterVersion = clusterVersion
	return c.agent.Check(input)
}
<|endoftext|>


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

package ebiten

import (
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/clock"
	"github.com/hajimehoshi/ebiten/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/shareable"
	"github.com/hajimehoshi/ebiten/internal/ui"
	"github.com/hajimehoshi/ebiten/internal/web"
)

func newGraphicsContext(f func(*Image) error) *graphicsContext {
	return &graphicsContext{
		f: f,
	}
}

type graphicsContext struct {
	f            func(*Image) error
	offscreen    *Image
	screen       *Image
	screenWidth  int
	screenHeight int
	screenScale  float64
	initialized  bool
	invalidated  bool // browser only
	offsetX      float64
	offsetY      float64
}

func (c *graphicsContext) Invalidate() {
	// Note that this is called only on browsers so far.
	// TODO: On mobiles, this function is not called and instead IsTexture is called
	// to detect if the context is lost. This is simple but might not work on some platforms.
	// Should Invalidate be called explicitly?
	c.invalidated = true
}

func (c *graphicsContext) SetSize(screenWidth, screenHeight int, screenScale float64) {
	c.screenScale = screenScale

	if c.screen != nil {
		_ = c.screen.Dispose()
	}
	if c.offscreen != nil {
		_ = c.offscreen.Dispose()
	}
	c.offscreen = newVolatileImage(screenWidth, screenHeight)

	w := int(math.Ceil(float64(screenWidth) * screenScale))
	h := int(math.Ceil(float64(screenHeight) * screenScale))
	px0, py0, px1, py1 := ui.ScreenPadding()
	c.screen = newImageWithScreenFramebuffer(w+int(math.Ceil(px0+px1)), h+int(math.Ceil(py0+py1)))
	c.screenWidth = w
	c.screenHeight = h

	c.offsetX = px0
	c.offsetY = py0
}

func (c *graphicsContext) initializeIfNeeded() error {
	if !c.initialized {
		if err := shareable.InitializeGraphicsDriverState(); err != nil {
			return err
		}
		c.initialized = true
	}
	if err := c.restoreIfNeeded(); err != nil {
		return err
	}
	return nil
}

func (c *graphicsContext) Update(afterFrameUpdate func()) error {
	tps := int(MaxTPS())
	updateCount := clock.Update(tps)

	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err := c.initializeIfNeeded(); err != nil {
		return err
	}
	for i := 0; i < updateCount; i++ {
		c.offscreen.Fill(color.Transparent)
		// Mipmap images should be disposed by fill.

		setDrawingSkipped(i < updateCount-1)
		if err := hooks.RunBeforeUpdateHooks(); err != nil {
			return err
		}
		if err := c.f(c.offscreen); err != nil {
			return err
		}
		afterFrameUpdate()
	}

	// TODO: This clear is needed only when the screen size is changed.
	if c.offsetX > 0 || c.offsetY > 0 {
		c.screen.Clear()
	}

	op := &DrawImageOptions{}

	switch graphicscommand.Driver().VDirection() {
	case graphicsdriver.VDownward:
		// c.screen is special: its Y axis is down to up,
		// and the origin point is lower left.
		op.GeoM.Scale(c.screenScale, -c.screenScale)
		// Make the screen height an even number to fit the upper side of the screen (#662).
		op.GeoM.Translate(0, float64((c.screenHeight+1)/2*2))
	case graphicsdriver.VUpward:
		op.GeoM.Scale(c.screenScale, c.screenScale)
	default:
		panic("not reached")
	}

	op.GeoM.Translate(c.offsetX, c.offsetY)
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if c.screenScale >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	_ = c.screen.DrawImage(c.offscreen, op)

	shareable.ResolveStaleImages()

	if err := shareable.Error(); err != nil {
		return err
	}
	return nil
}

func (c *graphicsContext) needsRestoring() (bool, error) {
	if web.IsBrowser() {
		return c.invalidated, nil
	}
	return c.offscreen.mipmap.original().IsInvalidated()
}

func (c *graphicsContext) restoreIfNeeded() error {
	if !shareable.IsRestoringEnabled() {
		return nil
	}
	r, err := c.needsRestoring()
	if err != nil {
		return err
	}
	if !r {
		return nil
	}
	if err := shareable.Restore(); err != nil {
		return err
	}
	c.invalidated = false
	return nil
}
<commit_msg>Revert: graphics: Use an even number for screen height offset<commit_after>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebiten

import (
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/clock"
	"github.com/hajimehoshi/ebiten/internal/graphicscommand"
	"github.com/hajimehoshi/ebiten/internal/graphicsdriver"
	"github.com/hajimehoshi/ebiten/internal/hooks"
	"github.com/hajimehoshi/ebiten/internal/shareable"
	"github.com/hajimehoshi/ebiten/internal/ui"
	"github.com/hajimehoshi/ebiten/internal/web"
)

func newGraphicsContext(f func(*Image) error) *graphicsContext {
	return &graphicsContext{
		f: f,
	}
}

type graphicsContext struct {
	f            func(*Image) error
	offscreen    *Image
	screen       *Image
	screenWidth  int
	screenHeight int
	screenScale  float64
	initialized  bool
	invalidated  bool // browser only
	offsetX      float64
	offsetY      float64
}

func (c *graphicsContext) Invalidate() {
	// Note that this is called only on browsers so far.
	// TODO: On mobiles, this function is not called and instead IsTexture is called
	// to detect if the context is lost. This is simple but might not work on some platforms.
	// Should Invalidate be called explicitly?
	c.invalidated = true
}

func (c *graphicsContext) SetSize(screenWidth, screenHeight int, screenScale float64) {
	c.screenScale = screenScale

	if c.screen != nil {
		_ = c.screen.Dispose()
	}
	if c.offscreen != nil {
		_ = c.offscreen.Dispose()
	}
	c.offscreen = newVolatileImage(screenWidth, screenHeight)

	w := int(math.Ceil(float64(screenWidth) * screenScale))
	h := int(math.Ceil(float64(screenHeight) * screenScale))
	px0, py0, px1, py1 := ui.ScreenPadding()
	c.screen = newImageWithScreenFramebuffer(w+int(math.Ceil(px0+px1)), h+int(math.Ceil(py0+py1)))
	c.screenWidth = w
	c.screenHeight = h

	c.offsetX = px0
	c.offsetY = py0
}

func (c *graphicsContext) initializeIfNeeded() error {
	if !c.initialized {
		if err := shareable.InitializeGraphicsDriverState(); err != nil {
			return err
		}
		c.initialized = true
	}
	if err := c.restoreIfNeeded(); err != nil {
		return err
	}
	return nil
}

func (c *graphicsContext) Update(afterFrameUpdate func()) error {
	tps := int(MaxTPS())
	updateCount := clock.Update(tps)

	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err := c.initializeIfNeeded(); err != nil {
		return err
	}
	for i := 0; i < updateCount; i++ {
		c.offscreen.Fill(color.Transparent)
		// Mipmap images should be disposed by fill.

		setDrawingSkipped(i < updateCount-1)
		if err := hooks.RunBeforeUpdateHooks(); err != nil {
			return err
		}
		if err := c.f(c.offscreen); err != nil {
			return err
		}
		afterFrameUpdate()
	}

	// TODO: This clear is needed only when the screen size is changed.
	if c.offsetX > 0 || c.offsetY > 0 {
		c.screen.Clear()
	}

	op := &DrawImageOptions{}

	switch graphicscommand.Driver().VDirection() {
	case graphicsdriver.VDownward:
		// c.screen is special: its Y axis is down to up,
		// and the origin point is lower left.
		op.GeoM.Scale(c.screenScale, -c.screenScale)
		op.GeoM.Translate(0, float64(c.screenHeight))
	case graphicsdriver.VUpward:
		op.GeoM.Scale(c.screenScale, c.screenScale)
	default:
		panic("not reached")
	}

	op.GeoM.Translate(c.offsetX, c.offsetY)
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if c.screenScale >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	_ = c.screen.DrawImage(c.offscreen, op)

	shareable.ResolveStaleImages()

	if err := shareable.Error(); err != nil {
		return err
	}
	return nil
}

func (c *graphicsContext) needsRestoring() (bool, error) {
	if web.IsBrowser() {
		return c.invalidated, nil
	}
	return c.offscreen.mipmap.original().IsInvalidated()
}

func (c *graphicsContext) restoreIfNeeded() error {
	if !shareable.IsRestoringEnabled() {
		return nil
	}
	r, err := c.needsRestoring()
	if err != nil {
		return err
	}
	if !r {
		return nil
	}
	if err := shareable.Restore(); err != nil {
		return err
	}
	c.invalidated = false
	return nil
}
<|endoftext|>