<commit_before>/*
Copyright 2018 The OpenEBS Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package startcontroller

import (
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/openebs/maya/cmd/cstor-volume-mgmt/controller/common"
	volumecontroller "github.com/openebs/maya/cmd/cstor-volume-mgmt/controller/volume-controller"
	"github.com/openebs/maya/cmd/cstor-volume-mgmt/volume"

	//clientset "github.com/openebs/maya/pkg/client/clientset/versioned"
	clientset "github.com/openebs/maya/pkg/client/generated/clientset/internalclientset"
	//informers "github.com/openebs/maya/pkg/client/informers/externalversions"
	informers "github.com/openebs/maya/pkg/client/generated/informer/externalversions"
	"github.com/openebs/maya/pkg/signals"
	"github.com/openebs/maya/pkg/util"
)

const (
	// NumThreads defines number of worker threads for resource watcher.
	NumThreads = 1
	// NumRoutinesThatFollow is for handling golang waitgroups.
	NumRoutinesThatFollow = 1
)

// StartControllers instantiates CStorVolume controllers
// and watches them.
func StartControllers(kubeconfig string) {
	// Set up signals to handle the first shutdown signal gracefully.
	stopCh := signals.SetupSignalHandler()

	cfg, err := getClusterConfig(kubeconfig)
	if err != nil {
		glog.Fatalf(err.Error())
	}

	kubeClient, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("Error building kubernetes clientset: %s", err.Error())
	}

	openebsClient, err := clientset.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("Error building openebs clientset: %s", err.Error())
	}

	volume.FileOperatorVar = util.RealFileOperator{}

	volume.UnixSockVar = util.RealUnixSock{}

	// Blocking call for checking status of istgt running in cstor-volume container.
	volume.CheckForIscsi()

	// Blocking call for checking status of CStorVolume CR.
	common.CheckForCStorVolumeCRD(openebsClient)

	//NewInformer returns a cache.Store and a controller for populating the store
	// while also providing event notifications. It’s basically a controller with some
	//boilerplate code to sync events from the FIFO queue to the downstream store.
	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30)
	openebsInformerFactory := informers.NewSharedInformerFactory(openebsClient, time.Second*30)

	cStorVolumeController := volumecontroller.NewCStorVolumeController(kubeClient, openebsClient, kubeInformerFactory,
		openebsInformerFactory)

	go startSharedInformerFactory(kubeInformerFactory, stopCh)
	go startExternalVersionsInformerFactory(openebsInformerFactory, stopCh)

	// Waitgroup for starting volume controller goroutines.
	var wg sync.WaitGroup
	wg.Add(NumRoutinesThatFollow)

	// Run controller for cStorVolume.
	go func() {
		if err = cStorVolumeController.Run(NumThreads, stopCh); err != nil {
			glog.Fatalf("Error running CStorVolume controller: %s", err.Error())
		}
		wg.Done()
	}()
	wg.Wait()
}

func startSharedInformerFactory(factory kubeinformers.SharedInformerFactory, stopCh <-chan struct{}) {
	for {
		factory.Start(stopCh)
	}
}

func startExternalVersionsInformerFactory(factory informers.SharedInformerFactory, stopCh <-chan struct{}) {
	for {
		factory.Start(stopCh)
	}
}

// GetClusterConfig return the config for k8s.
func getClusterConfig(kubeconfig string) (*rest.Config, error) {
	var masterURL string
	cfg, err := rest.InClusterConfig()
	if err != nil {
		glog.Errorf("Failed to get k8s Incluster config. %+v", err)
		if len(kubeconfig) == 0 {
			return nil, fmt.Errorf("kubeconfig is empty: %v", err.Error())
		}
		cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
		if err != nil {
			return nil, fmt.Errorf("Error building kubeconfig: %s", err.Error())
		}
	}
	return cfg, err
}
<commit_msg>fix(cstor-volume-mgmt): high cpu usage caused by for {} loop<commit_after>/*
Copyright 2018 The OpenEBS Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package startcontroller

import (
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	kubeinformers "k8s.io/client-go/informers"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/openebs/maya/cmd/cstor-volume-mgmt/controller/common"
	volumecontroller "github.com/openebs/maya/cmd/cstor-volume-mgmt/controller/volume-controller"
	"github.com/openebs/maya/cmd/cstor-volume-mgmt/volume"

	//clientset "github.com/openebs/maya/pkg/client/clientset/versioned"
	clientset "github.com/openebs/maya/pkg/client/generated/clientset/internalclientset"
	//informers "github.com/openebs/maya/pkg/client/informers/externalversions"
	informers "github.com/openebs/maya/pkg/client/generated/informer/externalversions"
	"github.com/openebs/maya/pkg/signals"
	"github.com/openebs/maya/pkg/util"
)

const (
	// NumThreads defines number of worker threads for resource watcher.
	NumThreads = 1
	// NumRoutinesThatFollow is for handling golang waitgroups.
	NumRoutinesThatFollow = 1
)

// StartControllers instantiates CStorVolume controllers
// and watches them.
func StartControllers(kubeconfig string) {
	// Set up signals to handle the first shutdown signal gracefully.
	stopCh := signals.SetupSignalHandler()

	cfg, err := getClusterConfig(kubeconfig)
	if err != nil {
		glog.Fatalf(err.Error())
	}

	kubeClient, err := kubernetes.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("Error building kubernetes clientset: %s", err.Error())
	}

	openebsClient, err := clientset.NewForConfig(cfg)
	if err != nil {
		glog.Fatalf("Error building openebs clientset: %s", err.Error())
	}

	volume.FileOperatorVar = util.RealFileOperator{}

	volume.UnixSockVar = util.RealUnixSock{}

	// Blocking call for checking status of istgt running in cstor-volume container.
	volume.CheckForIscsi()

	// Blocking call for checking status of CStorVolume CR.
	common.CheckForCStorVolumeCRD(openebsClient)

	//NewInformer returns a cache.Store and a controller for populating the store
	// while also providing event notifications. It’s basically a controller with some
	//boilerplate code to sync events from the FIFO queue to the downstream store.
	kubeInformerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, time.Second*30)
	openebsInformerFactory := informers.NewSharedInformerFactory(openebsClient, time.Second*30)

	cStorVolumeController := volumecontroller.NewCStorVolumeController(kubeClient, openebsClient, kubeInformerFactory,
		openebsInformerFactory)

	go kubeInformerFactory.Start(stopCh)
	go openebsInformerFactory.Start(stopCh)

	// Waitgroup for starting volume controller goroutines.
	var wg sync.WaitGroup
	wg.Add(NumRoutinesThatFollow)

	// Run controller for cStorVolume.
	go func() {
		if err = cStorVolumeController.Run(NumThreads, stopCh); err != nil {
			glog.Fatalf("Error running CStorVolume controller: %s", err.Error())
		}
		wg.Done()
	}()
	wg.Wait()
}

// GetClusterConfig return the config for k8s.
func getClusterConfig(kubeconfig string) (*rest.Config, error) {
	var masterURL string
	cfg, err := rest.InClusterConfig()
	if err != nil {
		glog.Errorf("Failed to get k8s Incluster config. %+v", err)
		if len(kubeconfig) == 0 {
			return nil, fmt.Errorf("kubeconfig is empty: %v", err.Error())
		}
		cfg, err = clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
		if err != nil {
			return nil, fmt.Errorf("Error building kubeconfig: %s", err.Error())
		}
	}
	return cfg, err
}
<|endoftext|>


<commit_before>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resolver_test

import (
	"errors"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	"launchpad.net/tomb"

	"github.com/juju/juju/testing"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/uniter/operation"
	"github.com/juju/juju/worker/uniter/remotestate"
	"github.com/juju/juju/worker/uniter/resolver"
)

type LoopSuite struct {
	testing.BaseSuite

	resolver  resolver.Resolver
	watcher   *mockRemoteStateWatcher
	opFactory *mockOpFactory
	executor  *mockOpExecutor
	charmURL  *charm.URL
	dying     chan struct{}
	onIdle    func() error
}

var _ = gc.Suite(&LoopSuite{})

func (s *LoopSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.resolver = resolver.ResolverFunc(func(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) {
		return nil, resolver.ErrNoOperation
	})
	s.watcher = &mockRemoteStateWatcher{
		changes: make(chan struct{}, 1),
	}
	s.opFactory = &mockOpFactory{}
	s.executor = &mockOpExecutor{}
	s.charmURL = charm.MustParseURL("cs:trusty/mysql")
	s.dying = make(chan struct{})
}

func (s *LoopSuite) loop() (resolver.LocalState, error) {
	return resolver.Loop(resolver.LoopConfig{
		Resolver: s.resolver,
		Factory:  s.opFactory,
		Watcher:  s.watcher,
		Executor: s.executor,
		UpdateStatusChannel: func() <-chan time.Time {
			// TODO(axw) test update status channel
			return nil
		},
		CharmURL:       s.charmURL,
		Dying:          s.dying,
		OnIdle:         s.onIdle,
		CharmDirLocker: &mockCharmDirLocker{},
	})
}

func (s *LoopSuite) TestDying(c *gc.C) {
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
}

func (s *LoopSuite) TestOnIdle(c *gc.C) {
	onIdleCh := make(chan interface{}, 1)
	s.onIdle = func() error {
		onIdleCh <- nil
		return nil
	}

	done := make(chan interface{}, 1)
	go func() {
		_, err := s.loop()
		done <- err
	}()

	waitChannel(c, onIdleCh, "waiting for onIdle")
	s.watcher.changes <- struct{}{}
	waitChannel(c, onIdleCh, "waiting for onIdle")
	close(s.dying)

	err := waitChannel(c, done, "waiting for loop to exit")
	c.Assert(err, gc.Equals, tomb.ErrDying)

	select {
	case <-onIdleCh:
		c.Fatal("unexpected onIdle call")
	default:
	}
}

func (s *LoopSuite) TestOnIdleError(c *gc.C) {
	s.onIdle = func() error {
		return errors.New("onIdle failed")
	}
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "onIdle failed")
}

func (s *LoopSuite) TestErrWaitingNoOnIdle(c *gc.C) {
	var onIdleCalled bool
	s.onIdle = func() error {
		onIdleCalled = true
		return nil
	}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, resolver.ErrWaiting
	})
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(onIdleCalled, jc.IsFalse)
}

func (s *LoopSuite) TestInitialFinalLocalState(c *gc.C) {
	var local resolver.LocalState
	s.resolver = resolver.ResolverFunc(func(
		l resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		local = l
		return nil, resolver.ErrNoOperation
	})

	close(s.dying)
	lastLocal, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(local, jc.DeepEquals, resolver.LocalState{
		CharmURL:         s.charmURL,
		CompletedActions: map[string]struct{}{},
	})
	c.Assert(lastLocal, jc.DeepEquals, local)
}

func (s *LoopSuite) TestLoop(c *gc.C) {
	var resolverCalls int
	theOp := &mockOp{}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		resolverCalls++
		switch resolverCalls {
		// On the first call, return an operation.
		case 1:
			return theOp, nil
		// On the second call, simulate having
		// no operations to perform, at which
		// point we'll wait for a remote state
		// change.
		case 2:
			s.watcher.changes <- struct{}{}
			break
		// On the third call, kill the loop.
		case 3:
			close(s.dying)
			break
		}
		return nil, resolver.ErrNoOperation
	})

	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(resolverCalls, gc.Equals, 3)
	s.executor.CheckCallNames(c, "State", "Run", "State", "State")
	c.Assert(s.executor.Calls()[1].Args, jc.SameContents, []interface{}{theOp})
}

func (s *LoopSuite) TestRunFails(c *gc.C) {
	s.executor.SetErrors(errors.New("Run fails"))
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return mockOp{}, nil
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "Run fails")
}

func (s *LoopSuite) TestNextOpFails(c *gc.C) {
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, errors.New("NextOp fails")
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "NextOp fails")
}

func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} {
	select {
	case v := <-ch:
		return v
	case <-time.After(coretesting.LongWait):
		c.Fatalf("timed out " + activity)
		panic("unreachable")
	}
}
<commit_msg>Update stub tests to take into account extra local state lookup.<commit_after>// Copyright 2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package resolver_test

import (
	"errors"
	"time"

	jc "github.com/juju/testing/checkers"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/charm.v5"
	"launchpad.net/tomb"

	"github.com/juju/juju/testing"
	coretesting "github.com/juju/juju/testing"
	"github.com/juju/juju/worker/uniter/operation"
	"github.com/juju/juju/worker/uniter/remotestate"
	"github.com/juju/juju/worker/uniter/resolver"
)

type LoopSuite struct {
	testing.BaseSuite

	resolver  resolver.Resolver
	watcher   *mockRemoteStateWatcher
	opFactory *mockOpFactory
	executor  *mockOpExecutor
	charmURL  *charm.URL
	dying     chan struct{}
	onIdle    func() error
}

var _ = gc.Suite(&LoopSuite{})

func (s *LoopSuite) SetUpTest(c *gc.C) {
	s.BaseSuite.SetUpTest(c)
	s.resolver = resolver.ResolverFunc(func(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) {
		return nil, resolver.ErrNoOperation
	})
	s.watcher = &mockRemoteStateWatcher{
		changes: make(chan struct{}, 1),
	}
	s.opFactory = &mockOpFactory{}
	s.executor = &mockOpExecutor{}
	s.charmURL = charm.MustParseURL("cs:trusty/mysql")
	s.dying = make(chan struct{})
}

func (s *LoopSuite) loop() (resolver.LocalState, error) {
	return resolver.Loop(resolver.LoopConfig{
		Resolver: s.resolver,
		Factory:  s.opFactory,
		Watcher:  s.watcher,
		Executor: s.executor,
		UpdateStatusChannel: func() <-chan time.Time {
			// TODO(axw) test update status channel
			return nil
		},
		CharmURL:       s.charmURL,
		Dying:          s.dying,
		OnIdle:         s.onIdle,
		CharmDirLocker: &mockCharmDirLocker{},
	})
}

func (s *LoopSuite) TestDying(c *gc.C) {
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
}

func (s *LoopSuite) TestOnIdle(c *gc.C) {
	onIdleCh := make(chan interface{}, 1)
	s.onIdle = func() error {
		onIdleCh <- nil
		return nil
	}

	done := make(chan interface{}, 1)
	go func() {
		_, err := s.loop()
		done <- err
	}()

	waitChannel(c, onIdleCh, "waiting for onIdle")
	s.watcher.changes <- struct{}{}
	waitChannel(c, onIdleCh, "waiting for onIdle")
	close(s.dying)

	err := waitChannel(c, done, "waiting for loop to exit")
	c.Assert(err, gc.Equals, tomb.ErrDying)

	select {
	case <-onIdleCh:
		c.Fatal("unexpected onIdle call")
	default:
	}
}

func (s *LoopSuite) TestOnIdleError(c *gc.C) {
	s.onIdle = func() error {
		return errors.New("onIdle failed")
	}
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "onIdle failed")
}

func (s *LoopSuite) TestErrWaitingNoOnIdle(c *gc.C) {
	var onIdleCalled bool
	s.onIdle = func() error {
		onIdleCalled = true
		return nil
	}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, resolver.ErrWaiting
	})
	close(s.dying)
	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(onIdleCalled, jc.IsFalse)
}

func (s *LoopSuite) TestInitialFinalLocalState(c *gc.C) {
	var local resolver.LocalState
	s.resolver = resolver.ResolverFunc(func(
		l resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		local = l
		return nil, resolver.ErrNoOperation
	})

	close(s.dying)
	lastLocal, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(local, jc.DeepEquals, resolver.LocalState{
		CharmURL:         s.charmURL,
		CompletedActions: map[string]struct{}{},
	})
	c.Assert(lastLocal, jc.DeepEquals, local)
}

func (s *LoopSuite) TestLoop(c *gc.C) {
	var resolverCalls int
	theOp := &mockOp{}
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		resolverCalls++
		switch resolverCalls {
		// On the first call, return an operation.
		case 1:
			return theOp, nil
		// On the second call, simulate having
		// no operations to perform, at which
		// point we'll wait for a remote state
		// change.
		case 2:
			s.watcher.changes <- struct{}{}
			break
		// On the third call, kill the loop.
		case 3:
			close(s.dying)
			break
		}
		return nil, resolver.ErrNoOperation
	})

	_, err := s.loop()
	c.Assert(err, gc.Equals, tomb.ErrDying)
	c.Assert(resolverCalls, gc.Equals, 3)
	s.executor.CheckCallNames(c, "State", "State", "Run", "State", "State")
	c.Assert(s.executor.Calls()[2].Args, jc.SameContents, []interface{}{theOp})
}

func (s *LoopSuite) TestRunFails(c *gc.C) {
	s.executor.SetErrors(errors.New("Run fails"))
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return mockOp{}, nil
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "Run fails")
}

func (s *LoopSuite) TestNextOpFails(c *gc.C) {
	s.resolver = resolver.ResolverFunc(func(
		_ resolver.LocalState,
		_ remotestate.Snapshot,
		_ operation.Factory,
	) (operation.Operation, error) {
		return nil, errors.New("NextOp fails")
	})
	_, err := s.loop()
	c.Assert(err, gc.ErrorMatches, "NextOp fails")
}

func waitChannel(c *gc.C, ch <-chan interface{}, activity string) interface{} {
	select {
	case v := <-ch:
		return v
	case <-time.After(coretesting.LongWait):
		c.Fatalf("timed out " + activity)
		panic("unreachable")
	}
}
<|endoftext|>


<commit_before>// Package form provides an easy to use way to parse form values from an HTTP
// request into a struct
package form // import "vimagination.zapto.org/form"

import (
	"net/http"
	"reflect"
	"strings"
	"sync"
)

var interType = reflect.TypeOf((*formParser)(nil)).Elem()

type processorDetails struct {
	processor
	Post, Required bool
	Index          []int
}

type typeMap map[string]processorDetails

var (
	tmMu     sync.RWMutex
	typeMaps = make(map[reflect.Type]typeMap)
)

func getTypeMap(t reflect.Type) typeMap {
	tmMu.RLock()
	tm, ok := typeMaps[t]
	tmMu.RUnlock()
	if ok {
		return tm
	}
	tmMu.Lock()
	tm = createTypeMap(t)
	tmMu.Unlock()
	return tm
}

func basicTypeProcessor(t reflect.Type, tag reflect.StructTag) processor {
	switch t.Kind() {
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		return newInum(tag, t.Bits())
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
		return newUnum(tag, t.Bits())
	case reflect.Float32, reflect.Float64:
		return newFloat(tag, t.Bits())
	case reflect.String:
		return str{}
	case reflect.Bool:
		return boolean{}
	}
	return nil
}

func createTypeMap(t reflect.Type) typeMap {
	tm, ok := typeMaps[t]
	if ok {
		return tm
	}
	tm = make(typeMap)
	for i := 0; i < t.Len(); i++ {
		f := t.Field(i)
		if f.PkgPath != "" {
			continue
		}
		name := f.Name
		var required, post bool
		if n := f.Tag.Get("form"); n == "-" {
			continue
		} else if n != "" {
			p := strings.IndexByte(n, ',')
			if p >= 0 {
				if p > 0 {
					name = n[:p]
				}
				rest := n[p:]
				required = strings.Contains(rest, ",required,") || strings.HasPrefix(rest, ",required")
				post = strings.Contains(rest, ",post,") || strings.HasPrefix(rest, ",post")
			} else {
				name = n
			}
		}
		var p processor
		if f.Type.Implements(interType) {
			p = inter(false)
		} else if reflect.PtrTo(f.Type).Implements(interType) {
			p = inter(true)
		} else if k := f.Type.Kind(); k == reflect.Slice || k == reflect.Ptr {
			et := f.Type.Elem()
			s := basicTypeProcessor(et, f.Tag)
			if s == nil {
				continue
			}
			if k == reflect.Slice {
				p = slice{
					processor: s,
					typ:       et,
				}
			} else {
				p = pointer{
					processor: s,
					typ:       et,
				}
			}
		} else if k == reflect.Struct && f.Anonymous {
			for n, p := range createTypeMap(f.Type) {
				if _, ok := tm[n]; !ok {
					tm[n] = processorDetails{
						processor: p.processor,
						Required:  p.Required,
						Post:      p.Post,
						Index:     append(append(make([]int, 0, len(p.Index)+1), i), p.Index...),
					}
				}
			}
			continue
		} else {
			p = basicTypeProcessor(f.Type, f.Tag)
			if p == nil {
				continue
			}
		}
		tm[name] = processorDetails{
			processor: p,
			Required:  required,
			Post:      post,
			Index:     []int{i},
		}
	}
	typeMaps[t] = tm
	return tm
}

func Process(r *http.Request, fv interface{}) error {
	v := reflect.ValueOf(fv)
	if v.Kind() != reflect.Ptr {
		return ErrNeedPointer
	}
	for v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		return ErrNeedStruct
	}
	tm := getTypeMap(v.Type())
	if err := r.ParseForm(); err != nil {
		return err
	}
	var errors Errors
	for key, pd := range tm {
		var (
			val []string
			ok  bool
		)
		if pd.Post {
			val, ok = r.PostForm[key]
		} else {
			val, ok = r.Form[key]
		}
		if ok {
			if err := pd.processor.process(v.FieldByIndex(pd.Index), val); err != nil {
				errors = append(errors, ErrProcessingFailed{
					Key: key,
					Err: err,
				})
			}
		} else if pd.Required {
			errors = append(errors, ErrRequiredMissing(key))
		}
	}
	if len(errors) > 0 {
		return errors
	}
	return nil
}
<commit_msg>added main fn comment<commit_after>// Package form provides an easy to use way to parse form values from an HTTP
// request into a struct
package form // import "vimagination.zapto.org/form"

import (
	"net/http"
	"reflect"
	"strings"
	"sync"
)

var interType = reflect.TypeOf((*formParser)(nil)).Elem()

type processorDetails struct {
	processor
	Post, Required bool
	Index          []int
}

type typeMap map[string]processorDetails

var (
	tmMu     sync.RWMutex
	typeMaps = make(map[reflect.Type]typeMap)
)

func getTypeMap(t reflect.Type) typeMap {
	tmMu.RLock()
	tm, ok := typeMaps[t]
	tmMu.RUnlock()
	if ok {
		return tm
	}
	tmMu.Lock()
	tm = createTypeMap(t)
	tmMu.Unlock()
	return tm
}

func basicTypeProcessor(t reflect.Type, tag reflect.StructTag) processor {
	switch t.Kind() {
	case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
		return newInum(tag, t.Bits())
	case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
		return newUnum(tag, t.Bits())
	case reflect.Float32, reflect.Float64:
		return newFloat(tag, t.Bits())
	case reflect.String:
		return str{}
	case reflect.Bool:
		return boolean{}
	}
	return nil
}

func createTypeMap(t reflect.Type) typeMap {
	tm, ok := typeMaps[t]
	if ok {
		return tm
	}
	tm = make(typeMap)
	for i := 0; i < t.Len(); i++ {
		f := t.Field(i)
		if f.PkgPath != "" {
			continue
		}
		name := f.Name
		var required, post bool
		if n := f.Tag.Get("form"); n == "-" {
			continue
		} else if n != "" {
			p := strings.IndexByte(n, ',')
			if p >= 0 {
				if p > 0 {
					name = n[:p]
				}
				rest := n[p:]
				required = strings.Contains(rest, ",required,") || strings.HasPrefix(rest, ",required")
				post = strings.Contains(rest, ",post,") || strings.HasPrefix(rest, ",post")
			} else {
				name = n
			}
		}
		var p processor
		if f.Type.Implements(interType) {
			p = inter(false)
		} else if reflect.PtrTo(f.Type).Implements(interType) {
			p = inter(true)
		} else if k := f.Type.Kind(); k == reflect.Slice || k == reflect.Ptr {
			et := f.Type.Elem()
			s := basicTypeProcessor(et, f.Tag)
			if s == nil {
				continue
			}
			if k == reflect.Slice {
				p = slice{
					processor: s,
					typ:       et,
				}
			} else {
				p = pointer{
					processor: s,
					typ:       et,
				}
			}
		} else if k == reflect.Struct && f.Anonymous {
			for n, p := range createTypeMap(f.Type) {
				if _, ok := tm[n]; !ok {
					tm[n] = processorDetails{
						processor: p.processor,
						Required:  p.Required,
						Post:      p.Post,
						Index:     append(append(make([]int, 0, len(p.Index)+1), i), p.Index...),
					}
				}
			}
			continue
		} else {
			p = basicTypeProcessor(f.Type, f.Tag)
			if p == nil {
				continue
			}
		}
		tm[name] = processorDetails{
			processor: p,
			Required:  required,
			Post:      post,
			Index:     []int{i},
		}
	}
	typeMaps[t] = tm
	return tm
}

// Process parses the form data from the request into the passed value, which
// must be a pointer to a struct.
//
// Form keys are assumed to be the field names unless a 'form' tag is provided
// with an alternate name, for example, in the following struct, the int is
// parse with key 'A' and the bool is parsed with key 'C'.
//
// type Example struct {
//	A int
//	B bool `form:"C"`
// }
//
// Two options can be added to the form tag to modify the processing. The
// 'post' option forces the processer to parse a value from the PostForm field
// of the Request, and the 'required' option will have an error thrown if the
// key in not set.
//
// Number types can also have minimums and maximums checked during processing
// by setting the min and max tags accordingly.
//
// Lastly, a custom data processor can be specified by attaching a method to
// the field type with the following specification:
//
// ParseForm([]string) error
func Process(r *http.Request, fv interface{}) error {
	v := reflect.ValueOf(fv)
	if v.Kind() != reflect.Ptr {
		return ErrNeedPointer
	}
	for v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	if v.Kind() != reflect.Struct {
		return ErrNeedStruct
	}
	tm := getTypeMap(v.Type())
	if err := r.ParseForm(); err != nil {
		return err
	}
	var errors Errors
	for key, pd := range tm {
		var (
			val []string
			ok  bool
		)
		if pd.Post {
			val, ok = r.PostForm[key]
		} else {
			val, ok = r.Form[key]
		}
		if ok {
			if err := pd.processor.process(v.FieldByIndex(pd.Index), val); err != nil {
				errors = append(errors, ErrProcessingFailed{
					Key: key,
					Err: err,
				})
			}
		} else if pd.Required {
			errors = append(errors, ErrRequiredMissing(key))
		}
	}
	if len(errors) > 0 {
		return errors
	}
	return nil
}
<|endoftext|>


<commit_before>package ftls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net"
)

var (
	DefaultMinVersion = uint16(tls.VersionTLS12)
	DefaultCipherSet  = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}
)

type Ftsl struct {
	c *Config
}

type Config struct {
	StrictClientVerify bool
	MutualTLS          bool
	InsecureSkipVerify bool
	MinVersion         uint16
	CipherSet          []uint16
	CertFile           string
	KeyFile            string
	CAFile             string
}

// DefaultServerFTLSConf returns a ftls config with the most commonly used config set.
func DefaultServerFTLSConf(CertFile, KeyFile, CAFile string) *Config {
	return &Config{
		StrictClientVerify: true,
		MutualTLS:          true,
		InsecureSkipVerify: false,
		MinVersion:         DefaultMinVersion,
		CipherSet:          DefaultCipherSet,
		CertFile:           CertFile,
		KeyFile:            KeyFile,
		CAFile:             CAFile,
	}
}

// DefaultClientFTLSConf returns a ftls config with the most commonly used config set.
func DefaultClientFTLSConf(CertFile, KeyFile, CAFile string) *Config {
	return &Config{
		MutualTLS:          true,
		InsecureSkipVerify: false,
		MinVersion:         DefaultMinVersion,
		CertFile:           CertFile,
		KeyFile:            KeyFile,
		CAFile:             CAFile,
	}
}

// NewClientTLSConfig constructs a client tls.Conf from provide ftls Config.
func NewClientTLSConfig(c *Config) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
	if err != nil {
		return &tls.Config{}, fmt.Errorf("Unable to load cert %s %s: %s", c.CertFile, c.KeyFile, err.Error())
	}
	clientCertPool := x509.NewCertPool()
	if c.CAFile != "" {
		clientCACert, err := ioutil.ReadFile(c.CAFile)
		if err != nil {
			return &tls.Config{}, fmt.Errorf("Unable to load CA cert %s: %s", c.CAFile, err.Error())
		}
		clientCertPool.AppendCertsFromPEM(clientCACert)
	}
	if c.MutualTLS {
		tlsConf := &tls.Config{
			Certificates: []tls.Certificate{cert},
			RootCAs:      clientCertPool,
			CipherSuites: c.CipherSet,
			MinVersion:   DefaultMinVersion,
		}
		tlsConf.BuildNameToCertificate()
		return tlsConf, nil
	}
	return &tls.Config{RootCAs: clientCertPool, InsecureSkipVerify: c.InsecureSkipVerify}, nil
}

// NewServerTLSConfig constructs a server tls.Conf from the provided ftls Config.
func NewServerTLSConfig(c *Config) (*tls.Config, error) {
	tlsConfig := &tls.Config{}
	if c.MutualTLS {
		caCert, err := ioutil.ReadFile(c.CAFile)
		if err != nil {
			return nil, fmt.Errorf("Unable to load ca cert %s: %s", c.CAFile, err.Error())
		}
		clientCertPool := x509.NewCertPool()
		if ok := clientCertPool.AppendCertsFromPEM(caCert); !ok {
			return nil, fmt.Errorf("Unable to append cert %s to pool.", c.CAFile)
		}
		strictness := tls.RequireAndVerifyClientCert
		if !c.StrictClientVerify {
			strictness = tls.RequireAnyClientCert
		}
		tlsConf = &tls.Config{
			ClientAuth:               strictness,
			ClientCAs:                clientCertPool,
			CipherSuites:             c.CipherSet,
			PreferServerCipherSuites: true,
			MinVersion:               DefaultMinVersion,
		}
		tlsConf.BuildNameToCertificate()
	}
	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
	if err != nil {
		return nil, err
	}
	tlsConf.Certificates = []tls.Certificate{cert}
	tlsConf.InsecureSkipVerify = c.InsecureSkipVerify
	return tlsConf, nil
}

func VerifyClientAddrMatch(c *tls.Conn) error {
	err := c.Handshake()
	if err != nil {
		return err
	}
	addr, _, err := net.SplitHostPort(c.RemoteAddr().String())
	if err != nil {
		return err
	}
	return c.ConnectionState().VerifiedChains[0][0].VerifyHostname(addr)
}
<commit_msg>i haz a dumb<commit_after>package ftls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"io/ioutil"
	"net"
)

var (
	DefaultMinVersion = uint16(tls.VersionTLS12)
	DefaultCipherSet  = []uint16{tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384}
)

type Ftsl struct {
	c *Config
}

type Config struct {
	StrictClientVerify bool
	MutualTLS          bool
	InsecureSkipVerify bool
	MinVersion         uint16
	CipherSet          []uint16
	CertFile           string
	KeyFile            string
	CAFile             string
}

// DefaultServerFTLSConf returns a ftls config with the most commonly used config set.
func DefaultServerFTLSConf(CertFile, KeyFile, CAFile string) *Config {
	return &Config{
		StrictClientVerify: true,
		MutualTLS:          true,
		InsecureSkipVerify: false,
		MinVersion:         DefaultMinVersion,
		CipherSet:          DefaultCipherSet,
		CertFile:           CertFile,
		KeyFile:            KeyFile,
		CAFile:             CAFile,
	}
}

// DefaultClientFTLSConf returns a ftls config with the most commonly used config set.
func DefaultClientFTLSConf(CertFile, KeyFile, CAFile string) *Config {
	return &Config{
		MutualTLS:          true,
		InsecureSkipVerify: false,
		MinVersion:         DefaultMinVersion,
		CertFile:           CertFile,
		KeyFile:            KeyFile,
		CAFile:             CAFile,
	}
}

// NewClientTLSConfig constructs a client tls.Conf from provide ftls Config.
func NewClientTLSConfig(c *Config) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
	if err != nil {
		return &tls.Config{}, fmt.Errorf("Unable to load cert %s %s: %s", c.CertFile, c.KeyFile, err.Error())
	}
	clientCertPool := x509.NewCertPool()
	if c.CAFile != "" {
		clientCACert, err := ioutil.ReadFile(c.CAFile)
		if err != nil {
			return &tls.Config{}, fmt.Errorf("Unable to load CA cert %s: %s", c.CAFile, err.Error())
		}
		clientCertPool.AppendCertsFromPEM(clientCACert)
	}
	if c.MutualTLS {
		tlsConf := &tls.Config{
			Certificates: []tls.Certificate{cert},
			RootCAs:      clientCertPool,
			CipherSuites: c.CipherSet,
			MinVersion:   DefaultMinVersion,
		}
		tlsConf.BuildNameToCertificate()
		return tlsConf, nil
	}
	return &tls.Config{RootCAs: clientCertPool, InsecureSkipVerify: c.InsecureSkipVerify}, nil
}

// NewServerTLSConfig constructs a server tls.Conf from the provided ftls Config.
func NewServerTLSConfig(c *Config) (*tls.Config, error) {
	tlsConf := &tls.Config{}
	if c.MutualTLS {
		caCert, err := ioutil.ReadFile(c.CAFile)
		if err != nil {
			return nil, fmt.Errorf("Unable to load ca cert %s: %s", c.CAFile, err.Error())
		}
		clientCertPool := x509.NewCertPool()
		if ok := clientCertPool.AppendCertsFromPEM(caCert); !ok {
			return nil, fmt.Errorf("Unable to append cert %s to pool.", c.CAFile)
		}
		strictness := tls.RequireAndVerifyClientCert
		if !c.StrictClientVerify {
			strictness = tls.RequireAnyClientCert
		}
		tlsConf = &tls.Config{
			ClientAuth:               strictness,
			ClientCAs:                clientCertPool,
			CipherSuites:             c.CipherSet,
			PreferServerCipherSuites: true,
			MinVersion:               DefaultMinVersion,
		}
		tlsConf.BuildNameToCertificate()
	}
	cert, err := tls.LoadX509KeyPair(c.CertFile, c.KeyFile)
	if err != nil {
		return nil, err
	}
	tlsConf.Certificates = []tls.Certificate{cert}
	tlsConf.InsecureSkipVerify = c.InsecureSkipVerify
	return tlsConf, nil
}

func VerifyClientAddrMatch(c *tls.Conn) error {
	err := c.Handshake()
	if err != nil {
		return err
	}
	addr, _, err := net.SplitHostPort(c.RemoteAddr().String())
	if err != nil {
		return err
	}
	return c.ConnectionState().VerifiedChains[0][0].VerifyHostname(addr)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"strconv"
	"sync"
	"time"
)

// define our game states
type State string

const IS_RUNNING State = "running"
const IS_STOPPED State = "stopped"

// define our stop reasons
type StopReason string

const FINISHED StopReason = "user finished the game"
const TIMEOUT StopReason = "game timed out"
const STOPPED StopReason = "user stoped the game"

// define the genders available in our application
type Gender string

const MALE Gender = "male"
const FEMALE Gender = "female"

// current state of the game
var GameState State

// define the time the user has to complete the game
var Timeout time.Duration

// define some communication channels
var contactChannel chan bool          // a channel to register all contacts with the wire
var contactChannelDebounced chan bool // a debouncer for our contact channel
var startChannel chan bool            // a channel to register touching the start area
var finishChannel chan StopReason     // a channel to register finish events (from user or timeout)

var DebounceContact time.Duration // how long should the contact channel be debounced

// lock concurrent access to the shared variables
var Mutex *sync.RWMutex

// --- EVENT HANDLERS FOR USER INTERACTION ---

// handleButtonPress will be called on every button press
// - i.e. start, stop, restart - and should be passed the gender
// that the button is allocated to (male, female)
func handleButtonPress(gender Gender) {
	log.Println("button pressed:", gender)
	currentState := getState()

	// start or stop the game
	if currentState == IS_STOPPED {
		go startGame(gender)
		return
	}

	// send a message into our finish channel
	// if the game is running
	select {
	case finishChannel <- STOPPED:
	default:
	}

}

// handleStartContact will make sure, that the users is starting from the
// beginning of the wire
func handleStartContact(s interface{}) {
	// send a signal down the start channel
	select {
	case startChannel <- true:
	default:
	}
}

// handleWireContact will be called whenever the user
// touches the wire
func handleWireContact(s interface{}) {
	log.Println("wire touched")
	currentState := getState()

	if currentState != IS_RUNNING {
		fmt.Println("the timer is currently not running")
		return
	}

	// send a contact event to our communication channel
	select {
	case contactChannelDebounced <- true:
	default:
	}
}

// handleFinishContact will be called whenever the user
// is touching the finish platform
func handleFinishContact(s interface{}) {
	log.Println("game finished")
	currentState := getState()

	if currentState != IS_RUNNING {
		fmt.Println("the timer is currently not running")
		return
	}

	// send a message into our finish channel
	select {
	case finishChannel <- FINISHED:
	default:
	}

}

// --- ACTUAL GAME ACTIONS ---

// startGame will start a new round
func startGame(gender Gender) {

	// signal the webserver that we are about to start the game
	signalChannel <- "game::start::" + string(gender)
	fmt.Println("game almost started")

	// initialize the start time and touch counter
	startTime := time.Now()
	touchCounter := 0

	// set the game state
	setState(IS_RUNNING)

	fmt.Print("\033[H\033[2J")

	done := make(chan struct{})

	// define the led event to use (male or female)
	var ledEvent string
	if gender == FEMALE {
		ledEvent = "enableLedWoman"
	} else {
		ledEvent = "enalbeLedMan"
	}

	// enable the led light
	select {
	case GameEvents <- ledEvent:
	default:
	}

	// create a separate go-routine for our ticker
	go func(startTime time.Time, done <-chan struct{}) {
		fmt.Println("game started")
		for {
			select {
			// create a ticker to check the time in regular timespans
			case <-time.After(200 * time.Millisecond):
				timeElapsed := time.Now().Sub(startTime)
				// fmt.Print("\rTICK: ", timeElapsed.Seconds())

				if timeElapsed >= Timeout {
					finishChannel <- TIMEOUT
				}

			case <-done:
				return
			}
		}

	}(startTime, done)

	// increase the touch counter on every touch
	go func(startTime time.Time, counter int, gender Gender, done chan struct{}) {

		// check if the user touched the start of the wire
		var startTouched bool

		for {
			select {
			case <-startChannel:
				fmt.Println("start region touched")
				startTouched = true

			case <-contactChannel:
				fmt.Println("register contact")

				// check if the start region has been touched before
				if startTouched == false {
					fmt.Println("BEEP: start region not touched yet")
					break
				}

				// increase the touch counter
				counter++

				// signal the webserver that the wire was touched
				signalChannel <- "game::contact::" + strconv.Itoa(counter)

				// sound the buzzer
				select {
				case GameEvents <- "soundBuzzer":
				default:
				}

			case reason := <-finishChannel:

				timeElapsed := time.Now().Sub(startTime)

				// signal the webserver that the game was finished
				signalChannel <- "game::finished::" + string(reason) + "::" + strconv.FormatFloat(timeElapsed.Seconds(), 'f', 3, 64)

				// set the state of the game to stopped
				setState(IS_STOPPED)

				// print the results
				fmt.Printf(resultLog, gender, reason, timeElapsed.Seconds(), counter)

				// close our waiting channel
				close(done)

			case <-done:
				fmt.Println("game was finished..")
				select {
				case GameEvents <- "ledOff":
				default:
				}
				return
			}
		}

	}(startTime, touchCounter, gender, done)

}
<commit_msg>change spelling mistake<commit_after>package main

import (
	"fmt"
	"log"
	"strconv"
	"sync"
	"time"
)

// define our game states
type State string

const IS_RUNNING State = "running"
const IS_STOPPED State = "stopped"

// define our stop reasons
type StopReason string

const FINISHED StopReason = "user finished the game"
const TIMEOUT StopReason = "game timed out"
const STOPPED StopReason = "user stopped the game"

// define the genders available in our application
type Gender string

const MALE Gender = "male"
const FEMALE Gender = "female"

// current state of the game
var GameState State

// define the time the user has to complete the game
var Timeout time.Duration

// define some communication channels
var contactChannel chan bool          // a channel to register all contacts with the wire
var contactChannelDebounced chan bool // a debouncer for our contact channel
var startChannel chan bool            // a channel to register touching the start area
var finishChannel chan StopReason     // a channel to register finish events (from user or timeout)

var DebounceContact time.Duration // how long should the contact channel be debounced

// lock concurrent access to the shared variables
var Mutex *sync.RWMutex

// --- EVENT HANDLERS FOR USER INTERACTION ---

// handleButtonPress will be called on every button press
// - i.e. start, stop, restart - and should be passed the gender
// that the button is allocated to (male, female)
func handleButtonPress(gender Gender) {
	log.Println("button pressed:", gender)
	currentState := getState()

	// start or stop the game
	if currentState == IS_STOPPED {
		go startGame(gender)
		return
	}

	// send a message into our finish channel
	// if the game is running
	select {
	case finishChannel <- STOPPED:
	default:
	}

}

// handleStartContact will make sure, that the users is starting from the
// beginning of the wire
func handleStartContact(s interface{}) {
	// send a signal down the start channel
	select {
	case startChannel <- true:
	default:
	}
}

// handleWireContact will be called whenever the user
// touches the wire
func handleWireContact(s interface{}) {
	log.Println("wire touched")
	currentState := getState()

	if currentState != IS_RUNNING {
		fmt.Println("the timer is currently not running")
		return
	}

	// send a contact event to our communication channel
	select {
	case contactChannelDebounced <- true:
	default:
	}
}

// handleFinishContact will be called whenever the user
// is touching the finish platform
func handleFinishContact(s interface{}) {
	log.Println("game finished")
	currentState := getState()

	if currentState != IS_RUNNING {
		fmt.Println("the timer is currently not running")
		return
	}

	// send a message into our finish channel
	select {
	case finishChannel <- FINISHED:
	default:
	}

}

// --- ACTUAL GAME ACTIONS ---

// startGame will start a new round
func startGame(gender Gender) {

	// signal the webserver that we are about to start the game
	signalChannel <- "game::start::" + string(gender)
	fmt.Println("game almost started")

	// initialize the start time and touch counter
	startTime := time.Now()
	touchCounter := 0

	// set the game state
	setState(IS_RUNNING)

	fmt.Print("\033[H\033[2J")

	done := make(chan struct{})

	// define the led event to use (male or female)
	var ledEvent string
	if gender == FEMALE {
		ledEvent = "enableLedWoman"
	} else {
		ledEvent = "enalbeLedMan"
	}

	// enable the led light
	select {
	case GameEvents <- ledEvent:
	default:
	}

	// create a separate go-routine for our ticker
	go func(startTime time.Time, done <-chan struct{}) {
		fmt.Println("game started")
		for {
			select {
			// create a ticker to check the time in regular timespans
			case <-time.After(200 * time.Millisecond):
				timeElapsed := time.Now().Sub(startTime)
				// fmt.Print("\rTICK: ", timeElapsed.Seconds())

				if timeElapsed >= Timeout {
					finishChannel <- TIMEOUT
				}

			case <-done:
				return
			}
		}

	}(startTime, done)

	// increase the touch counter on every touch
	go func(startTime time.Time, counter int, gender Gender, done chan struct{}) {

		// check if the user touched the start of the wire
		var startTouched bool

		for {
			select {
			case <-startChannel:
				fmt.Println("start region touched")
				startTouched = true

			case <-contactChannel:
				fmt.Println("register contact")

				// check if the start region has been touched before
				if startTouched == false {
					fmt.Println("BEEP: start region not touched yet")
					break
				}

				// increase the touch counter
				counter++

				// signal the webserver that the wire was touched
				signalChannel <- "game::contact::" + strconv.Itoa(counter)

				// sound the buzzer
				select {
				case GameEvents <- "soundBuzzer":
				default:
				}

			case reason := <-finishChannel:

				timeElapsed := time.Now().Sub(startTime)

				// signal the webserver that the game was finished
				signalChannel <- "game::finished::" + string(reason) + "::" + strconv.FormatFloat(timeElapsed.Seconds(), 'f', 3, 64)

				// set the state of the game to stopped
				setState(IS_STOPPED)

				// print the results
				fmt.Printf(resultLog, gender, reason, timeElapsed.Seconds(), counter)

				// close our waiting channel
				close(done)

			case <-done:
				fmt.Println("game was finished..")
				select {
				case GameEvents <- "ledOff":
				default:
				}
				return
			}
		}

	}(startTime, touchCounter, gender, done)

}
<|endoftext|>


<commit_before>// Copyright 2012 The go-gl 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 gltext

import (
	"fmt"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
)

const IsEdit = false

type Text struct {
	font *Font

	// final position on screen
	finalPosition mgl32.Vec2

	// text color
	color mgl32.Vec4

	// scaling the text
	Scale       float32
	ScaleMax    float32
	scaleMatrix mgl32.Mat4

	// bounding box of text
	BoundingBox *BoundingBox

	// general opengl values
	vao           uint32
	vbo           uint32
	ebo           uint32
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int

	// X1, X2: the lower left and upper right points of a box that bounds the text
	X1 Point
	X2 Point
}

func LoadText(f *Font) (t *Text) {
	t = new(Text)
	t.font = f

	// text hover values - implicit ScaleMin of 1.0
	t.ScaleMax = 1.1
	t.SetScale(1)

	// size of glfloat
	glfloat_size := int32(4)

	// stride of the buffered data
	xy_count := int32(2)
	stride := xy_count + int32(2)

	gl.GenVertexArrays(1, &t.vao)
	gl.GenBuffers(1, &t.vbo)
	gl.GenBuffers(1, &t.ebo)

	// vao
	gl.BindVertexArray(t.vao)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, t.font.textureID)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)

	gl.EnableVertexAttribArray(t.font.centeredPosition)
	gl.VertexAttribPointer(
		t.font.centeredPosition,
		2,
		gl.FLOAT,
		false,
		glfloat_size*stride,
		gl.PtrOffset(0),
	)

	gl.EnableVertexAttribArray(t.font.uv)
	gl.VertexAttribPointer(
		t.font.uv,
		2,
		gl.FLOAT,
		false,
		glfloat_size*stride,
		gl.PtrOffset(int(glfloat_size*xy_count)),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return t
}

// Release releases font resources.
// A font can no longer be used for rendering after this call completes.
func (t *Text) Release() {
	gl.DeleteBuffers(1, &t.vbo)
	gl.DeleteBuffers(1, &t.ebo)
	gl.DeleteBuffers(1, &t.vao)
}

func (t *Text) SetScale(s float32) (changed bool) {
	if s > t.ScaleMax || s < 1.0 {
		return
	}
	changed = true
	t.Scale = s
	t.scaleMatrix = mgl32.Scale3D(s, s, s)
	return
}

func (t *Text) AddScale(s float32) (changed bool) {
	if s < 0 && t.Scale <= 1.0 {
		return
	}
	if s > 0 && t.Scale >= t.ScaleMax {
		return
	}
	changed = true
	t.Scale += s
	t.scaleMatrix = mgl32.Scale3D(t.Scale, t.Scale, t.Scale)
	return
}

func (t *Text) SetColor(r, g, b, a float32) {
	t.color = mgl32.Vec4{r, g, b, a}
}

func (t *Text) SetString(fs string, argv ...interface{}) (Point, Point) {
	indices := []rune(fmt.Sprintf(fs, argv...))
	if len(indices) == 0 {
		return Point{}, Point{}
	}

	// ebo, vbo data
	glfloat_size := int32(4)

	t.vboIndexCount = len(indices) * 4 * 2 * 2 // 4 indexes per rune (containing 2 position + 2 texture)
	t.eboIndexCount = len(indices) * 6         // each rune requires 6 triangle indices for a quad
	t.vboData = make([]float32, t.vboIndexCount, t.vboIndexCount)
	t.eboData = make([]int32, t.eboIndexCount, t.eboIndexCount)

	// generate the basic vbo data and bounding box
	t.makeBufferData(indices)

	// find the centered position of the bounding box
	lowerLeft := t.center()

	// reposition the vbo data so that it is centered at (0,0)
	// according to the orthographic projection being used
	t.setDataPosition(lowerLeft)

	if IsDebug {
		fmt.Printf("bounding box %v %v\n", t.X1, t.X2)
		fmt.Printf("text vbo data\n%v\n", t.vboData)
		fmt.Printf("text ebo data\n%v\n", t.eboData)
	}
	gl.BindVertexArray(t.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, int(glfloat_size)*t.vboIndexCount, gl.Ptr(t.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, int(glfloat_size)*t.eboIndexCount, gl.Ptr(t.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	return t.X1, t.X2
}

func (t *Text) center() (lowerLeft Point) {
	lineWidthHalf := (t.X2.X - t.X1.X) / 2
	lineHeightHalf := (t.X2.Y - t.X1.Y) / 2

	lowerLeft.X = -lineWidthHalf
	lowerLeft.Y = -lineHeightHalf
	return
}

func (t *Text) SetPosition(x, y float32) {
	// at this point we are in orthographic projection coordinates which range from -1 to 1
	// and the text's default position is with its bounding box perfectly centered in the screen

	// final place the corner on the position specified by the user
	t.finalPosition[0] = x
	t.finalPosition[1] = y
	if IsEdit {
		t.BoundingBox.finalPosition[0] = x
		t.BoundingBox.finalPosition[1] = y
	}
}

func (t *Text) Draw() {
	if IsEdit {
		t.BoundingBox.Draw()
	}
	gl.UseProgram(t.font.program)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, t.font.textureID)

	// uniforms
	gl.Uniform1i(t.font.fragmentTextureUniform, 0)
	gl.Uniform1f(t.font.textLowerBoundUniform, t.font.textLowerBound)
	gl.Uniform4fv(t.font.colorUniform, 1, &t.color[0])
	gl.Uniform2fv(t.font.finalPositionUniform, 1, &t.finalPosition[0])
	gl.UniformMatrix4fv(t.font.orthographicMatrixUniform, 1, false, &t.font.orthographicMatrix[0])
	gl.UniformMatrix4fv(t.font.scaleMatrixUniform, 1, false, &t.scaleMatrix[0])

	// draw
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	gl.BindVertexArray(t.vao)
	gl.DrawElements(gl.TRIANGLES, int32(t.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	gl.Disable(gl.BLEND)
}

func (t *Text) getBoundingBox(vboIndex int) {
	// index -4: x, index -3: y, index -2: uv's x, index -1 uv's y
	x := t.vboData[vboIndex-4]
	y := t.vboData[vboIndex-3]

	if vboIndex-4 == 0 {
		t.X1.X = x
		t.X1.Y = y
	} else {
		if x < t.X1.X {
			t.X1.X = x
		}
		if y < t.X1.Y {
			t.X1.Y = y
		}
		if x > t.X2.X {
			t.X2.X = x
		}
		if y > t.X2.Y {
			t.X2.Y = y
		}
	}
}

// all text originally sits at point (0,0) which is the
// lower left hand corner of the screen.
func (t *Text) setDataPosition(lowerLeft Point) (err error) {
	length := len(t.vboData)
	for index := 0; index < length; {
		// index (0,0)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3 // skip texture data

		// index (1,0)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3

		// index (1,1)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3

		// index (0,1)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3
	}
	// update bounding box
	t.X1.X += lowerLeft.X
	t.X2.X += lowerLeft.X
	t.X1.Y += lowerLeft.Y
	t.X2.Y += lowerLeft.Y
	if IsEdit {
		t.BoundingBox, err = loadBoundingBox(t.font, t.X1, t.X2)
	}
	return
}

// currently only supports left to right text flow
func (t *Text) makeBufferData(indices []rune) {
	glyphs := t.font.config.Glyphs
	low := t.font.config.Low

	vboIndex := 0
	eboIndex := 0
	lineX := float32(0)
	eboOffset := int32(0)
	for _, r := range indices {
		r -= low
		if r >= 0 && int(r) < len(glyphs) {
			vw := float32(glyphs[r].Width)
			vh := float32(glyphs[r].Height)
			tP1, tP2 := glyphs[r].GetIndices(t.font)

			// counter-clockwise quad

			// index (0,0)
			t.vboData[vboIndex] = lineX // position
			vboIndex++
			t.vboData[vboIndex] = 0
			vboIndex++
			t.vboData[vboIndex] = tP1.X // texture uv
			vboIndex++
			t.vboData[vboIndex] = tP2.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (1,0)
			t.vboData[vboIndex] = lineX + vw
			vboIndex++
			t.vboData[vboIndex] = 0
			vboIndex++
			t.vboData[vboIndex] = tP2.X
			vboIndex++
			t.vboData[vboIndex] = tP2.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (1,1)
			t.vboData[vboIndex] = lineX + vw
			vboIndex++
			t.vboData[vboIndex] = vh
			vboIndex++
			t.vboData[vboIndex] = tP2.X
			vboIndex++
			t.vboData[vboIndex] = tP1.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (0,1)
			t.vboData[vboIndex] = lineX
			vboIndex++
			t.vboData[vboIndex] = vh
			vboIndex++
			t.vboData[vboIndex] = tP1.X
			vboIndex++
			t.vboData[vboIndex] = tP1.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			advance := float32(glyphs[r].Advance)
			lineX += advance

			// ebo data
			t.eboData[eboIndex] = 0 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 1 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 2 + eboOffset
			eboIndex++

			t.eboData[eboIndex] = 0 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 2 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 3 + eboOffset
			eboIndex++
			eboOffset += 4
		}
	}
	return
}
<commit_msg>Comment cleanup.<commit_after>// Copyright 2012 The go-gl 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 gltext

import (
	"fmt"
	"github.com/go-gl/glow/gl-core/3.3/gl"
	"github.com/go-gl/mathgl/mgl32"
)

const IsEdit = false

type Text struct {
	font *Font

	// final position on screen
	finalPosition mgl32.Vec2

	// text color
	color mgl32.Vec4

	// scaling the text
	Scale       float32
	ScaleMax    float32
	scaleMatrix mgl32.Mat4

	// bounding box of text
	BoundingBox *BoundingBox

	// general opengl values
	vao           uint32
	vbo           uint32
	ebo           uint32
	vboData       []float32
	vboIndexCount int
	eboData       []int32
	eboIndexCount int

	// X1, X2: the lower left and upper right points of a box that bounds the text
	X1 Point
	X2 Point
}

func LoadText(f *Font) (t *Text) {
	t = new(Text)
	t.font = f

	// text hover values - implicit ScaleMin of 1.0
	t.ScaleMax = 1.1
	t.SetScale(1)

	// size of glfloat
	glfloat_size := int32(4)

	// stride of the buffered data
	xy_count := int32(2)
	stride := xy_count + int32(2)

	gl.GenVertexArrays(1, &t.vao)
	gl.GenBuffers(1, &t.vbo)
	gl.GenBuffers(1, &t.ebo)

	// vao
	gl.BindVertexArray(t.vao)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, t.font.textureID)

	// vbo
	// specify the buffer for which the VertexAttribPointer calls apply
	gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)

	gl.EnableVertexAttribArray(t.font.centeredPosition)
	gl.VertexAttribPointer(
		t.font.centeredPosition,
		2,
		gl.FLOAT,
		false,
		glfloat_size*stride,
		gl.PtrOffset(0),
	)

	gl.EnableVertexAttribArray(t.font.uv)
	gl.VertexAttribPointer(
		t.font.uv,
		2,
		gl.FLOAT,
		false,
		glfloat_size*stride,
		gl.PtrOffset(int(glfloat_size*xy_count)),
	)

	// ebo
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)

	// i am guessing that order is important here
	gl.BindVertexArray(0)
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
	return t
}

// Release releases font resources.
// A font can no longer be used for rendering after this call completes.
func (t *Text) Release() {
	gl.DeleteBuffers(1, &t.vbo)
	gl.DeleteBuffers(1, &t.ebo)
	gl.DeleteBuffers(1, &t.vao)
}

func (t *Text) SetScale(s float32) (changed bool) {
	if s > t.ScaleMax || s < 1.0 {
		return
	}
	changed = true
	t.Scale = s
	t.scaleMatrix = mgl32.Scale3D(s, s, s)
	return
}

func (t *Text) AddScale(s float32) (changed bool) {
	if s < 0 && t.Scale <= 1.0 {
		return
	}
	if s > 0 && t.Scale >= t.ScaleMax {
		return
	}
	changed = true
	t.Scale += s
	t.scaleMatrix = mgl32.Scale3D(t.Scale, t.Scale, t.Scale)
	return
}

func (t *Text) SetColor(r, g, b, a float32) {
	t.color = mgl32.Vec4{r, g, b, a}
}

func (t *Text) SetString(fs string, argv ...interface{}) (Point, Point) {
	indices := []rune(fmt.Sprintf(fs, argv...))
	if len(indices) == 0 {
		return Point{}, Point{}
	}

	// ebo, vbo data
	glfloat_size := int32(4)

	t.vboIndexCount = len(indices) * 4 * 2 * 2 // 4 indexes per rune (containing 2 position + 2 texture)
	t.eboIndexCount = len(indices) * 6         // each rune requires 6 triangle indices for a quad
	t.vboData = make([]float32, t.vboIndexCount, t.vboIndexCount)
	t.eboData = make([]int32, t.eboIndexCount, t.eboIndexCount)

	// generate the basic vbo data and bounding box
	t.makeBufferData(indices)

	// find the centered position of the bounding box
	lowerLeft := t.center()

	// reposition the vbo data so that it is centered at (0,0)
	// according to the orthographic projection being used
	t.setDataPosition(lowerLeft)

	if IsDebug {
		fmt.Printf("bounding box %v %v\n", t.X1, t.X2)
		fmt.Printf("text vbo data\n%v\n", t.vboData)
		fmt.Printf("text ebo data\n%v\n", t.eboData)
	}
	gl.BindVertexArray(t.vao)
	gl.BindBuffer(gl.ARRAY_BUFFER, t.vbo)
	gl.BufferData(
		gl.ARRAY_BUFFER, int(glfloat_size)*t.vboIndexCount, gl.Ptr(t.vboData), gl.DYNAMIC_DRAW)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, t.ebo)
	gl.BufferData(
		gl.ELEMENT_ARRAY_BUFFER, int(glfloat_size)*t.eboIndexCount, gl.Ptr(t.eboData), gl.DYNAMIC_DRAW)
	gl.BindVertexArray(0)

	// not necesssary, but i just want to better understand using vertex arrays
	gl.BindBuffer(gl.ARRAY_BUFFER, 0)
	gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)

	return t.X1, t.X2
}

func (t *Text) center() (lowerLeft Point) {
	lineWidthHalf := (t.X2.X - t.X1.X) / 2
	lineHeightHalf := (t.X2.Y - t.X1.Y) / 2

	lowerLeft.X = -lineWidthHalf
	lowerLeft.Y = -lineHeightHalf
	return
}

func (t *Text) SetPosition(x, y float32) {
	// final place the corner on the position specified by the user
	t.finalPosition[0] = x
	t.finalPosition[1] = y
	if IsEdit {
		t.BoundingBox.finalPosition[0] = x
		t.BoundingBox.finalPosition[1] = y
	}
}

func (t *Text) Draw() {
	if IsEdit {
		t.BoundingBox.Draw()
	}
	gl.UseProgram(t.font.program)

	gl.ActiveTexture(gl.TEXTURE0)
	gl.BindTexture(gl.TEXTURE_2D, t.font.textureID)

	// uniforms
	gl.Uniform1i(t.font.fragmentTextureUniform, 0)
	gl.Uniform1f(t.font.textLowerBoundUniform, t.font.textLowerBound)
	gl.Uniform4fv(t.font.colorUniform, 1, &t.color[0])
	gl.Uniform2fv(t.font.finalPositionUniform, 1, &t.finalPosition[0])
	gl.UniformMatrix4fv(t.font.orthographicMatrixUniform, 1, false, &t.font.orthographicMatrix[0])
	gl.UniformMatrix4fv(t.font.scaleMatrixUniform, 1, false, &t.scaleMatrix[0])

	// draw
	gl.Enable(gl.BLEND)
	gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
	gl.BindVertexArray(t.vao)
	gl.DrawElements(gl.TRIANGLES, int32(t.eboIndexCount), gl.UNSIGNED_INT, nil)
	gl.BindVertexArray(0)
	gl.Disable(gl.BLEND)
}

func (t *Text) getBoundingBox(vboIndex int) {
	// index -4: x, index -3: y, index -2: uv's x, index -1 uv's y
	x := t.vboData[vboIndex-4]
	y := t.vboData[vboIndex-3]

	if vboIndex-4 == 0 {
		t.X1.X = x
		t.X1.Y = y
	} else {
		if x < t.X1.X {
			t.X1.X = x
		}
		if y < t.X1.Y {
			t.X1.Y = y
		}
		if x > t.X2.X {
			t.X2.X = x
		}
		if y > t.X2.Y {
			t.X2.Y = y
		}
	}
}

// all text originally sits at point (0,0) which is the
// lower left hand corner of the screen.
func (t *Text) setDataPosition(lowerLeft Point) (err error) {
	length := len(t.vboData)
	for index := 0; index < length; {
		// index (0,0)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3 // skip texture data

		// index (1,0)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3

		// index (1,1)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3

		// index (0,1)
		t.vboData[index] += lowerLeft.X
		index++
		t.vboData[index] += lowerLeft.Y
		index += 3
	}
	// update bounding box
	t.X1.X += lowerLeft.X
	t.X2.X += lowerLeft.X
	t.X1.Y += lowerLeft.Y
	t.X2.Y += lowerLeft.Y
	if IsEdit {
		t.BoundingBox, err = loadBoundingBox(t.font, t.X1, t.X2)
	}
	fmt.Println(t.X1, t.X2)
	return
}

// currently only supports left to right text flow
func (t *Text) makeBufferData(indices []rune) {
	glyphs := t.font.config.Glyphs
	low := t.font.config.Low

	vboIndex := 0
	eboIndex := 0
	lineX := float32(0)
	eboOffset := int32(0)
	for _, r := range indices {
		r -= low
		if r >= 0 && int(r) < len(glyphs) {
			vw := float32(glyphs[r].Width)
			vh := float32(glyphs[r].Height)
			tP1, tP2 := glyphs[r].GetIndices(t.font)

			// counter-clockwise quad

			// index (0,0)
			t.vboData[vboIndex] = lineX // position
			vboIndex++
			t.vboData[vboIndex] = 0
			vboIndex++
			t.vboData[vboIndex] = tP1.X // texture uv
			vboIndex++
			t.vboData[vboIndex] = tP2.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (1,0)
			t.vboData[vboIndex] = lineX + vw
			vboIndex++
			t.vboData[vboIndex] = 0
			vboIndex++
			t.vboData[vboIndex] = tP2.X
			vboIndex++
			t.vboData[vboIndex] = tP2.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (1,1)
			t.vboData[vboIndex] = lineX + vw
			vboIndex++
			t.vboData[vboIndex] = vh
			vboIndex++
			t.vboData[vboIndex] = tP2.X
			vboIndex++
			t.vboData[vboIndex] = tP1.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			// index (0,1)
			t.vboData[vboIndex] = lineX
			vboIndex++
			t.vboData[vboIndex] = vh
			vboIndex++
			t.vboData[vboIndex] = tP1.X
			vboIndex++
			t.vboData[vboIndex] = tP1.Y
			vboIndex++
			t.getBoundingBox(vboIndex)

			advance := float32(glyphs[r].Advance)
			lineX += advance

			// ebo data
			t.eboData[eboIndex] = 0 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 1 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 2 + eboOffset
			eboIndex++

			t.eboData[eboIndex] = 0 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 2 + eboOffset
			eboIndex++
			t.eboData[eboIndex] = 3 + eboOffset
			eboIndex++
			eboOffset += 4
		}
	}
	return
}
<|endoftext|>


<commit_before>/* Provides a low-level Go interface to the systemd journal C API.

All public methods map closely to the sd-journal API functions. See the
sd-journal.h documentation[1] for information about each function.

[1] http://www.freedesktop.org/software/systemd/man/sd-journal.html
*/
package journal

/*
#cgo pkg-config: libsystemd-journal
#include <systemd/sd-journal.h>
#include <stdlib.h>
#include <syslog.h>

int go_sd_journal_print(int priority, char* s) {
	int r;
	r = sd_journal_print(priority, "%s", s);
	return r;
}
*/
import "C"
import (
	"fmt"
	"unsafe"
)

// Journal entry field strings which correspond to:
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
const (
	SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
	SD_JOURNAL_FIELD_MESSAGE      = "MESSAGE"
	SD_JOURNAL_FIELD_PID          = "_PID"
	SD_JOURNAL_FIELD_UID          = "_UID"
	SD_JOURNAL_FIELD_GID          = "_GID"
	SD_JOURNAL_FIELD_HOSTNAME     = "_HOSTNAME"
	SD_JOURNAL_FIELD_MACHINE_ID   = "_MACHINE_ID"
)

// Journal event constants
const (
	SD_JOURNAL_NOP        = int(C.SD_JOURNAL_NOP)
	SD_JOURNAL_APPEND     = int(C.SD_JOURNAL_APPEND)
	SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE)
)

// A Journal is a Go wrapper of an sd_journal structure.
type Journal struct {
	cjournal *C.sd_journal
}

// A Match is a convenience wrapper to describe filters supplied to AddMatch.
type Match struct {
	Field string
	Value string
}

// String returns a string representation of a Match suitable for use with AddMatch.
func (m *Match) String() string {
	return m.Field + "=" + m.Value
}

// NewJournal returns a new Journal instance pointing to the local journal
func NewJournal() (*Journal, error) {
	j := &Journal{}
	err := C.sd_journal_open(&j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)

	if err < 0 {
		return nil, fmt.Errorf("failed to open journal: %s", err)
	}

	return j, nil
}

func (j *Journal) Close() error {
	C.sd_journal_close(j.cjournal)
	return nil
}

func (j *Journal) AddMatch(match string) error {
	m := C.CString(match)
	defer C.free(unsafe.Pointer(m))

	C.sd_journal_add_match(j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
	return nil
}

func (j *Journal) Next() (int, error) {
	r := C.sd_journal_next(j.cjournal)

	if r < 0 {
		return int(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return int(r), nil
}

func (j *Journal) Previous() (uint64, error) {
	r := C.sd_journal_previous(j.cjournal)

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) PreviousSkip(skip uint64) (uint64, error) {
	r := C.sd_journal_previous_skip(j.cjournal, C.uint64_t(skip))

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) GetData(field string) (string, error) {
	f := C.CString(field)
	defer C.free(unsafe.Pointer(f))

	var d unsafe.Pointer
	var l C.size_t

	err := C.sd_journal_get_data(j.cjournal, f, &d, &l)

	if err < 0 {
		return "", fmt.Errorf("failed to read message: %d", err)
	}

	msg := C.GoStringN((*C.char)(d), C.int(l))
	return msg, nil
}

func (j *Journal) GetRealtimeUsec() (uint64, error) {
	var usec C.uint64_t

	r := C.sd_journal_get_realtime_usec(j.cjournal, &usec)

	if r < 0 {
		return 0, fmt.Errorf("error getting timestamp for entry: %d", r)
	}

	return uint64(usec), nil
}

func (j *Journal) Print(priority int, message string) error {
	m := C.CString(message)
	defer C.free(unsafe.Pointer(m))

	err := C.go_sd_journal_print(C.LOG_INFO, m)

	if err != 0 {
		return fmt.Errorf("failed to print message: %s", err)
	}

	return nil
}

func (j *Journal) SeekTail() error {
	err := C.sd_journal_seek_tail(j.cjournal)

	if err != 0 {
		return fmt.Errorf("failed to seek to tail of journal: %s", err)
	}

	return nil
}

func (j *Journal) SeekRealtimeUsec(usec uint64) error {
	err := C.sd_journal_seek_realtime_usec(j.cjournal, C.uint64_t(usec))

	if err != 0 {
		return fmt.Errorf("failed to seek to %d: %d", usec, int(err))
	}

	return nil
}

func (j *Journal) Wait(timeout uint64) int {
	r := C.sd_journal_wait(j.cjournal, C.uint64_t(timeout))

	return int(r)
}
<commit_msg>journal: serialize sd_journal C API use with mutex<commit_after>/* Provides a low-level Go interface to the systemd journal C API.

All public methods map closely to the sd-journal API functions. See the
sd-journal.h documentation[1] for information about each function.

[1] http://www.freedesktop.org/software/systemd/man/sd-journal.html
*/
package journal

/*
#cgo pkg-config: libsystemd-journal
#include <systemd/sd-journal.h>
#include <stdlib.h>
#include <syslog.h>

int go_sd_journal_print(int priority, char* s) {
	int r;
	r = sd_journal_print(priority, "%s", s);
	return r;
}
*/
import "C"
import (
	"fmt"
	"sync"
	"time"
	"unsafe"
)

// Journal entry field strings which correspond to:
// http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html
const (
	SD_JOURNAL_FIELD_SYSTEMD_UNIT = "_SYSTEMD_UNIT"
	SD_JOURNAL_FIELD_MESSAGE      = "MESSAGE"
	SD_JOURNAL_FIELD_PID          = "_PID"
	SD_JOURNAL_FIELD_UID          = "_UID"
	SD_JOURNAL_FIELD_GID          = "_GID"
	SD_JOURNAL_FIELD_HOSTNAME     = "_HOSTNAME"
	SD_JOURNAL_FIELD_MACHINE_ID   = "_MACHINE_ID"
)

// Journal event constants
const (
	SD_JOURNAL_NOP        = int(C.SD_JOURNAL_NOP)
	SD_JOURNAL_APPEND     = int(C.SD_JOURNAL_APPEND)
	SD_JOURNAL_INVALIDATE = int(C.SD_JOURNAL_INVALIDATE)
)

// A Journal is a Go wrapper of an sd_journal structure.
type Journal struct {
	cjournal *C.sd_journal
	mu       sync.Mutex
}

// A Match is a convenience wrapper to describe filters supplied to AddMatch.
type Match struct {
	Field string
	Value string
}

// String returns a string representation of a Match suitable for use with AddMatch.
func (m *Match) String() string {
	return m.Field + "=" + m.Value
}

// NewJournal returns a new Journal instance pointing to the local journal
func NewJournal() (*Journal, error) {
	j := &Journal{}
	err := C.sd_journal_open(&j.cjournal, C.SD_JOURNAL_LOCAL_ONLY)

	if err < 0 {
		return nil, fmt.Errorf("failed to open journal: %s", err)
	}

	return j, nil
}

func (j *Journal) Close() error {
	j.mu.Lock()
	C.sd_journal_close(j.cjournal)
	j.mu.Unlock()
	return nil
}

func (j *Journal) AddMatch(match string) error {
	m := C.CString(match)
	defer C.free(unsafe.Pointer(m))

	j.mu.Lock()
	C.sd_journal_add_match(j.cjournal, unsafe.Pointer(m), C.size_t(len(match)))
	j.mu.Unlock()

	return nil
}

func (j *Journal) Next() (int, error) {
	j.mu.Lock()
	r := C.sd_journal_next(j.cjournal)
	j.mu.Unlock()

	if r < 0 {
		return int(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return int(r), nil
}

func (j *Journal) Previous() (uint64, error) {
	j.mu.Lock()
	r := C.sd_journal_previous(j.cjournal)
	j.mu.Unlock()

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) PreviousSkip(skip uint64) (uint64, error) {
	j.mu.Lock()
	r := C.sd_journal_previous_skip(j.cjournal, C.uint64_t(skip))
	j.mu.Unlock()

	if r < 0 {
		return uint64(r), fmt.Errorf("failed to iterate journal: %d", r)
	}

	return uint64(r), nil
}

func (j *Journal) GetData(field string) (string, error) {
	f := C.CString(field)
	defer C.free(unsafe.Pointer(f))

	var d unsafe.Pointer
	var l C.size_t

	j.mu.Lock()
	err := C.sd_journal_get_data(j.cjournal, f, &d, &l)
	j.mu.Unlock()

	if err < 0 {
		return "", fmt.Errorf("failed to read message: %d", err)
	}

	msg := C.GoStringN((*C.char)(d), C.int(l))
	return msg, nil
}

func (j *Journal) GetRealtimeUsec() (uint64, error) {
	var usec C.uint64_t

	j.mu.Lock()
	r := C.sd_journal_get_realtime_usec(j.cjournal, &usec)
	j.mu.Unlock()

	if r < 0 {
		return 0, fmt.Errorf("error getting timestamp for entry: %d", r)
	}

	return uint64(usec), nil
}

func (j *Journal) Print(priority int, message string) error {
	m := C.CString(message)
	defer C.free(unsafe.Pointer(m))

	err := C.go_sd_journal_print(C.LOG_INFO, m)

	if err != 0 {
		return fmt.Errorf("failed to print message: %s", err)
	}

	return nil
}

func (j *Journal) SeekTail() error {
	j.mu.Lock()
	err := C.sd_journal_seek_tail(j.cjournal)
	j.mu.Unlock()

	if err != 0 {
		return fmt.Errorf("failed to seek to tail of journal: %s", err)
	}

	return nil
}

func (j *Journal) SeekRealtimeUsec(usec uint64) error {
	j.mu.Lock()
	err := C.sd_journal_seek_realtime_usec(j.cjournal, C.uint64_t(usec))
	j.mu.Unlock()

	if err != 0 {
		return fmt.Errorf("failed to seek to %d: %d", usec, int(err))
	}

	return nil
}

func (j *Journal) Wait(timeout time.Duration) int {
	to := uint64(time.Now().Add(timeout).Unix() / 1000)
	j.mu.Lock()
	r := C.sd_journal_wait(j.cjournal, C.uint64_t(to))
	j.mu.Unlock()

	return int(r)
}
<|endoftext|>


<commit_before>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 lib

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

	piazza "github.com/venicegeo/pz-gocommon"
)

type Client struct {
	url            string
	serviceName    piazza.ServiceName
	serviceAddress string
}

func NewClient(sys *piazza.SystemConfig) (*Client, error) {
	var _ IClient = new(Client)

	var err error

	url, err := sys.GetURL(piazza.PzLogger)
	if err != nil {
		return nil, err
	}

	service := &Client{
		url:            url,
		serviceName:    "notset",
		serviceAddress: "0.0.0.0",
	}

	err = sys.WaitForService(piazza.PzLogger)
	if err != nil {
		return nil, err
	}

	return service, nil
}

func (c *Client) GetFromMessages() ([]Message, error) {

	resp, err := http.Get(c.url + "/messages")
	if err != nil {
		return nil, err
	}

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

	if resp.StatusCode != http.StatusOK {
		return nil, err
	}

	var mssgs []Message
	err = json.Unmarshal(data, &mssgs)
	if err != nil {
		return nil, err
	}

	return mssgs, nil
}

func (c *Client) GetFromAdminStats() (*LoggerAdminStats, error) {

	resp, err := http.Get(c.url + "/admin/stats")
	if err != nil {
		return nil, err
	}

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

	stats := new(LoggerAdminStats)
	err = json.Unmarshal(data, stats)
	if err != nil {
		return nil, err
	}

	return stats, nil
}

func (c *Client) GetFromAdminSettings() (*LoggerAdminSettings, error) {

	resp, err := http.Get(c.url + "/admin/settings")
	if err != nil {
		return nil, err
	}

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

	settings := new(LoggerAdminSettings)
	err = json.Unmarshal(data, settings)
	if err != nil {
		return nil, err
	}

	return settings, nil
}

func (c *Client) PostToAdminSettings(settings *LoggerAdminSettings) error {

	data, err := json.Marshal(settings)
	if err != nil {
		return err
	}

	resp, err := http.Post(c.url+"/admin/settings", piazza.ContentTypeJSON, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New(resp.Status)
	}

	return nil
}

///////////////////

func (pz *Client) LogMessage(mssg *Message) error {

	data, err := json.Marshal(mssg)
	if err != nil {
		return err
	}

	resp, err := http.Post(pz.url+"/messages", piazza.ContentTypeJSON, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New(resp.Status)
	}

	return nil
}

// Log sends the components of a LogMessage to the logger.
func (pz *Client) Log(
	service piazza.ServiceName,
	address string,
	severity Severity,
	t time.Time,
	message string, v ...interface{}) error {

	str := fmt.Sprintf(message, v...)
	mssg := Message{Service: service, Address: address, Severity: severity, Time: t, Message: str}

	return pz.LogMessage(&mssg)
}

func (logger *Client) SetService(name piazza.ServiceName, address string) {
	logger.serviceName = name
	logger.serviceAddress = address
}

func (logger *Client) post(severity Severity, message string, v ...interface{}) error {
	str := fmt.Sprintf(message, v...)
	return logger.Log(logger.serviceName, logger.serviceAddress, severity, time.Now(), str)
}

// Debug sends a Debug-level message to the logger.
func (logger *Client) Debug(message string, v ...interface{}) error {
	return logger.post(SeverityDebug, message, v...)
}

// Info sends an Info-level message to the logger.
func (logger *Client) Info(message string, v ...interface{}) error {
	return logger.post(SeverityInfo, message, v...)
}

// Warn sends a Waring-level message to the logger.
func (logger *Client) Warn(message string, v ...interface{}) error {
	return logger.post(SeverityWarning, message, v...)
}

// Error sends a Error-level message to the logger.
func (logger *Client) Error(message string, v ...interface{}) error {
	return logger.post(SeverityError, message, v...)
}

// Fatal sends a Fatal-level message to the logger.
func (logger *Client) Fatal(message string, v ...interface{}) error {
	return logger.post(SeverityFatal, message, v...)
}
<commit_msg>add extra checks<commit_after>// Copyright 2016, RadiantBlue Technologies, Inc.
//
// 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 lib

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

	piazza "github.com/venicegeo/pz-gocommon"
)

type Client struct {
	url            string
	serviceName    piazza.ServiceName
	serviceAddress string
}

func NewClient(sys *piazza.SystemConfig) (*Client, error) {
	var _ IClient = new(Client)

	var err error

	url, err := sys.GetURL(piazza.PzLogger)
	if err != nil {
		return nil, err
	}

	service := &Client{
		url:            url,
		serviceName:    "notset",
		serviceAddress: "0.0.0.0",
	}

	err = sys.WaitForService(piazza.PzLogger)
	if err != nil {
		return nil, err
	}

	return service, nil
}

func (c *Client) GetFromMessages() ([]Message, error) {

	resp, err := http.Get(c.url + "/messages")
	if err != nil {
		return nil, err
	}

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

	if resp.StatusCode != http.StatusOK {
		return nil, err
	}

	var mssgs []Message
	err = json.Unmarshal(data, &mssgs)
	if err != nil {
		return nil, err
	}

	return mssgs, nil
}

func (c *Client) GetFromAdminStats() (*LoggerAdminStats, error) {

	resp, err := http.Get(c.url + "/admin/stats")
	if err != nil {
		return nil, err
	}

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

	stats := new(LoggerAdminStats)
	err = json.Unmarshal(data, stats)
	if err != nil {
		return nil, err
	}

	return stats, nil
}

func (c *Client) GetFromAdminSettings() (*LoggerAdminSettings, error) {

	resp, err := http.Get(c.url + "/admin/settings")
	if err != nil {
		return nil, err
	}

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

	settings := new(LoggerAdminSettings)
	err = json.Unmarshal(data, settings)
	if err != nil {
		return nil, err
	}

	return settings, nil
}

func (c *Client) PostToAdminSettings(settings *LoggerAdminSettings) error {

	data, err := json.Marshal(settings)
	if err != nil {
		return err
	}

	resp, err := http.Post(c.url+"/admin/settings", piazza.ContentTypeJSON, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New(resp.Status)
	}

	return nil
}

///////////////////

func (pz *Client) LogMessage(mssg *Message) error {

	err := mssg.Validate()
	if err != nil {
		return errors.New("message did not validate")
	}

	data, err := json.Marshal(mssg)
	if err != nil {
		return err
	}

	resp, err := http.Post(pz.url+"/messages", piazza.ContentTypeJSON, bytes.NewBuffer(data))
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New(resp.Status)
	}

	return nil
}

// Log sends the components of a LogMessage to the logger.
func (pz *Client) Log(
	service piazza.ServiceName,
	address string,
	severity Severity,
	t time.Time,
	message string, v ...interface{}) error {

	str := fmt.Sprintf(message, v...)
	mssg := Message{Service: service, Address: address, Severity: severity, Time: t, Message: str}

	return pz.LogMessage(&mssg)
}

func (logger *Client) SetService(name piazza.ServiceName, address string) {
	logger.serviceName = name
	logger.serviceAddress = address
}

func (logger *Client) post(severity Severity, message string, v ...interface{}) error {
	str := fmt.Sprintf(message, v...)
	return logger.Log(logger.serviceName, logger.serviceAddress, severity, time.Now(), str)
}

// Debug sends a Debug-level message to the logger.
func (logger *Client) Debug(message string, v ...interface{}) error {
	return logger.post(SeverityDebug, message, v...)
}

// Info sends an Info-level message to the logger.
func (logger *Client) Info(message string, v ...interface{}) error {
	return logger.post(SeverityInfo, message, v...)
}

// Warn sends a Waring-level message to the logger.
func (logger *Client) Warn(message string, v ...interface{}) error {
	return logger.post(SeverityWarning, message, v...)
}

// Error sends a Error-level message to the logger.
func (logger *Client) Error(message string, v ...interface{}) error {
	return logger.post(SeverityError, message, v...)
}

// Fatal sends a Fatal-level message to the logger.
func (logger *Client) Fatal(message string, v ...interface{}) error {
	return logger.post(SeverityFatal, message, v...)
}
<|endoftext|>


<commit_before>package command

import (
	"flag"
	"fmt"
	"sort"
	"strings"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/gofmt"
	"github.com/ryanuber/columnize"
	gozk "github.com/samuel/go-zookeeper/zk"
)

type Consumers struct {
	Ui  cli.Ui
	Cmd string

	onlineOnly   bool
	groupPattern string
	byHost       bool
	cleanup      bool
	tableFmt     bool
	topicPattern string
}

func (this *Consumers) Run(args []string) (exitCode int) {
	var (
		cluster string
		zone    string
	)
	cmdFlags := flag.NewFlagSet("consumers", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.StringVar(&cluster, "c", "", "")
	cmdFlags.StringVar(&this.groupPattern, "g", "", "")
	cmdFlags.BoolVar(&this.tableFmt, "table", true, "")
	cmdFlags.BoolVar(&this.onlineOnly, "online", false, "")
	cmdFlags.BoolVar(&this.byHost, "byhost", false, "")
	cmdFlags.StringVar(&this.topicPattern, "t", "", "")
	cmdFlags.BoolVar(&this.cleanup, "cleanup", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if validateArgs(this, this.Ui).
		requireAdminRights("-cleanup").
		invalid(args) {
		return 2
	}

	if zone == "" {
		forSortedZones(func(zkzone *zk.ZkZone) {
			switch {
			case this.cleanup:
				this.cleanupStaleConsumerGroups(zkzone, cluster)
			case this.byHost:
				this.printConsumersByHost(zkzone, cluster)
			default:
				if this.tableFmt {
					this.printConsumersByGroupTable(zkzone, cluster)
				} else {
					this.printConsumersByGroup(zkzone, cluster)
				}
			}
		})

		return
	}

	zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
	switch {
	case this.cleanup:
		this.cleanupStaleConsumerGroups(zkzone, cluster)
	case this.byHost:
		this.printConsumersByHost(zkzone, cluster)
	default:
		if this.tableFmt {
			this.printConsumersByGroupTable(zkzone, cluster)
		} else {
			this.printConsumersByGroup(zkzone, cluster)
		}
	}

	return
}

func (this *Consumers) cleanupStaleConsumerGroups(zkzone *zk.ZkZone, clusterPattern string) {
	// what consumer groups are safe to delete?
	// 1. not online
	// 2. have no offsets
	this.Ui.Output(color.Blue(zkzone.Name()))

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		this.Ui.Output(strings.Repeat(" ", 4) + zkcluster.Name())
		consumerGroups := zkcluster.ConsumerGroups()
		for group, consumers := range consumerGroups {
			if len(consumers) > 0 {
				// this consumer group is online
				continue
			}

			_, _, err := zkzone.Conn().Children(zkcluster.ConsumerGroupOffsetPath(group))
			if err == nil {
				// have offsets, unsafe to delete
				continue
			}

			if err != gozk.ErrNoNode {
				// should never happen
				swallow(err)
			}

			// have no offsets, safe to delete
			yes, err := this.Ui.Ask(fmt.Sprintf("confirm to remove consumer group: %s? [y/N]", group))
			swallow(err)
			if strings.ToLower(yes) != "y" {
				this.Ui.Info(fmt.Sprintf("%s skipped", group))
				continue
			}

			// do delete this consumer group
			zkzone.DeleteRecursive(zkcluster.ConsumerGroupRoot(group))
			this.Ui.Info(fmt.Sprintf("%s deleted", group))
		}
	})
}

func (this *Consumers) printConsumersByHost(zkzone *zk.ZkZone, clusterPattern string) {
	outputs := make(map[string]map[string]map[string]int) // host: {cluster: {topic: count}}

	this.Ui.Output(color.Blue(zkzone.Name()))

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		consumerGroups := zkcluster.ConsumerGroups()
		for _, group := range consumerGroups {
			for _, c := range group {
				if _, present := outputs[c.Host()]; !present {
					outputs[c.Host()] = make(map[string]map[string]int)
				}

				if _, present := outputs[c.Host()][zkcluster.Name()]; !present {
					outputs[c.Host()][zkcluster.Name()] = make(map[string]int)
				}

				for topic, count := range c.Subscription {
					outputs[c.Host()][zkcluster.Name()][topic] += count
				}
			}
		}

	})

	sortedHosts := make([]string, 0, len(outputs))
	for host, _ := range outputs {
		sortedHosts = append(sortedHosts, host)
	}
	sort.Strings(sortedHosts)
	for _, host := range sortedHosts {
		tc := outputs[host]
		this.Ui.Output(fmt.Sprintf("%s %+v", color.Green("%22s", host), tc))
	}
}

func (this *Consumers) printConsumersByGroupTable(zkzone *zk.ZkZone, clusterPattern string) {
	lines := make([]string, 0)
	header := "Zone|Cluster|M|Host|ConsumerGroup|Topic/Partition|Offset|Uptime"
	lines = append(lines, header)

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		consumerGroups := zkcluster.ConsumerGroups()
		sortedGroups := make([]string, 0, len(consumerGroups))
		for group, _ := range consumerGroups {
			if !patternMatched(group, this.groupPattern) {
				continue
			}

			sortedGroups = append(sortedGroups, group)
		}

		sort.Strings(sortedGroups)
		for _, group := range sortedGroups {
			consumers := consumerGroups[group]
			if this.onlineOnly && len(consumers) == 0 {
				continue
			}

			if len(consumers) > 0 {
				// sort by host
				sortedIds := make([]string, 0)
				consumersMap := make(map[string]*zk.ConsumerZnode)
				for _, c := range consumers {
					sortedIds = append(sortedIds, c.Id)
					consumersMap[c.Id] = c
				}
				sort.Strings(sortedIds)

				for _, consumerId := range sortedIds {
					for _, offset := range this.displayGroupOffsets(zkcluster, group, false) {
						ownerByPartition := zkcluster.OwnersOfGroupByTopic(group, offset.topic)
						onlineSymbol := "◉"
						if ownerByPartition[offset.partitionId] == consumerId {
							onlineSymbol += "*"
						}
						c := consumersMap[consumerId]
						lines = append(lines,
							fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s",
								zkzone.Name(), zkcluster.Name(),
								onlineSymbol,
								c.Host(),
								group,
								fmt.Sprintf("%s/%s", offset.topic, offset.partitionId),
								offset.offset,
								gofmt.PrettySince(c.Uptime())))
					}
				}
			} else {
				// offline
				for _, offset := range this.displayGroupOffsets(zkcluster, group, false) {
					lines = append(lines,
						fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s",
							zkzone.Name(), zkcluster.Name(),
							"◎",
							" ",
							group, fmt.Sprintf("%s/%s", offset.topic, offset.partitionId),
							offset.offset, " "))
				}
			}
		}
	})

	this.Ui.Output(columnize.SimpleFormat(lines))
}

// Print all consumers of all clusters within a zone.
func (this *Consumers) printConsumersByGroup(zkzone *zk.ZkZone, clusterPattern string) {
	this.Ui.Output(color.Blue(zkzone.Name()))
	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		this.Ui.Output(strings.Repeat(" ", 4) + zkcluster.Name())
		consumerGroups := zkcluster.ConsumerGroups()
		sortedGroups := make([]string, 0, len(consumerGroups))
		for group, _ := range consumerGroups {
			if !patternMatched(group, this.groupPattern) {
				continue
			}

			sortedGroups = append(sortedGroups, group)
		}

		sort.Strings(sortedGroups)
		for _, group := range sortedGroups {
			consumers := consumerGroups[group]
			if len(consumers) > 0 {
				this.Ui.Output(fmt.Sprintf("\t%s %s", color.Green("☀"), group))

				// sort by host
				sortedIds := make([]string, 0)
				consumersMap := make(map[string]*zk.ConsumerZnode)
				for _, c := range consumers {
					sortedIds = append(sortedIds, c.Id)
					consumersMap[c.Id] = c
				}
				sort.Strings(sortedIds)
				for _, id := range sortedIds {
					this.Ui.Output(fmt.Sprintf("\t\t%s", consumersMap[id]))
				}

			} else if !this.onlineOnly {
				this.Ui.Output(fmt.Sprintf("\t%s %s", color.Yellow("☔"), group))
			}

			this.displayGroupOffsets(zkcluster, group, true)
		}
	})

}

type consumerGroupOffset struct {
	topic, partitionId string
	offset             string // comma fmt
}

func (this *Consumers) displayGroupOffsets(zkcluster *zk.ZkCluster, group string, echo bool) []consumerGroupOffset {
	offsetMap := zkcluster.ConsumerOffsetsOfGroup(group)
	sortedTopics := make([]string, 0, len(offsetMap))
	for topic, _ := range offsetMap {
		sortedTopics = append(sortedTopics, topic)
	}
	sort.Strings(sortedTopics)

	r := make([]consumerGroupOffset, 0)

	for _, topic := range sortedTopics {
		if !patternMatched(topic, this.topicPattern) {
			continue
		}

		sortedPartitionIds := make([]string, 0, len(offsetMap[topic]))
		for partitionId, _ := range offsetMap[topic] {
			sortedPartitionIds = append(sortedPartitionIds, partitionId)
		}
		sort.Strings(sortedPartitionIds)

		for _, partitionId := range sortedPartitionIds {
			r = append(r, consumerGroupOffset{
				topic:       topic,
				partitionId: partitionId,
				offset:      gofmt.Comma(offsetMap[topic][partitionId]),
			})

			if echo {
				this.Ui.Output(fmt.Sprintf("\t\t%s/%s Offset:%s",
					topic, partitionId, gofmt.Comma(offsetMap[topic][partitionId])))
			}

		}
	}

	return r

}

func (*Consumers) Synopsis() string {
	return "Print high level consumer groups from Zookeeper"
}

func (this *Consumers) Help() string {
	help := fmt.Sprintf(`
Usage: %s consumers [options]

    Print high level consumer groups from Zookeeper

Options:

    -z zone
      Only print kafka controllers within this zone.

    -c cluster

    -g group name pattern

    -t topic pattern

    -online
      Only show online consumer groups.

    -table
      Display in table format.

    -cleanup
      Cleanup the stale consumer groups after confirmation.

    -byhost
      Display consumer groups by consumer hosts.

`, this.Cmd)
	return strings.TrimSpace(help)
}
<commit_msg>all displayed in table<commit_after>package command

import (
	"flag"
	"fmt"
	"sort"
	"strings"

	"github.com/funkygao/gafka/ctx"
	"github.com/funkygao/gafka/zk"
	"github.com/funkygao/gocli"
	"github.com/funkygao/golib/color"
	"github.com/funkygao/golib/gofmt"
	"github.com/ryanuber/columnize"
	gozk "github.com/samuel/go-zookeeper/zk"
)

type Consumers struct {
	Ui  cli.Ui
	Cmd string

	onlineOnly   bool
	groupPattern string
	byHost       bool
	cleanup      bool
	topicPattern string
}

func (this *Consumers) Run(args []string) (exitCode int) {
	var (
		cluster string
		zone    string
	)
	cmdFlags := flag.NewFlagSet("consumers", flag.ContinueOnError)
	cmdFlags.Usage = func() { this.Ui.Output(this.Help()) }
	cmdFlags.StringVar(&zone, "z", "", "")
	cmdFlags.StringVar(&cluster, "c", "", "")
	cmdFlags.StringVar(&this.groupPattern, "g", "", "")
	cmdFlags.BoolVar(&this.onlineOnly, "online", false, "")
	cmdFlags.BoolVar(&this.byHost, "byhost", false, "")
	cmdFlags.StringVar(&this.topicPattern, "t", "", "")
	cmdFlags.BoolVar(&this.cleanup, "cleanup", false, "")
	if err := cmdFlags.Parse(args); err != nil {
		return 1
	}

	if validateArgs(this, this.Ui).
		requireAdminRights("-cleanup").
		invalid(args) {
		return 2
	}

	if zone == "" {
		forSortedZones(func(zkzone *zk.ZkZone) {
			switch {
			case this.cleanup:
				this.cleanupStaleConsumerGroups(zkzone, cluster)
			case this.byHost:
				this.printConsumersByHost(zkzone, cluster)
			default:
				this.printConsumersByGroupTable(zkzone, cluster)
			}
		})

		return
	}

	zkzone := zk.NewZkZone(zk.DefaultConfig(zone, ctx.ZoneZkAddrs(zone)))
	switch {
	case this.cleanup:
		this.cleanupStaleConsumerGroups(zkzone, cluster)
	case this.byHost:
		this.printConsumersByHost(zkzone, cluster)
	default:
		this.printConsumersByGroupTable(zkzone, cluster)
	}

	return
}

func (this *Consumers) cleanupStaleConsumerGroups(zkzone *zk.ZkZone, clusterPattern string) {
	// what consumer groups are safe to delete?
	// 1. not online
	// 2. have no offsets
	this.Ui.Output(color.Blue(zkzone.Name()))

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		this.Ui.Output(strings.Repeat(" ", 4) + zkcluster.Name())
		consumerGroups := zkcluster.ConsumerGroups()
		for group, consumers := range consumerGroups {
			if len(consumers) > 0 {
				// this consumer group is online
				continue
			}

			_, _, err := zkzone.Conn().Children(zkcluster.ConsumerGroupOffsetPath(group))
			if err == nil {
				// have offsets, unsafe to delete
				continue
			}

			if err != gozk.ErrNoNode {
				// should never happen
				swallow(err)
			}

			// have no offsets, safe to delete
			yes, err := this.Ui.Ask(fmt.Sprintf("confirm to remove consumer group: %s? [y/N]", group))
			swallow(err)
			if strings.ToLower(yes) != "y" {
				this.Ui.Info(fmt.Sprintf("%s skipped", group))
				continue
			}

			// do delete this consumer group
			zkzone.DeleteRecursive(zkcluster.ConsumerGroupRoot(group))
			this.Ui.Info(fmt.Sprintf("%s deleted", group))
		}
	})
}

func (this *Consumers) printConsumersByHost(zkzone *zk.ZkZone, clusterPattern string) {
	outputs := make(map[string]map[string]map[string]int) // host: {cluster: {topic: count}}

	this.Ui.Output(color.Blue(zkzone.Name()))

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		consumerGroups := zkcluster.ConsumerGroups()
		for _, group := range consumerGroups {
			for _, c := range group {
				if _, present := outputs[c.Host()]; !present {
					outputs[c.Host()] = make(map[string]map[string]int)
				}

				if _, present := outputs[c.Host()][zkcluster.Name()]; !present {
					outputs[c.Host()][zkcluster.Name()] = make(map[string]int)
				}

				for topic, count := range c.Subscription {
					outputs[c.Host()][zkcluster.Name()][topic] += count
				}
			}
		}

	})

	sortedHosts := make([]string, 0, len(outputs))
	for host, _ := range outputs {
		sortedHosts = append(sortedHosts, host)
	}
	sort.Strings(sortedHosts)
	for _, host := range sortedHosts {
		tc := outputs[host]
		this.Ui.Output(fmt.Sprintf("%s %+v", color.Green("%22s", host), tc))
	}
}

func (this *Consumers) printConsumersByGroupTable(zkzone *zk.ZkZone, clusterPattern string) {
	lines := make([]string, 0)
	header := "Zone|Cluster|M|Host|ConsumerGroup|Topic/Partition|Offset|Uptime"
	lines = append(lines, header)

	zkzone.ForSortedClusters(func(zkcluster *zk.ZkCluster) {
		if !patternMatched(zkcluster.Name(), clusterPattern) {
			return
		}

		consumerGroups := zkcluster.ConsumerGroups()
		sortedGroups := make([]string, 0, len(consumerGroups))
		for group, _ := range consumerGroups {
			if !patternMatched(group, this.groupPattern) {
				continue
			}

			sortedGroups = append(sortedGroups, group)
		}

		sort.Strings(sortedGroups)
		for _, group := range sortedGroups {
			consumers := consumerGroups[group]
			if this.onlineOnly && len(consumers) == 0 {
				continue
			}

			if len(consumers) > 0 {
				// sort by host
				sortedIds := make([]string, 0)
				consumersMap := make(map[string]*zk.ConsumerZnode)
				for _, c := range consumers {
					sortedIds = append(sortedIds, c.Id)
					consumersMap[c.Id] = c
				}
				sort.Strings(sortedIds)

				for _, consumerId := range sortedIds {
					for _, offset := range this.displayGroupOffsets(zkcluster, group, false) {
						ownerByPartition := zkcluster.OwnersOfGroupByTopic(group, offset.topic)
						onlineSymbol := "◉"
						if ownerByPartition[offset.partitionId] == consumerId {
							onlineSymbol += "*"
						}
						c := consumersMap[consumerId]
						lines = append(lines,
							fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s",
								zkzone.Name(), zkcluster.Name(),
								onlineSymbol,
								c.Host(),
								group,
								fmt.Sprintf("%s/%s", offset.topic, offset.partitionId),
								offset.offset,
								gofmt.PrettySince(c.Uptime())))
					}
				}
			} else {
				// offline
				for _, offset := range this.displayGroupOffsets(zkcluster, group, false) {
					lines = append(lines,
						fmt.Sprintf("%s|%s|%s|%s|%s|%s|%s|%s",
							zkzone.Name(), zkcluster.Name(),
							"◎",
							" ",
							group, fmt.Sprintf("%s/%s", offset.topic, offset.partitionId),
							offset.offset, " "))
				}
			}
		}
	})

	this.Ui.Output(columnize.SimpleFormat(lines))
}

type consumerGroupOffset struct {
	topic, partitionId string
	offset             string // comma fmt
}

func (this *Consumers) displayGroupOffsets(zkcluster *zk.ZkCluster, group string, echo bool) []consumerGroupOffset {
	offsetMap := zkcluster.ConsumerOffsetsOfGroup(group)
	sortedTopics := make([]string, 0, len(offsetMap))
	for topic, _ := range offsetMap {
		sortedTopics = append(sortedTopics, topic)
	}
	sort.Strings(sortedTopics)

	r := make([]consumerGroupOffset, 0)

	for _, topic := range sortedTopics {
		if !patternMatched(topic, this.topicPattern) {
			continue
		}

		sortedPartitionIds := make([]string, 0, len(offsetMap[topic]))
		for partitionId, _ := range offsetMap[topic] {
			sortedPartitionIds = append(sortedPartitionIds, partitionId)
		}
		sort.Strings(sortedPartitionIds)

		for _, partitionId := range sortedPartitionIds {
			r = append(r, consumerGroupOffset{
				topic:       topic,
				partitionId: partitionId,
				offset:      gofmt.Comma(offsetMap[topic][partitionId]),
			})

			if echo {
				this.Ui.Output(fmt.Sprintf("\t\t%s/%s Offset:%s",
					topic, partitionId, gofmt.Comma(offsetMap[topic][partitionId])))
			}

		}
	}

	return r

}

func (*Consumers) Synopsis() string {
	return "Print high level consumer groups from Zookeeper"
}

func (this *Consumers) Help() string {
	help := fmt.Sprintf(`
Usage: %s consumers [options]

    Print high level consumer groups from Zookeeper

Options:

    -z zone
      Only print kafka controllers within this zone.

    -c cluster

    -g group name pattern

    -t topic pattern

    -online
      Only show online consumer groups.

    -cleanup
      Cleanup the stale consumer groups after confirmation.

    -byhost
      Display consumer groups by consumer hosts.

`, this.Cmd)
	return strings.TrimSpace(help)
}
<|endoftext|>