<commit_before>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2021 IBM, Inc.
 *
 */

package watcher

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

	k8sv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"
	kvv1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/client-go/log"
	"kubevirt.io/kubevirt/pkg/controller"
	objUtils "kubevirt.io/kubevirt/tools/perfscale-load-generator/object"
)

const (
	informerTimeout = time.Minute
)

type ObjListWatcher struct {
	sync.Mutex

	// runningObjs is a set to avoid recount duplicated events of an object
	runningObjs            map[string]bool
	desiredObjRunningCount int
	updateChannel          chan bool

	virtCli      kubecli.KubevirtClient
	listOpts     metav1.ListOptions
	ResourceKind string

	informer    cache.SharedInformer
	stopChannel chan struct{}
}

func NewObjListWatcher(virtCli kubecli.KubevirtClient, resourceKind string, objCount int, listOpts metav1.ListOptions) *ObjListWatcher {
	w := &ObjListWatcher{
		stopChannel:            make(chan struct{}),
		updateChannel:          make(chan bool),
		runningObjs:            make(map[string]bool),
		virtCli:                virtCli,
		ResourceKind:           resourceKind,
		desiredObjRunningCount: objCount,
		listOpts:               listOpts,
	}

	labelSelector, err := labels.Parse(w.listOpts.LabelSelector)
	if err != nil {
		panic(err)
	}
	objListWatcher := controller.NewListWatchFromClient(
		virtCli.RestClient(),
		w.ResourceKind,
		k8sv1.NamespaceAll,
		fields.Everything(),
		labelSelector)

	w.informer = cache.NewSharedInformer(objListWatcher, nil, 0)
	w.stopChannel = make(chan struct{})
	w.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		UpdateFunc: func(oldObj, newObj interface{}) {
			w.handleUpdate(newObj)
		},
		DeleteFunc: func(obj interface{}) {
			w.handleDeleted(obj)
		},
	})

	return w
}

func (w *ObjListWatcher) Run() {
	go w.informer.Run(w.stopChannel)
	timeoutCh := make(chan struct{})
	timeoutTimer := time.AfterFunc(informerTimeout, func() {
		close(timeoutCh)
	})
	defer timeoutTimer.Stop()
	if !cache.WaitForCacheSync(timeoutCh, w.informer.HasSynced) {
		panic(fmt.Errorf("watcher timed out waiting for caches to sync"))
	}
	// waiting 1s to prevent the watcher miss events from the test
	time.Sleep(1 * time.Second)
}

func (w *ObjListWatcher) Stop() {
	close(w.stopChannel)
}

func (w *ObjListWatcher) handleUpdate(obj interface{}) {
	switch w.ResourceKind {
	case objUtils.VMIResource:
		vmi, ok := obj.(*kvv1.VirtualMachineInstance)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstance obj: %v", w.ResourceKind)
		}
		if vmi.Status.Phase == kvv1.Running {
			w.addObj(string(vmi.GetUID()))
		}
		if vmi.Status.Phase == kvv1.Succeeded || vmi.Status.Phase == kvv1.Failed {
			log.Log.V(4).Errorf("VirtualMachineInstance obj: %v Succeeded, waiting for garbage collection", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name))
		}

	case objUtils.VMResource:
		vm, ok := obj.(*kvv1.VirtualMachine)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachine obj: %v", w.ResourceKind)
		}
		if vm.Status.Ready {
			w.addObj(string(vm.GetUID()))
		}

	case objUtils.VMIReplicaSetResource:
		replicaSet, ok := obj.(*kvv1.VirtualMachineInstanceReplicaSet)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstanceReplicaSet obj: %v", w.ResourceKind)
		}
		w.addObj(string(replicaSet.GetUID()))

	default:
		log.Log.V(2).Errorf("Watcher does not support object type %s", w.ResourceKind)
		return
	}
}

func (w *ObjListWatcher) handleDeleted(obj interface{}) {
	switch w.ResourceKind {
	case objUtils.VMResource:
		vm, ok := obj.(*kvv1.VirtualMachine)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachine obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(vm)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachine: %v, err: %#v", fmt.Sprintf("%s/%s", vm.Namespace, vm.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachine obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", vm.Namespace, vm.Name))
			return
		}
		w.deleteObj(string(vm.GetUID()))

	case objUtils.VMIReplicaSetResource:
		replicaSet, ok := obj.(*kvv1.VirtualMachineInstanceReplicaSet)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstanceReplicaSet obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(replicaSet)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachineInstanceReplicaSet: %v, err: %#v", fmt.Sprintf("%s/%s", replicaSet.Namespace, replicaSet.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachineInstanceReplicaSet obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", replicaSet.Namespace, replicaSet.Name))
			return
		}
		w.deleteObj(string(replicaSet.GetUID()))
	case objUtils.VMIResource:
		vmi, ok := obj.(*kvv1.VirtualMachineInstance)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstance obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(vmi)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachineInstance: %v, err: %#v", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachineInstance obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name))
			return
		}
		w.deleteObj(string(vmi.GetUID()))

	default:
		return
	}
}

func (w *ObjListWatcher) WaitRunning(timeout time.Duration) error {
	if w.getRunningCount() == w.desiredObjRunningCount {
		return nil
	}
	for {
		select {
		case <-time.After(timeout):
			return fmt.Errorf("timeout after %s waiting for objects be running", timeout)

		case <-w.updateChannel:
			count := w.getRunningCount()
			if count == w.desiredObjRunningCount {
				return nil
			}
			log.Log.V(6).Infof("Waiting %d %v to be Running", (w.desiredObjRunningCount - count), w.ResourceKind)
		}
	}
}

func (w *ObjListWatcher) WaitDeletion(timeout time.Duration) error {
	runningCount := w.getRunningCount()
	if runningCount == 0 || runningCount == w.desiredObjRunningCount {
		return nil
	}
	for {
		select {
		case <-time.After(timeout):
			return fmt.Errorf("timeout after %s waiting for objects be deleted", timeout)

		case <-w.updateChannel:
			count := w.getRunningCount()
			if count == 0 || count == w.desiredObjRunningCount {
				return nil
			}
			log.Log.V(6).Infof("Still %d %v waiting to be Garbage Collected", count, w.ResourceKind)
		}
	}
}

func (w *ObjListWatcher) addObj(id string) {
	w.Lock()
	w.runningObjs[id] = true
	w.Unlock()
	w.updateChannel <- true
}

func (w *ObjListWatcher) deleteObj(id string) {
	w.Lock()
	delete(w.runningObjs, id)
	w.Unlock()
	w.updateChannel <- true
}

func (w *ObjListWatcher) getRunningCount() int {
	var count int
	w.Lock()
	count = len(w.runningObjs)
	w.Unlock()
	return count
}

func (w *ObjListWatcher) isGarbageCollected(obj metav1.Object) (bool, error) {
	err := w.virtCli.RestClient().Get().
		Namespace(obj.GetNamespace()).
		Name(obj.GetName()).
		Resource(w.ResourceKind).
		Do(context.Background()).Error()
	switch {
	case err != nil && !errors.IsNotFound(err):
		log.Log.V(2).Errorf("Error deleting obj %s %s: %v", w.ResourceKind, obj.GetName(), err)
		return false, err
	case err != nil && errors.IsNotFound(err):
		return true, err
	default:
		return false, nil
	}
}
<commit_msg>update get to use cache instead of live apiserver<commit_after>/*
 * This file is part of the KubeVirt project
 *
 * 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.
 *
 * Copyright 2021 IBM, Inc.
 *
 */

package watcher

import (
	"fmt"
	"sync"
	"time"

	k8sv1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/fields"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/tools/cache"
	kvv1 "kubevirt.io/api/core/v1"
	"kubevirt.io/client-go/kubecli"
	"kubevirt.io/client-go/log"
	"kubevirt.io/kubevirt/pkg/controller"
	objUtils "kubevirt.io/kubevirt/tools/perfscale-load-generator/object"
)

const (
	informerTimeout = time.Minute
)

type ObjListWatcher struct {
	sync.Mutex

	// runningObjs is a set to avoid recount duplicated events of an object
	runningObjs            map[string]bool
	desiredObjRunningCount int
	updateChannel          chan bool

	virtCli      kubecli.KubevirtClient
	listOpts     metav1.ListOptions
	ResourceKind string

	informer    cache.SharedInformer
	stopChannel chan struct{}
}

func NewObjListWatcher(virtCli kubecli.KubevirtClient, resourceKind string, objCount int, listOpts metav1.ListOptions) *ObjListWatcher {
	w := &ObjListWatcher{
		stopChannel:            make(chan struct{}),
		updateChannel:          make(chan bool),
		runningObjs:            make(map[string]bool),
		virtCli:                virtCli,
		ResourceKind:           resourceKind,
		desiredObjRunningCount: objCount,
		listOpts:               listOpts,
	}

	labelSelector, err := labels.Parse(w.listOpts.LabelSelector)
	if err != nil {
		panic(err)
	}
	objListWatcher := controller.NewListWatchFromClient(
		virtCli.RestClient(),
		w.ResourceKind,
		k8sv1.NamespaceAll,
		fields.Everything(),
		labelSelector)

	w.informer = cache.NewSharedInformer(objListWatcher, nil, 0)
	w.stopChannel = make(chan struct{})
	w.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
		UpdateFunc: func(oldObj, newObj interface{}) {
			w.handleUpdate(newObj)
		},
		DeleteFunc: func(obj interface{}) {
			w.handleDeleted(obj)
		},
	})

	return w
}

func (w *ObjListWatcher) Run() {
	go w.informer.Run(w.stopChannel)
	timeoutCh := make(chan struct{})
	timeoutTimer := time.AfterFunc(informerTimeout, func() {
		close(timeoutCh)
	})
	defer timeoutTimer.Stop()
	if !cache.WaitForCacheSync(timeoutCh, w.informer.HasSynced) {
		panic(fmt.Errorf("watcher timed out waiting for caches to sync"))
	}
	// waiting 1s to prevent the watcher miss events from the test
	time.Sleep(1 * time.Second)
}

func (w *ObjListWatcher) Stop() {
	close(w.stopChannel)
}

func (w *ObjListWatcher) handleUpdate(obj interface{}) {
	switch w.ResourceKind {
	case objUtils.VMIResource:
		vmi, ok := obj.(*kvv1.VirtualMachineInstance)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstance obj: %v", w.ResourceKind)
		}
		if vmi.Status.Phase == kvv1.Running {
			w.addObj(w.getID(vmi))
		}
		if vmi.Status.Phase == kvv1.Succeeded || vmi.Status.Phase == kvv1.Failed {
			log.Log.V(4).Errorf("VirtualMachineInstance obj: %v Succeeded, waiting for garbage collection", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name))
		}

	case objUtils.VMResource:
		vm, ok := obj.(*kvv1.VirtualMachine)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachine obj: %v", w.ResourceKind)
		}
		if vm.Status.Ready {
			w.addObj(w.getID(vm))
		}

	case objUtils.VMIReplicaSetResource:
		replicaSet, ok := obj.(*kvv1.VirtualMachineInstanceReplicaSet)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstanceReplicaSet obj: %v", w.ResourceKind)
		}
		w.addObj(w.getID(replicaSet))

	default:
		log.Log.V(2).Errorf("Watcher does not support object type %s", w.ResourceKind)
		return
	}
}

func (w *ObjListWatcher) handleDeleted(obj interface{}) {
	switch w.ResourceKind {
	case objUtils.VMResource:
		vm, ok := obj.(*kvv1.VirtualMachine)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachine obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(vm)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachine: %v, err: %#v", fmt.Sprintf("%s/%s", vm.Namespace, vm.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachine obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", vm.Namespace, vm.Name))
			return
		}
		w.deleteObj(w.getID(vm))

	case objUtils.VMIReplicaSetResource:
		replicaSet, ok := obj.(*kvv1.VirtualMachineInstanceReplicaSet)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstanceReplicaSet obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(replicaSet)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachineInstanceReplicaSet: %v, err: %#v", fmt.Sprintf("%s/%s", replicaSet.Namespace, replicaSet.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachineInstanceReplicaSet obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", replicaSet.Namespace, replicaSet.Name))
			return
		}
		w.deleteObj(w.getID(replicaSet))
	case objUtils.VMIResource:
		vmi, ok := obj.(*kvv1.VirtualMachineInstance)
		if !ok {
			log.Log.V(2).Errorf("Could not convert VirtualMachineInstance obj: %v", w.ResourceKind)
			return
		}
		isGarbageCollected, err := w.isGarbageCollected(vmi)
		if err != nil {
			log.Log.V(2).Errorf("Could not get VirtualMachineInstance: %v, err: %#v", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name), err)
		}
		if !isGarbageCollected {
			log.Log.V(4).Errorf("VirtualMachineInstance obj: %v has not been garbage collected yet", fmt.Sprintf("%s/%s", vmi.Namespace, vmi.Name))
			return
		}
		w.deleteObj(w.getID(vmi))

	default:
		return
	}
}

func (w *ObjListWatcher) WaitRunning(timeout time.Duration) error {
	if w.getRunningCount() == w.desiredObjRunningCount {
		return nil
	}
	for {
		select {
		case <-time.After(timeout):
			return fmt.Errorf("timeout after %s waiting for objects be running", timeout)

		case <-w.updateChannel:
			count := w.getRunningCount()
			if count == w.desiredObjRunningCount {
				return nil
			}
			log.Log.V(6).Infof("Waiting %d %v to be Running", (w.desiredObjRunningCount - count), w.ResourceKind)
		}
	}
}

func (w *ObjListWatcher) WaitDeletion(timeout time.Duration) error {
	runningCount := w.getRunningCount()
	if runningCount == 0 || runningCount == w.desiredObjRunningCount {
		return nil
	}
	for {
		select {
		case <-time.After(timeout):
			return fmt.Errorf("timeout after %s waiting for objects be deleted", timeout)

		case <-w.updateChannel:
			count := w.getRunningCount()
			if count == 0 || count == w.desiredObjRunningCount {
				return nil
			}
			log.Log.V(6).Infof("Still %d %v waiting to be Garbage Collected", count, w.ResourceKind)
		}
	}
}

func (w *ObjListWatcher) addObj(id string) {
	w.Lock()
	w.runningObjs[id] = true
	w.Unlock()
	w.updateChannel <- true
}

func (w *ObjListWatcher) deleteObj(id string) {
	w.Lock()
	delete(w.runningObjs, id)
	w.Unlock()
	w.updateChannel <- true
}

func (w *ObjListWatcher) getRunningCount() int {
	var count int
	w.Lock()
	count = len(w.runningObjs)
	w.Unlock()
	return count
}

func (w *ObjListWatcher) isGarbageCollected(obj interface{}) (bool, error) {
	objKey, err := controller.KeyFunc(obj)
	if err != nil {
		log.Log.V(2).Errorf("Error getting obj key for %s err: %v", obj, err)
		return false, err
	}
	_, exists, err := w.informer.GetStore().GetByKey(objKey)
	switch {
	case err != nil:
		log.Log.V(2).Errorf("Error getting obj %s from cache err: %v", obj, err)
		return false, err
	case !exists:
		return true, nil
	default:
		return false, nil
	}
}

func (w *ObjListWatcher) getID(obj metav1.Object) string {
	return fmt.Sprintf("%s/%s/%s", w.ResourceKind, obj.GetNamespace(), obj.GetName())
}
<|endoftext|>


<commit_before>// A memcached binary protocol client.
package memcached

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"sync"

	"github.com/dustin/gomemcached"
)

const bufsize = 1024

// The Client itself.
type Client struct {
	conn io.ReadWriteCloser

	hdrBuf []byte
}

// Connect to a memcached server.
func Connect(prot, dest string) (rv *Client, err error) {
	conn, err := net.Dial(prot, dest)
	if err != nil {
		return nil, err
	}
	return &Client{
		conn:   conn,
		hdrBuf: make([]byte, gomemcached.HDR_LEN),
	}, nil
}

// Close the connection when you're done.
func (c *Client) Close() {
	c.conn.Close()
}

// Send a custom request and get the response.
func (client *Client) Send(req *gomemcached.MCRequest) (rv *gomemcached.MCResponse, err error) {
	err = transmitRequest(client.conn, req)
	if err != nil {
		return
	}
	return getResponse(client.conn, client.hdrBuf)
}

// Send a request, but do not wait for a response.
func (client *Client) Transmit(req *gomemcached.MCRequest) error {
	return transmitRequest(client.conn, req)
}

// Receive a response
func (client *Client) Receive() (*gomemcached.MCResponse, error) {
	return getResponse(client.conn, client.hdrBuf)
}

// Get the value for a key.
func (client *Client) Get(vb uint16, key string) (*gomemcached.MCResponse, error) {
	return client.Send(&gomemcached.MCRequest{
		Opcode:  gomemcached.GET,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{},
		Body:    []byte{}})
}

// Delete a key.
func (client *Client) Del(vb uint16, key string) (*gomemcached.MCResponse, error) {
	return client.Send(&gomemcached.MCRequest{
		Opcode:  gomemcached.DELETE,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{},
		Body:    []byte{}})
}

func (client *Client) store(opcode gomemcached.CommandCode, vb uint16,
	key string, flags int, exp int, body []byte) (*gomemcached.MCResponse, error) {

	req := &gomemcached.MCRequest{
		Opcode:  opcode,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{0, 0, 0, 0, 0, 0, 0, 0},
		Body:    body}

	binary.BigEndian.PutUint64(req.Extras, uint64(flags)<<32|uint64(exp))
	return client.Send(req)
}

// Increment a value.
func (client *Client) Incr(vb uint16, key string,
	amt, def uint64, exp int) (*gomemcached.MCResponse, error) {

	req := &gomemcached.MCRequest{
		Opcode:  gomemcached.INCREMENT,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  make([]byte, 8+8+4),
		Body:    []byte{}}
	binary.BigEndian.PutUint64(req.Extras[:8], amt)
	binary.BigEndian.PutUint64(req.Extras[8:16], def)
	binary.BigEndian.PutUint32(req.Extras[16:20], uint32(exp))
	return client.Send(req)
}

// Add a value for a key (store if not exists).
func (client *Client) Add(vb uint16, key string, flags int, exp int,
	body []byte) (*gomemcached.MCResponse, error) {
	return client.store(gomemcached.ADD, vb, key, flags, exp, body)
}

// Set the value for a key.
func (client *Client) Set(vb uint16, key string, flags int, exp int,
	body []byte) (*gomemcached.MCResponse, error) {
	return client.store(gomemcached.SET, vb, key, flags, exp, body)
}

// Get keys in bulk
func (client *Client) GetBulk(vb uint16, keys []string) (map[string]*gomemcached.MCResponse, error) {
	terminalOpaque := uint32(len(keys) + 5)
	rv := map[string]*gomemcached.MCResponse{}
	wg := sync.WaitGroup{}
	going := true

	defer func() {
		going = false
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		for going {
			res, err := client.Receive()
			if err != nil {
				return
			}
			if res.Opaque == terminalOpaque {
				return
			}
			rv[keys[res.Opaque]] = res
		}
	}()

	for i, k := range keys {
		err := client.Transmit(&gomemcached.MCRequest{
			Opcode:  gomemcached.GETQ,
			VBucket: vb,
			Key:     []byte(k),
			Cas:     0,
			Opaque:  uint32(i),
			Extras:  []byte{},
			Body:    []byte{}})
		if err != nil {
			return rv, err
		}
	}

	err := client.Transmit(&gomemcached.MCRequest{
		Opcode: gomemcached.NOOP,
		Key:    []byte{},
		Cas:    0,
		Extras: []byte{},
		Body:   []byte{},
		Opaque: terminalOpaque})
	if err != nil {
		return rv, err
	}

	wg.Wait()

	return rv, nil
}

// Stats returns a slice of these.
type StatValue struct {
	// The stat key
	Key string
	// The stat value
	Val string
}

// Get stats from the server
// use "" as the stat key for toplevel stats.
func (client *Client) Stats(key string) ([]StatValue, error) {
	rv := make([]StatValue, 0, 128)

	req := &gomemcached.MCRequest{
		Opcode:  gomemcached.STAT,
		VBucket: 0,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  918494,
		Extras:  []byte{}}

	err := transmitRequest(client.conn, req)
	if err != nil {
		return rv, err
	}

	for {
		res, err := getResponse(client.conn, client.hdrBuf)
		if err != nil {
			return rv, err
		}
		k := string(res.Key)
		if k == "" {
			break
		}
		rv = append(rv, StatValue{
			Key: k,
			Val: string(res.Body),
		})
	}

	return rv, nil
}

// Get the stats from the server as a map
func (client *Client) StatsMap(key string) (map[string]string, error) {
	rv := make(map[string]string)
	st, err := client.Stats(key)
	if err != nil {
		return rv, err
	}
	for _, sv := range st {
		rv[sv.Key] = sv.Val
	}
	return rv, nil
}

func getResponse(s io.Reader, buf []byte) (rv *gomemcached.MCResponse, err error) {
	_, err = io.ReadFull(s, buf)
	if err != nil {
		return rv, err
	}
	rv, err = grokHeader(buf)
	if err != nil {
		return rv, err
	}
	err = readContents(s, rv)
	return rv, err
}

func readContents(s io.Reader, res *gomemcached.MCResponse) error {
	if len(res.Extras) > 0 {
		_, err := io.ReadFull(s, res.Extras)
		if err != nil {
			return err
		}
	}
	if len(res.Key) > 0 {
		_, err := io.ReadFull(s, res.Key)
		if err != nil {
			return err
		}
	}
	_, err := io.ReadFull(s, res.Body)
	return err
}

func grokHeader(hdrBytes []byte) (rv *gomemcached.MCResponse, err error) {
	if hdrBytes[0] != gomemcached.RES_MAGIC {
		return rv, fmt.Errorf("Bad magic: 0x%02x", hdrBytes[0])
	}
	rv = &gomemcached.MCResponse{
		Opcode: gomemcached.CommandCode(hdrBytes[1]),
		Key:    make([]byte, binary.BigEndian.Uint16(hdrBytes[2:4])),
		Extras: make([]byte, hdrBytes[4]),
		Status: gomemcached.Status(binary.BigEndian.Uint16(hdrBytes[6:8])),
		Opaque: binary.BigEndian.Uint32(hdrBytes[12:16]),
		Cas:    binary.BigEndian.Uint64(hdrBytes[16:24]),
	}
	bodyLen := binary.BigEndian.Uint32(hdrBytes[8:12]) -
		uint32(len(rv.Key)+len(rv.Extras))
	rv.Body = make([]byte, bodyLen)

	return
}

func transmitRequest(o io.Writer, req *gomemcached.MCRequest) (err error) {
	if len(req.Body) < 128 {
		_, err = o.Write(req.Bytes())
	} else {
		_, err = o.Write(req.HeaderBytes())
		if err == nil && len(req.Body) > 0 {
			_, err = o.Write(req.Body)
		}
	}
	return
}
<commit_msg>Incr shoudl return a uint64<commit_after>// A memcached binary protocol client.
package memcached

import (
	"encoding/binary"
	"fmt"
	"io"
	"net"
	"sync"

	"github.com/dustin/gomemcached"
)

const bufsize = 1024

// The Client itself.
type Client struct {
	conn io.ReadWriteCloser

	hdrBuf []byte
}

// Connect to a memcached server.
func Connect(prot, dest string) (rv *Client, err error) {
	conn, err := net.Dial(prot, dest)
	if err != nil {
		return nil, err
	}
	return &Client{
		conn:   conn,
		hdrBuf: make([]byte, gomemcached.HDR_LEN),
	}, nil
}

// Close the connection when you're done.
func (c *Client) Close() {
	c.conn.Close()
}

// Send a custom request and get the response.
func (client *Client) Send(req *gomemcached.MCRequest) (rv *gomemcached.MCResponse, err error) {
	err = transmitRequest(client.conn, req)
	if err != nil {
		return
	}
	return getResponse(client.conn, client.hdrBuf)
}

// Send a request, but do not wait for a response.
func (client *Client) Transmit(req *gomemcached.MCRequest) error {
	return transmitRequest(client.conn, req)
}

// Receive a response
func (client *Client) Receive() (*gomemcached.MCResponse, error) {
	return getResponse(client.conn, client.hdrBuf)
}

// Get the value for a key.
func (client *Client) Get(vb uint16, key string) (*gomemcached.MCResponse, error) {
	return client.Send(&gomemcached.MCRequest{
		Opcode:  gomemcached.GET,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{},
		Body:    []byte{}})
}

// Delete a key.
func (client *Client) Del(vb uint16, key string) (*gomemcached.MCResponse, error) {
	return client.Send(&gomemcached.MCRequest{
		Opcode:  gomemcached.DELETE,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{},
		Body:    []byte{}})
}

func (client *Client) store(opcode gomemcached.CommandCode, vb uint16,
	key string, flags int, exp int, body []byte) (*gomemcached.MCResponse, error) {

	req := &gomemcached.MCRequest{
		Opcode:  opcode,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  []byte{0, 0, 0, 0, 0, 0, 0, 0},
		Body:    body}

	binary.BigEndian.PutUint64(req.Extras, uint64(flags)<<32|uint64(exp))
	return client.Send(req)
}

// Increment a value.
func (client *Client) Incr(vb uint16, key string,
	amt, def uint64, exp int) (uint64, error) {

	req := &gomemcached.MCRequest{
		Opcode:  gomemcached.INCREMENT,
		VBucket: vb,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  0,
		Extras:  make([]byte, 8+8+4),
		Body:    []byte{}}
	binary.BigEndian.PutUint64(req.Extras[:8], amt)
	binary.BigEndian.PutUint64(req.Extras[8:16], def)
	binary.BigEndian.PutUint32(req.Extras[16:20], uint32(exp))

	resp, err := client.Send(req)
	if err != nil {
		return 0, err
	}

	return binary.BigEndian.Uint64(resp.Body), nil
}

// Add a value for a key (store if not exists).
func (client *Client) Add(vb uint16, key string, flags int, exp int,
	body []byte) (*gomemcached.MCResponse, error) {
	return client.store(gomemcached.ADD, vb, key, flags, exp, body)
}

// Set the value for a key.
func (client *Client) Set(vb uint16, key string, flags int, exp int,
	body []byte) (*gomemcached.MCResponse, error) {
	return client.store(gomemcached.SET, vb, key, flags, exp, body)
}

// Get keys in bulk
func (client *Client) GetBulk(vb uint16, keys []string) (map[string]*gomemcached.MCResponse, error) {
	terminalOpaque := uint32(len(keys) + 5)
	rv := map[string]*gomemcached.MCResponse{}
	wg := sync.WaitGroup{}
	going := true

	defer func() {
		going = false
	}()

	wg.Add(1)
	go func() {
		defer wg.Done()
		for going {
			res, err := client.Receive()
			if err != nil {
				return
			}
			if res.Opaque == terminalOpaque {
				return
			}
			rv[keys[res.Opaque]] = res
		}
	}()

	for i, k := range keys {
		err := client.Transmit(&gomemcached.MCRequest{
			Opcode:  gomemcached.GETQ,
			VBucket: vb,
			Key:     []byte(k),
			Cas:     0,
			Opaque:  uint32(i),
			Extras:  []byte{},
			Body:    []byte{}})
		if err != nil {
			return rv, err
		}
	}

	err := client.Transmit(&gomemcached.MCRequest{
		Opcode: gomemcached.NOOP,
		Key:    []byte{},
		Cas:    0,
		Extras: []byte{},
		Body:   []byte{},
		Opaque: terminalOpaque})
	if err != nil {
		return rv, err
	}

	wg.Wait()

	return rv, nil
}

// Stats returns a slice of these.
type StatValue struct {
	// The stat key
	Key string
	// The stat value
	Val string
}

// Get stats from the server
// use "" as the stat key for toplevel stats.
func (client *Client) Stats(key string) ([]StatValue, error) {
	rv := make([]StatValue, 0, 128)

	req := &gomemcached.MCRequest{
		Opcode:  gomemcached.STAT,
		VBucket: 0,
		Key:     []byte(key),
		Cas:     0,
		Opaque:  918494,
		Extras:  []byte{}}

	err := transmitRequest(client.conn, req)
	if err != nil {
		return rv, err
	}

	for {
		res, err := getResponse(client.conn, client.hdrBuf)
		if err != nil {
			return rv, err
		}
		k := string(res.Key)
		if k == "" {
			break
		}
		rv = append(rv, StatValue{
			Key: k,
			Val: string(res.Body),
		})
	}

	return rv, nil
}

// Get the stats from the server as a map
func (client *Client) StatsMap(key string) (map[string]string, error) {
	rv := make(map[string]string)
	st, err := client.Stats(key)
	if err != nil {
		return rv, err
	}
	for _, sv := range st {
		rv[sv.Key] = sv.Val
	}
	return rv, nil
}

func getResponse(s io.Reader, buf []byte) (rv *gomemcached.MCResponse, err error) {
	_, err = io.ReadFull(s, buf)
	if err != nil {
		return rv, err
	}
	rv, err = grokHeader(buf)
	if err != nil {
		return rv, err
	}
	err = readContents(s, rv)
	return rv, err
}

func readContents(s io.Reader, res *gomemcached.MCResponse) error {
	if len(res.Extras) > 0 {
		_, err := io.ReadFull(s, res.Extras)
		if err != nil {
			return err
		}
	}
	if len(res.Key) > 0 {
		_, err := io.ReadFull(s, res.Key)
		if err != nil {
			return err
		}
	}
	_, err := io.ReadFull(s, res.Body)
	return err
}

func grokHeader(hdrBytes []byte) (rv *gomemcached.MCResponse, err error) {
	if hdrBytes[0] != gomemcached.RES_MAGIC {
		return rv, fmt.Errorf("Bad magic: 0x%02x", hdrBytes[0])
	}
	rv = &gomemcached.MCResponse{
		Opcode: gomemcached.CommandCode(hdrBytes[1]),
		Key:    make([]byte, binary.BigEndian.Uint16(hdrBytes[2:4])),
		Extras: make([]byte, hdrBytes[4]),
		Status: gomemcached.Status(binary.BigEndian.Uint16(hdrBytes[6:8])),
		Opaque: binary.BigEndian.Uint32(hdrBytes[12:16]),
		Cas:    binary.BigEndian.Uint64(hdrBytes[16:24]),
	}
	bodyLen := binary.BigEndian.Uint32(hdrBytes[8:12]) -
		uint32(len(rv.Key)+len(rv.Extras))
	rv.Body = make([]byte, bodyLen)

	return
}

func transmitRequest(o io.Writer, req *gomemcached.MCRequest) (err error) {
	if len(req.Body) < 128 {
		_, err = o.Write(req.Bytes())
	} else {
		_, err = o.Write(req.HeaderBytes())
		if err == nil && len(req.Body) > 0 {
			_, err = o.Write(req.Body)
		}
	}
	return
}
<|endoftext|>


<commit_before>package model

import (
	"appengine"
	"appengine/datastore"
	"log"
	"strings"
)

type Season struct {
	Year string
	Name string
	Active bool
	Conferences []Conference
	Players []Player
}

func seasonKey(c appengine.Context, name string, year string) *datastore.Key {
	keyname := strings.Join([]string{name, year}, ":")
	return datastore.NewKey(c, "Season", keyname, 0, nil)
}

func SaveSeason(c appengine.Context, s Season) error {
	key := seasonKey(c, s.Name, s.Year)
	_, err := datastore.Put(c, key, &s)
	return err
}

func LoadSeason(c appengine.Context, name string, year string) *Season {
	key := seasonKey(c, name, year)
	var s Season
	err := datastore.Get(c, key, &s)
	if err == datastore.ErrNoSuchEntity {
		return nil
	} else if err != nil {
		log.Printf("Got an unexpected error looking up a season: %v", err)
	}
	return &s
}

func LoadCurrentSeason(c appengine.Context) *Season {
	q := datastore.NewQuery("Season").Filter("Active =", true)
	var seasons []Season
	_, err := q.GetAll(c, &seasons)
	if err != nil {
		log.Printf("Error loading current season %v", err)
		return nil
	}
	if len(seasons) == 0 {
		return nil
	}
	return &(seasons[0])
}

func LoadAllSeasons(c appengine.Context) []Season {
	q := datastore.NewQuery("Season")
	var seasons []Season
	_, err := q.GetAll(c, &seasons)
	if err != nil {
		log.Printf("Error loading all seasons '%v'", err)
		return []Season{}
	}
	return seasons
}
<commit_msg>Whoops - removed players from the Season entity<commit_after>package model

import (
	"appengine"
	"appengine/datastore"
	"log"
	"strings"
)

type Season struct {
	Year string
	Name string
	Active bool
	Conferences []Conference
}

func seasonKey(c appengine.Context, name string, year string) *datastore.Key {
	keyname := strings.Join([]string{name, year}, ":")
	return datastore.NewKey(c, "Season", keyname, 0, nil)
}

func SaveSeason(c appengine.Context, s Season) error {
	key := seasonKey(c, s.Name, s.Year)
	_, err := datastore.Put(c, key, &s)
	return err
}

func LoadSeason(c appengine.Context, name string, year string) *Season {
	key := seasonKey(c, name, year)
	var s Season
	err := datastore.Get(c, key, &s)
	if err == datastore.ErrNoSuchEntity {
		return nil
	} else if err != nil {
		log.Printf("Got an unexpected error looking up a season: %v", err)
	}
	return &s
}

func LoadCurrentSeason(c appengine.Context) *Season {
	q := datastore.NewQuery("Season").Filter("Active =", true)
	var seasons []Season
	_, err := q.GetAll(c, &seasons)
	if err != nil {
		log.Printf("Error loading current season %v", err)
		return nil
	}
	if len(seasons) == 0 {
		return nil
	}
	return &(seasons[0])
}

func LoadAllSeasons(c appengine.Context) []Season {
	q := datastore.NewQuery("Season")
	var seasons []Season
	_, err := q.GetAll(c, &seasons)
	if err != nil {
		log.Printf("Error loading all seasons '%v'", err)
		return []Season{}
	}
	return seasons
}
<|endoftext|>


<commit_before>package model

import "sort"

// LessGame partial game comparison
type LessGame func(g1, g2 *Game) bool

// GameSorter sort games
type GameSorter []LessGame

// GameOrder returns a Sorter that sorts using the less functions, in order.
func GameOrder(less ...LessGame) GameSorter {
	return less
}

// ByDate newest first
func ByDate(g1, g2 *Game) bool {
	return g1.Date.After(g2.Date)
}

// ByDateDesc oldest first
func ByDateDesc(g1, g2 *Game) bool {
	return g1.Date.Before(g2.Date)
}

// ByHandicap lowest first
func ByHandicap(g1, g2 *Game) bool {
	return g1.Handicap < g2.Handicap
}

// ByHandicapDesc highest first
func ByHandicapDesc(g1, g2 *Game) bool {
	return g1.Handicap > g2.Handicap
}

// ByRank highest first
func ByRank(g1, g2 *Game) bool {
	return MaxRank(g1.Black, g1.White) > MaxRank(g2.Black, g2.White)
}

// ByRankDesc lowest first
func ByRankDesc(g1, g2 *Game) bool {
	return MinRank(g1.Black, g1.White) < MinRank(g2.Black, g2.White)
}

// BySize greatest first
func BySize(g1, g2 *Game) bool {
	return g1.Size > g2.Size
}

// BySizeDesc smallest first
func BySizeDesc(g1, g2 *Game) bool {
	return g1.Size < g2.Size
}

// Sort sorts the argument slice according to the less functions passed to OrderedBy.
func (s *GameSorter) Sort(games []Game) {
	sort.Sort(&gameSorter{games: games, less: *s})
}

// gameSorter implements the Sort interface, sorting the games within.
type gameSorter struct {
	games []Game
	less  []LessGame
}

// Len is part of sort.Interface.
func (gs *gameSorter) Len() int {
	return len(gs.games)
}

// Swap is part of sort.Interface.
func (gs *gameSorter) Swap(i, j int) {
	gs.games[i], gs.games[j] = gs.games[j], gs.games[i]
}

// Less is part of sort.Interface. It is implemented by looping along the
// less functions until it finds a comparison that is either Less or !Less.
func (gs *gameSorter) Less(i, j int) bool {
	g1, g2 := &gs.games[i], &gs.games[j]
	var l int // try all but the last
	for l = 0; l < len(gs.less)-1; l++ {
		less := gs.less[l]
		switch {
		case less(g1, g2):
			return true
		case less(g2, g1):
			return false
		}
	}
	// call last comparison
	return gs.less[l](g1, g2)
}
<commit_msg>use func vars<commit_after>package model

import "sort"

// LessGame partial game comparison
type LessGame func(g1, g2 *Game) bool

// GameSorter sort games
type GameSorter []LessGame

// GameOrder returns a Sorter that sorts using the less functions, in order.
func GameOrder(less ...LessGame) GameSorter {
	return less
}

// ByDate newest first
var ByDate = func(g1, g2 *Game) bool {
	return g1.Date.After(g2.Date)
}

// ByDateDesc oldest first
var ByDateDesc = func(g1, g2 *Game) bool {
	return g1.Date.Before(g2.Date)
}

// ByHandicap lowest first
var ByHandicap = func(g1, g2 *Game) bool {
	return g1.Handicap < g2.Handicap
}

// ByHandicapDesc highest first
var ByHandicapDesc = func(g1, g2 *Game) bool {
	return g1.Handicap > g2.Handicap
}

// ByRank highest first
var ByRank = func(g1, g2 *Game) bool {
	return MaxRank(g1.Black, g1.White) > MaxRank(g2.Black, g2.White)
}

// ByRankDesc lowest first
var ByRankDesc = func(g1, g2 *Game) bool {
	return MinRank(g1.Black, g1.White) < MinRank(g2.Black, g2.White)
}

// BySize greatest first
var BySize = func(g1, g2 *Game) bool {
	return g1.Size > g2.Size
}

// BySizeDesc smallest first
var BySizeDesc = func(g1, g2 *Game) bool {
	return g1.Size < g2.Size
}

// Sort sorts the argument slice according to the less functions passed to OrderedBy.
func (s *GameSorter) Sort(games []Game) {
	sort.Sort(&gameSorter{games: games, less: *s})
}

// gameSorter implements the Sort interface, sorting the games within.
type gameSorter struct {
	games []Game
	less  []LessGame
}

// Len is part of sort.Interface.
func (gs *gameSorter) Len() int {
	return len(gs.games)
}

// Swap is part of sort.Interface.
func (gs *gameSorter) Swap(i, j int) {
	gs.games[i], gs.games[j] = gs.games[j], gs.games[i]
}

// Less is part of sort.Interface. It is implemented by looping along the
// less functions until it finds a comparison that is either Less or !Less.
func (gs *gameSorter) Less(i, j int) bool {
	g1, g2 := &gs.games[i], &gs.games[j]
	var l int // try all but the last
	for l = 0; l < len(gs.less)-1; l++ {
		less := gs.less[l]
		switch {
		case less(g1, g2):
			return true
		case less(g2, g1):
			return false
		}
	}
	// call last comparison
	return gs.less[l](g1, g2)
}
<|endoftext|>


<commit_before>package models

import (
	"appengine"
	"appengine/datastore"
	"fmt"
	"github.com/russross/blackfriday"
	"html/template"
	"regexp"
	"time"
)

type Entry struct {
	Id       int64
	Title    string // optional
	Content  string `datastore:",noindex"` // Markdown
	Datetime time.Time
	Created  time.Time
	Modified time.Time
	Tags     []string
	Public   bool
	// TODO(icco): Define a meta field that is a json hash of extra data
}

var HashtagRegex *regexp.Regexp = regexp.MustCompile(`(\s)#(\w+)`)
var TwitterHandleRegex *regexp.Regexp = regexp.MustCompile(`(\s)@([_A-Za-z0-9]+)`)

func NewEntry(title string, content string, datetime time.Time, public bool, tags []string) *Entry {
	e := new(Entry)

	// User supplied content
	e.Title = title
	e.Content = content
	e.Datetime = datetime
	e.Tags = tags
	e.Public = public

	// Computer generated content
	e.Created = time.Now()
	e.Modified = time.Now()

	return e
}

func ParseTags(text string) ([]string, error) {
	// http://golang.org/pkg/regexp/#Regexp.FindAllStringSubmatch
	finds := HashtagRegex.FindAllStringSubmatch(text, -1)
	ret := make([]string, 0)
	for _, v := range finds {
		if len(v) > 2 {
			ret = append(ret, v[2])
		}
	}

	return ret, nil
}

func GetEntry(c appengine.Context, id int64) (*Entry, error) {
	var entry Entry
	q := datastore.NewQuery("Entry").Filter("Id =", id).Limit(1)
	k, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Warningf("Error getting entry %d", id)
		return nil, err
	}

	// Do this for a while, why not.
	tags, err := ParseTags(entry.Content)
	if err != nil {
		return nil, err
	}
	entry.Tags = tags
	_, err = datastore.Put(c, k, &entry)
	if err != nil {
		c.Warningf("Error resaving entry %d: %v", id, err)
		return nil, err
	}

	return &entry, nil
}

func MaxId(c appengine.Context) (int64, error) {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("-Id").Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		return 0, err
	}
	return entry.Id, nil
}

func AllPosts(c appengine.Context) (*[]Entry, error) {
	q := datastore.NewQuery("Entry").Order("-Datetime")
	entries := new([]Entry)
	_, err := q.GetAll(c, entries)
	return entries, err
}

func (e *Entry) HasId() bool {
	return (e.Id <= 0)
}

func (e *Entry) Save(c appengine.Context) error {
	var k *datastore.Key
	if e.HasId() {
		id, _ := MaxId(c)
		e.Id = id + 1
		k = datastore.NewIncompleteKey(c, "Entry", nil)
	} else {
		var err error
		q := datastore.NewQuery("Entry").Filter("Id =", e.Id).Limit(1).KeysOnly()
		k, err = q.Run(c).Next(nil)
		if err != nil {
			return err
		}
	}

	_, err := datastore.Put(c, k, e)
	if err == nil {
		c.Infof("Wrote %+v", e)
	} else {
		c.Warningf("Error writing entry: %v", e)
	}
	return err
}

func (e *Entry) Url() string {
	return fmt.Sprintf("/post/%d", e.Id)
}

func (e *Entry) EditUrl() string {
	return fmt.Sprintf("/edit/%d", e.Id)
}

func (e *Entry) Html() template.HTML {
	return Markdown(e.Content)
}

func (e *Entry) PrevPost(c appengine.Context) string {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("-Datetime").Filter("Datetime <", e.Datetime).Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Infof("Error getting previous post for %d.", e.Id)
		return ""
	}

	return entry.Url()
}

func (e *Entry) NextPost(c appengine.Context) string {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("Datetime").Filter("Datetime >", e.Datetime).Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Infof("Error getting next post for %d.", e.Id)
		return ""
	}

	return entry.Url()
}

// Markdown.
func Markdown(args ...interface{}) template.HTML {
	inc := []byte(fmt.Sprintf("%s", args...))
	inc = twitterHandleToMarkdown(inc)
	inc = hashTagsToMarkdown(inc)
	s := blackfriday.MarkdownCommon(inc)
	return template.HTML(s)
}

func twitterHandleToMarkdown(in []byte) []byte {
	return TwitterHandleRegex.ReplaceAll(in, []byte("$1[@$2](http://twitter.com/$2)"))
}

func hashTagsToMarkdown(in []byte) []byte {
	return HashtagRegex.ReplaceAll(in, []byte("$1[#$2](/tags/$2)"))
}
<commit_msg>comment<commit_after>package models

import (
	"appengine"
	"appengine/datastore"
	"fmt"
	"github.com/russross/blackfriday"
	"html/template"
	"regexp"
	"time"
)

type Entry struct {
	Id       int64
	Title    string // optional
	Content  string `datastore:",noindex"` // Markdown
	Datetime time.Time
	Created  time.Time
	Modified time.Time
	Tags     []string
	Public   bool
	// TODO(icco): Define a meta field that is a json hash of extra data
}

var HashtagRegex *regexp.Regexp = regexp.MustCompile(`(\s)#(\w+)`)
var TwitterHandleRegex *regexp.Regexp = regexp.MustCompile(`(\s)@([_A-Za-z0-9]+)`)

func NewEntry(title string, content string, datetime time.Time, public bool, tags []string) *Entry {
	e := new(Entry)

	// User supplied content
	e.Title = title
	e.Content = content
	e.Datetime = datetime
	e.Tags = tags
	e.Public = public

	// Computer generated content
	e.Created = time.Now()
	e.Modified = time.Now()

	return e
}

func ParseTags(text string) ([]string, error) {
	// http://golang.org/pkg/regexp/#Regexp.FindAllStringSubmatch
	finds := HashtagRegex.FindAllStringSubmatch(text, -1)
	ret := make([]string, 0)
	for _, v := range finds {
		if len(v) > 2 {
			ret = append(ret, v[2])
		}
	}

	return ret, nil
}

func GetEntry(c appengine.Context, id int64) (*Entry, error) {
	var entry Entry
	q := datastore.NewQuery("Entry").Filter("Id =", id).Limit(1)
	k, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Warningf("Error getting entry %d", id)
		return nil, err
	}

	// Do this for a while, why not.
	tags, err := ParseTags(entry.Content)
	if err != nil {
		return nil, err
	}
	entry.Tags = tags
	_, err = datastore.Put(c, k, &entry)
	if err != nil {
		c.Warningf("Error resaving entry %d: %v", id, err)
		return nil, err
	}

	return &entry, nil
}

func MaxId(c appengine.Context) (int64, error) {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("-Id").Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		return 0, err
	}
	return entry.Id, nil
}

func AllPosts(c appengine.Context) (*[]Entry, error) {
	q := datastore.NewQuery("Entry").Order("-Datetime")
	entries := new([]Entry)
	_, err := q.GetAll(c, entries)
	return entries, err
}

func (e *Entry) HasId() bool {
	return (e.Id <= 0)
}

func (e *Entry) Save(c appengine.Context) error {
	// Note to self: This logic is fucked.
	var k *datastore.Key
	if e.HasId() {
		id, _ := MaxId(c)
		e.Id = id + 1
		k = datastore.NewIncompleteKey(c, "Entry", nil)
	} else {
		var err error
		q := datastore.NewQuery("Entry").Filter("Id =", e.Id).Limit(1).KeysOnly()
		k, err = q.Run(c).Next(nil)
		if err != nil {
			return err
		}
	}

	_, err := datastore.Put(c, k, e)
	if err == nil {
		c.Infof("Wrote %+v", e)
	} else {
		c.Warningf("Error writing entry: %v", e)
	}
	return err
}

func (e *Entry) Url() string {
	return fmt.Sprintf("/post/%d", e.Id)
}

func (e *Entry) EditUrl() string {
	return fmt.Sprintf("/edit/%d", e.Id)
}

func (e *Entry) Html() template.HTML {
	return Markdown(e.Content)
}

func (e *Entry) PrevPost(c appengine.Context) string {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("-Datetime").Filter("Datetime <", e.Datetime).Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Infof("Error getting previous post for %d.", e.Id)
		return ""
	}

	return entry.Url()
}

func (e *Entry) NextPost(c appengine.Context) string {
	var entry Entry
	q := datastore.NewQuery("Entry").Order("Datetime").Filter("Datetime >", e.Datetime).Limit(1)
	_, err := q.Run(c).Next(&entry)
	if err != nil {
		c.Infof("Error getting next post for %d.", e.Id)
		return ""
	}

	return entry.Url()
}

// Markdown.
func Markdown(args ...interface{}) template.HTML {
	inc := []byte(fmt.Sprintf("%s", args...))
	inc = twitterHandleToMarkdown(inc)
	inc = hashTagsToMarkdown(inc)
	s := blackfriday.MarkdownCommon(inc)
	return template.HTML(s)
}

func twitterHandleToMarkdown(in []byte) []byte {
	return TwitterHandleRegex.ReplaceAll(in, []byte("$1[@$2](http://twitter.com/$2)"))
}

func hashTagsToMarkdown(in []byte) []byte {
	return HashtagRegex.ReplaceAll(in, []byte("$1[#$2](/tags/$2)"))
}
<|endoftext|>


<commit_before>package models

import (
	"github.com/codegangsta/martini-contrib/binding"
	"gopkg.in/mgo.v2/bson"
	"net/http"
	"time"
)

type Event struct {
	Id          bson.ObjectId `json:"id,omitempty"	bson:"_id,omitempty"`
	Title       string        `json:"title"			bson:"title"`
	Description string        `json:"description"	bson:"description"`
	Coordinates []float64     `json:"coordinates"	bson:"coordinates"`
	CreatedBy   bson.ObjectId `json:"created_by"	bson:"created_by"`
	CreatedAt   time.Time     `json:"created_at"	bson:"created_at"`
	StartTime   time.Time     `json:"start_time"	bson:"start_time"`
	EndTime     time.Time     `json:"end_time"		bson:"end_time"`
}

// This method implements binding.Validator and is executed by the binding.Validate middleware
// Should only be called when creating a new event via a POST request
func (event Event) Validate(errors *binding.Errors, req *http.Request) {

	if event.Title == "" {
		errors.Fields["title"] = "This field is required"
	} else if len(event.Title) < 5 {
		errors.Fields["title"] = "Too short, minimum 5 characters"
	} else if len(event.Title) > 50 {
		errors.Fields["title"] = "Too long, maximum 50 characters"
	}

	if event.Description == "" {
		errors.Fields["description"] = "This field is required"
	} else if len(event.Description) < 5 {
		errors.Fields["description"] = "Too short, minimum 5 characters"
	} else if len(event.Title) > 200 {
		errors.Fields["description"] = "Too long, maximum 200 characters"
	}

	// TODO Validate Coordinates

	if event.StartTime.IsZero() {
		errors.Fields["start_time"] = "This field is required"
	}

	if event.EndTime.IsZero() {
		errors.Fields["end_time"] = "This field is required"
	} else if event.EndTime.Before(event.StartTime) {
		errors.Fields["end_time"] = "The end time must be after the start time"
	} else if event.EndTime.Before(time.Now()) {
		errors.Fields["end_time"] = "The event is already over..."
	}

	if len(errors.Fields) > 0 {
		errors.Overall["ValidationError"] = "Form validation failed"
	}

}
<commit_msg>Fix whitespace in event model<commit_after>package models

import (
	"github.com/codegangsta/martini-contrib/binding"
	"gopkg.in/mgo.v2/bson"
	"net/http"
	"time"
)

type Event struct {
	Id          bson.ObjectId `json:"id,omitempty"	bson:"_id,omitempty"`
	Title       string        `json:"title"		bson:"title"`
	Description string        `json:"description"	bson:"description"`
	Coordinates []float64     `json:"coordinates"	bson:"coordinates"`
	CreatedBy   bson.ObjectId `json:"created_by"	bson:"created_by"`
	CreatedAt   time.Time     `json:"created_at"	bson:"created_at"`
	StartTime   time.Time     `json:"start_time"	bson:"start_time"`
	EndTime     time.Time     `json:"end_time"	bson:"end_time"`
}

// This method implements binding.Validator and is executed by the binding.Validate middleware
// Should only be called when creating a new event via a POST request
func (event Event) Validate(errors *binding.Errors, req *http.Request) {

	if event.Title == "" {
		errors.Fields["title"] = "This field is required"
	} else if len(event.Title) < 5 {
		errors.Fields["title"] = "Too short, minimum 5 characters"
	} else if len(event.Title) > 50 {
		errors.Fields["title"] = "Too long, maximum 50 characters"
	}

	if event.Description == "" {
		errors.Fields["description"] = "This field is required"
	} else if len(event.Description) < 5 {
		errors.Fields["description"] = "Too short, minimum 5 characters"
	} else if len(event.Title) > 200 {
		errors.Fields["description"] = "Too long, maximum 200 characters"
	}

	// TODO Validate Coordinates

	if event.StartTime.IsZero() {
		errors.Fields["start_time"] = "This field is required"
	}

	if event.EndTime.IsZero() {
		errors.Fields["end_time"] = "This field is required"
	} else if event.EndTime.Before(event.StartTime) {
		errors.Fields["end_time"] = "The end time must be after the start time"
	} else if event.EndTime.Before(time.Now()) {
		errors.Fields["end_time"] = "The event is already over..."
	}

	if len(errors.Fields) > 0 {
		errors.Overall["ValidationError"] = "Form validation failed"
	}

}
<|endoftext|>


<commit_before>/*
Copyright 2015 Palm Stone Games, 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 polymer

import (
	"fmt"
	"reflect"
	"strings"

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

var (
	typeOfPtrProto = reflect.TypeOf(&Proto{})
	typeOfJsObject = reflect.TypeOf(&js.Object{})
)

func createdCallback(refType reflect.Type) *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Create a new Go side object
		refVal := reflect.New(refType.Elem())
		proto := refVal.Interface().(Interface)
		refVal = refVal.Elem()

		// Set the proto value, this is needed because we get our callers to embed *polymer.Proto, and it needs to get instantiated
		refVal.FieldByName("Proto").Set(reflect.ValueOf(&Proto{}))

		// Store ourselves in js land so we can map js to proto
		jsMap = append(jsMap, proto)
		this.Set(protoIndexKey, len(jsMap)-1)

		// Set data on the proto
		data := proto.data()
		data.this = this
		data.Element = WrapJSElement(this)

		// Setup channel based event handlers
		for _, handler := range parseChanHandlers(refType) {
			// Create channel
			chanVal := refVal.FieldByIndex(handler.Index)
			chanVal.Set(reflect.MakeChan(chanVal.Type(), 0))

			// Set handler function
			this.Set(getJsName(handler.Name), eventChanCallback(chanVal))
		}

		// Call the proto side callback for user hooks
		proto.Created()

		return nil
	})
}

func ensureReady(proto Interface) {
	refVal := reflect.ValueOf(proto).Elem()
	refType := reflect.TypeOf(proto).Elem()
	data := proto.data()

	if data.ready {
		return
	}

	data.ready = true

	// Set initial field values
	for i := 0; i < refType.NumField(); i++ {
		// Get field info first
		fieldVal := refVal.Field(i)
		fieldType := refType.Field(i)
		jsName := getJsName(fieldType.Name)

		// Ignore the *Proto and *BindProto anonymous field
		if fieldType.Anonymous && (fieldType.Type == typeOfPtrProto || fieldType.Type == typeOfPtrBindProto) {
			continue
		}

		// Special case check to not overwrite Model on dom-bind templates
		if _, ok := proto.(*autoBindTemplate); ok && fieldType.Name == "Model" {
			continue
		}

		// Skip unexported fields
		if !isFieldExported(fieldType.Name) {
			continue
		}

		// If the value in JS is set, we take it over
		// Otherwise, we take over the (usually zeroed) go value and set it in JS
		// We can get away with doing this for only first level values, as they'll either get decoded recursively if they were set
		// Or they'll get set from Go in their entirety if they were undefined
		if fieldVal.Kind() != reflect.Chan {
			jsVal := data.this.Get(jsName)
			if jsVal == nil || jsVal == js.Undefined {
				jsObj, filled := encodeRaw(fieldVal)
				if filled {
					proto.data().doNotify(jsName, jsObj)
				}
			} else {
				currVal := reflect.New(fieldType.Type)
				if err := Decode(jsVal, currVal.Interface()); err != nil {
					panic(fmt.Sprintf("Error while decoding polymer field value for %v: %v", fieldType.Name, err))
				}

				fieldVal.Set(currVal.Elem())
			}
		}
	}
}

func readyCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		ensureReady(proto)
		proto.Ready()

		return nil
	})
}

func attachedCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		// Call the proto side callback for user hooks
		proto.Attached()

		return nil
	})
}

func detachedCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		// Call the proto side callback for user hooks
		proto.Detached()

		return nil
	})
}

func observeShallowCallback(path []string) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		setObservedValue(lookupProto(this), path, jsArgs[0])
		return nil
	})
}

func observeDeepCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		record := jsArgs[0]
		setObservedValue(lookupProto(this), strings.Split(record.Get("path").String(), "."), record.Get("value"))
		return nil
	})
}

// reflectArgs builds up reflect args
// We loop through the function arguments and use the types of each argument to decode the jsArgs
// If the function has more arguments than we have jsArgs, they're passed in as Zero values
// If the function has less arguments than jsArgs, the superfluous jsArgs are silently discarded
func reflectArgs(handler reflect.Value, proto interface{}, jsArgs []*js.Object) []reflect.Value {
	handlerType := handler.Type()
	reflectArgs := make([]reflect.Value, handlerType.NumIn())

	jsIndex := 0
	for goIndex := 0; goIndex < handlerType.NumIn(); goIndex++ {
		argType := handlerType.In(goIndex)
		if goIndex == 0 && len(reflectArgs) != 0 && argType == reflect.TypeOf(proto) {
			reflectArgs[goIndex] = reflect.ValueOf(proto)
		} else {
			argPtrVal := reflect.New(argType)
			if len(jsArgs) > jsIndex {
				if err := decodeRaw(jsArgs[jsIndex], argPtrVal.Elem()); err != nil {
					panic(fmt.Sprintf("Error while decoding argument %v: %v", goIndex, err))
				}
			}

			reflectArgs[goIndex] = argPtrVal.Elem()
			jsIndex++
		}

	}

	return reflectArgs
}

func eventHandlerCallback(handler reflect.Value) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		proto := lookupProto(this)

		domWrappedEvent := js.Global.Get("Polymer").Call("dom", jsArgs[0])
		if domWrappedEvent.Get("event").Get("path").Index(0) != domWrappedEvent.Get("path").Index(0) {
			Log("Suppressed event:", domWrappedEvent)
			return nil
		}

		jsArgs[0] = domWrappedEvent

		if autoBind, ok := proto.(*autoBindTemplate); ok {
			handler.Call(reflectArgs(handler, autoBind.Model, jsArgs))
		} else {
			handler.Call(reflectArgs(handler, proto, jsArgs))
		}
		return nil
	})
}

func eventChanCallback(handlerChan reflect.Value) *js.Object {
	chanArgType := handlerChan.Type().Elem()
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		chanArg := reflect.New(chanArgType)
		domWrappedEvent := js.Global.Get("Polymer").Call("dom", jsArgs[0])
		if domWrappedEvent.Get("event").Get("path").Index(0) != domWrappedEvent.Get("path").Index(0) {
			Log("Suppressed event:", domWrappedEvent)
			return nil
		}

		decodeRaw(domWrappedEvent, chanArg.Elem())
		go func() {
			handlerChan.Send(chanArg.Elem())
		}()
		return nil
	})
}

func computeCallback(handler reflect.Value) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		proto := lookupProto(this)
		ensureReady(proto)

		var returnArgs []reflect.Value
		if autoBind, ok := proto.(*autoBindTemplate); ok {
			returnArgs = handler.Call(reflectArgs(handler, autoBind.Model, jsArgs))
		} else {
			returnArgs = handler.Call(reflectArgs(handler, proto, jsArgs))
		}

		encodedReturn, _ := encodeRaw(returnArgs[0])
		return encodedReturn
	})
}
<commit_msg>Take another approach to catching weird events: Suppress them when we get a panic<commit_after>/*
Copyright 2015 Palm Stone Games, 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 polymer

import (
	"fmt"
	"reflect"
	"strings"

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

var (
	typeOfPtrProto = reflect.TypeOf(&Proto{})
	typeOfJsObject = reflect.TypeOf(&js.Object{})
)

func createdCallback(refType reflect.Type) *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Create a new Go side object
		refVal := reflect.New(refType.Elem())
		proto := refVal.Interface().(Interface)
		refVal = refVal.Elem()

		// Set the proto value, this is needed because we get our callers to embed *polymer.Proto, and it needs to get instantiated
		refVal.FieldByName("Proto").Set(reflect.ValueOf(&Proto{}))

		// Store ourselves in js land so we can map js to proto
		jsMap = append(jsMap, proto)
		this.Set(protoIndexKey, len(jsMap)-1)

		// Set data on the proto
		data := proto.data()
		data.this = this
		data.Element = WrapJSElement(this)

		// Setup channel based event handlers
		for _, handler := range parseChanHandlers(refType) {
			// Create channel
			chanVal := refVal.FieldByIndex(handler.Index)
			chanVal.Set(reflect.MakeChan(chanVal.Type(), 0))

			// Set handler function
			this.Set(getJsName(handler.Name), eventChanCallback(chanVal))
		}

		// Call the proto side callback for user hooks
		proto.Created()

		return nil
	})
}

func ensureReady(proto Interface) {
	refVal := reflect.ValueOf(proto).Elem()
	refType := reflect.TypeOf(proto).Elem()
	data := proto.data()

	if data.ready {
		return
	}

	data.ready = true

	// Set initial field values
	for i := 0; i < refType.NumField(); i++ {
		// Get field info first
		fieldVal := refVal.Field(i)
		fieldType := refType.Field(i)
		jsName := getJsName(fieldType.Name)

		// Ignore the *Proto and *BindProto anonymous field
		if fieldType.Anonymous && (fieldType.Type == typeOfPtrProto || fieldType.Type == typeOfPtrBindProto) {
			continue
		}

		// Special case check to not overwrite Model on dom-bind templates
		if _, ok := proto.(*autoBindTemplate); ok && fieldType.Name == "Model" {
			continue
		}

		// Skip unexported fields
		if !isFieldExported(fieldType.Name) {
			continue
		}

		// If the value in JS is set, we take it over
		// Otherwise, we take over the (usually zeroed) go value and set it in JS
		// We can get away with doing this for only first level values, as they'll either get decoded recursively if they were set
		// Or they'll get set from Go in their entirety if they were undefined
		if fieldVal.Kind() != reflect.Chan {
			jsVal := data.this.Get(jsName)
			if jsVal == nil || jsVal == js.Undefined {
				jsObj, filled := encodeRaw(fieldVal)
				if filled {
					proto.data().doNotify(jsName, jsObj)
				}
			} else {
				currVal := reflect.New(fieldType.Type)
				if err := Decode(jsVal, currVal.Interface()); err != nil {
					panic(fmt.Sprintf("Error while decoding polymer field value for %v: %v", fieldType.Name, err))
				}

				fieldVal.Set(currVal.Elem())
			}
		}
	}
}

func readyCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		ensureReady(proto)
		proto.Ready()

		return nil
	})
}

func attachedCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		// Call the proto side callback for user hooks
		proto.Attached()

		return nil
	})
}

func detachedCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, arguments []*js.Object) interface{} {
		// Lookup the proto
		proto := lookupProto(this)

		// Call the proto side callback for user hooks
		proto.Detached()

		return nil
	})
}

func observeShallowCallback(path []string) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		setObservedValue(lookupProto(this), path, jsArgs[0])
		return nil
	})
}

func observeDeepCallback() *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		record := jsArgs[0]
		setObservedValue(lookupProto(this), strings.Split(record.Get("path").String(), "."), record.Get("value"))
		return nil
	})
}

// reflectArgs builds up reflect args
// We loop through the function arguments and use the types of each argument to decode the jsArgs
// If the function has more arguments than we have jsArgs, they're passed in as Zero values
// If the function has less arguments than jsArgs, the superfluous jsArgs are silently discarded
func reflectArgs(handler reflect.Value, proto interface{}, jsArgs []*js.Object) []reflect.Value {
	handlerType := handler.Type()
	reflectArgs := make([]reflect.Value, handlerType.NumIn())

	jsIndex := 0
	for goIndex := 0; goIndex < handlerType.NumIn(); goIndex++ {
		argType := handlerType.In(goIndex)
		if goIndex == 0 && len(reflectArgs) != 0 && argType == reflect.TypeOf(proto) {
			reflectArgs[goIndex] = reflect.ValueOf(proto)
		} else {
			argPtrVal := reflect.New(argType)
			if len(jsArgs) > jsIndex {
				if err := decodeRaw(jsArgs[jsIndex], argPtrVal.Elem()); err != nil {
					panic(fmt.Sprintf("Error while decoding argument %v: %v", goIndex, err))
				}
			}

			reflectArgs[goIndex] = argPtrVal.Elem()
			jsIndex++
		}

	}

	return reflectArgs
}

func eventHandlerRecover(event *js.Object) {
	r := recover()
	if r != nil {
		Log("Suppressed event ", event, " due to error while decoding: ", r)
	}
}

func eventHandlerCallback(handler reflect.Value) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		defer eventHandlerRecover(jsArgs[0])

		proto := lookupProto(this)

		jsArgs[0] = js.Global.Get("Polymer").Call("dom", jsArgs[0])
		if autoBind, ok := proto.(*autoBindTemplate); ok {
			handler.Call(reflectArgs(handler, autoBind.Model, jsArgs))
		} else {
			handler.Call(reflectArgs(handler, proto, jsArgs))
		}
		return nil
	})
}

func eventChanCallback(handlerChan reflect.Value) *js.Object {
	chanArgType := handlerChan.Type().Elem()
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		defer eventHandlerRecover(jsArgs[0])

		chanArg := reflect.New(chanArgType)
		decodeRaw(js.Global.Get("Polymer").Call("dom", jsArgs[0]), chanArg.Elem())
		go func() {
			handlerChan.Send(chanArg.Elem())
		}()
		return nil
	})
}

func computeCallback(handler reflect.Value) *js.Object {
	return js.MakeFunc(func(this *js.Object, jsArgs []*js.Object) interface{} {
		proto := lookupProto(this)
		ensureReady(proto)

		var returnArgs []reflect.Value
		if autoBind, ok := proto.(*autoBindTemplate); ok {
			returnArgs = handler.Call(reflectArgs(handler, autoBind.Model, jsArgs))
		} else {
			returnArgs = handler.Call(reflectArgs(handler, proto, jsArgs))
		}

		encodedReturn, _ := encodeRaw(returnArgs[0])
		return encodedReturn
	})
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"os"
)

var (
	silent bool
	// Declared in common:
	// hashFunction HashValue
	// cwd string
)

var cmdVerify = &Command{
	Run: verify,
	Usage: `verify [-s] [-c=hash] [-D=dir] [file]`,
	Short: "Verify using a checksum file",
	Long: `
Verify all files for the given directory against a checksum file. If a specific
file is given, only that file is verified.`,
}

// Initialized in common:
// var preferredHashes []string

func init() {
	const (
		silentUsage = "silent; don't output to STDERR"
		cwdUsage = "the directory for which to verify using the checksum file"
	)
	hashUsage := fmt.Sprintf("the hash to use; if unspecified, the following are tried in order: %s", strings.Join(preferredHashes, ", "))
	f := &cmdVerify.Flag
	f.BoolVar(&silent, "s", false, silentUsage)
	f.Var(&hashFunction, "c", hashUsage)
	f.StringVar(&cwd, "D", ".", cwdUsage)
}

func verify(cmd *Command, args []string) {
	var err error
	hash, checksums := setDirAndHashOptions()
	singleFileMode, singleFile := false, ""
	if len(args) == 1 {
		singleFileMode = true
		singleFile = args[0]
	}

	allMatch := true
	r := NewReader(checksums)
	err = r.Each(func(entry *Entry) {
		if singleFileMode && entry.Filename != singleFile {
			return // continue
		}

		if ok, err := entry.Verify(hash.Hash); err != nil {
			if !silent {
				warn("%s\n", err)
			}
			allMatch = false
		} else if !ok {
			if !silent {
				warn("%s does not match %s\n", entry.Filename, entry.Checksum)
			}
			allMatch = false
		}
	})
	if err != nil {
		die(err)
	}

	if allMatch {
		os.Exit(0)
	} else {
		os.Exit(1)
	}
}

<commit_msg>Improve documentation for verify<commit_after>package main

import (
	"fmt"
	"strings"
	"os"
)

var (
	silent bool
	// Declared in common:
	// hashFunction HashValue
	// cwd string
)

var cmdVerify = &Command{
	Run: verify,
	Usage: `verify [-s] [-c=hash] [-D=dir] [file]`,
	Short: "Verify using a checksum file",
	Long: `
Verify all files for the given directory against a checksum file. If a specific
file is given, only that file is verified.

If verification is successful, then the exit status will be 0 and no output will
be written. If there is a checksum mismatch, then the exit status will be 1 and
the mismatch(es) in question will be written to STDERR unless silent mode is on.`,
}

// Initialized in common:
// var preferredHashes []string

func init() {
	const (
		silentUsage = "silent; don't output to STDERR"
		cwdUsage = "the directory for which to verify using the checksum file"
	)
	hashUsage := fmt.Sprintf("the hash to use; if unspecified, the following are tried in order: %s", strings.Join(preferredHashes, ", "))
	f := &cmdVerify.Flag
	f.BoolVar(&silent, "s", false, silentUsage)
	f.Var(&hashFunction, "c", hashUsage)
	f.StringVar(&cwd, "D", ".", cwdUsage)
}

func verify(cmd *Command, args []string) {
	var err error
	hash, checksums := setDirAndHashOptions()
	singleFileMode, singleFile := false, ""
	if len(args) == 1 {
		singleFileMode = true
		singleFile = args[0]
	}

	allMatch := true
	r := NewReader(checksums)
	err = r.Each(func(entry *Entry) {
		if singleFileMode && entry.Filename != singleFile {
			return // continue
		}

		if ok, err := entry.Verify(hash.Hash); err != nil {
			if !silent {
				warn("%s\n", err)
			}
			allMatch = false
		} else if !ok {
			if !silent {
				warn("%s does not match %s\n", entry.Filename, entry.Checksum)
			}
			allMatch = false
		}
	})
	if err != nil {
		die(err)
	}

	if allMatch {
		os.Exit(0)
	} else {
		os.Exit(1)
	}
}

<|endoftext|>


<commit_before>package victor

import (
	"fmt"
	"github.com/brettbuddin/victor/pkg/chat"
	_ "github.com/brettbuddin/victor/pkg/chat/campfire"
	_ "github.com/brettbuddin/victor/pkg/chat/shell"
	_ "github.com/brettbuddin/victor/pkg/chat/slack"
	"github.com/brettbuddin/victor/pkg/httpserver"
	"github.com/brettbuddin/victor/pkg/store"
	"github.com/gorilla/mux"
	"log"
	"os"
	"strings"
)

type Robot interface {
	HandleFunc(string, HandlerFunc)
	Handle(string, Handler)
	Direct(string) string
	Receive(chat.Message)
	Chat() chat.Adapter
	Store() store.Store
}

type robot struct {
	*Dispatch
	name     string
	http     *httpserver.Server
	httpAddr string
	router   *mux.Router
	store    store.Store
	adapter  chat.Adapter
	incoming chan chat.Message
	stop     chan struct{}
}

// New returns a robot
func New(adapterName, robotName, httpAddr string) *robot {
	initFunc, err := chat.Load(adapterName)

	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	bot := &robot{
		name:     robotName,
		http:     httpserver.New(),
		store:    store.NewMemoryStore(),
		incoming: make(chan chat.Message),
		httpAddr: httpAddr,
		stop:     make(chan struct{}),
	}

	bot.Dispatch = NewDispatch(bot)
	bot.adapter = initFunc(bot)
	bot.router = handlers(bot)

	defaults(bot)
	return bot
}

// Direct wraps a regexp pattern in the necessary pattern
// for a direct command to the bot.
func (r *robot) Direct(exp string) string {
	return strings.Join([]string{
		"(?i)", // flags
		"\\A",  // begin
		"(?:(?:@)?" + r.name + "[:,]?\\s*|/)", // bot name
		"(?:" + exp + ")",                     // expression
		"\\z",                                 // end
	}, "")
}

func (r *robot) Receive(m chat.Message) {
	r.incoming <- m
}

// Run starts the robot.
func (r *robot) Run() error {
	go r.adapter.Run()
	go func() {
		for {
			select {
			case <-r.stop:
				return
			case m := <-r.incoming:
				if strings.ToLower(m.UserName()) != r.name {
					go r.ProcessMessage(m)
				}
			}
		}
	}()

	r.http.Handle("/", r.router)
	return r.http.ListenAndServe(r.httpAddr)
}

func (r *robot) Stop() {
	r.adapter.Stop()
	r.stop <- struct{}{}
	close(r.incoming)
	r.http.Stop()
}

func (r *robot) Name() string {
	return r.name
}

func (r *robot) SetName(n string) {
	r.name = n
}

func (r *robot) Store() store.Store {
	return r.store
}

func (r *robot) SetStore(s store.Store) {
	r.store = s
}

func (r *robot) HTTP() *mux.Router {
	return r.router
}

func (r *robot) SetHTTP(router *mux.Router) {
	r.router = router
}

func (r *robot) Chat() chat.Adapter {
	return r.adapter
}

func (r *robot) SetChat(name string) error {
	initFunc, err := chat.Load(name)

	if err != nil {
		return err
	}

	r.adapter = initFunc(r)
	return nil
}

func OnlyAllow(userNames []string, action func(s State)) func(State) {
	return func(s State) {
		actual := s.Message().UserName()
		for _, name := range userNames {
			if name == actual {
				action(s)
				return
			}
		}

		s.Chat().Send(s.Message().ChannelID(), fmt.Sprintf("Sorry, %s. I can't let you do that.", actual))
	}
}
<commit_msg>Smaller bits. Fixing methods I left out.<commit_after>package victor

import (
	"fmt"
	"github.com/brettbuddin/victor/pkg/chat"
	_ "github.com/brettbuddin/victor/pkg/chat/campfire"
	_ "github.com/brettbuddin/victor/pkg/chat/shell"
	_ "github.com/brettbuddin/victor/pkg/chat/slack"
	"github.com/brettbuddin/victor/pkg/httpserver"
	"github.com/brettbuddin/victor/pkg/store"
	"github.com/gorilla/mux"
	"log"
	"os"
	"strings"
)

type Runner interface {
    Run() error
    Stop()
}

type Chatter interface {
    Name() string
	HandleFunc(string, HandlerFunc)
	Handle(string, Handler)
	Direct(string) string
	Receive(chat.Message)
	Chat() chat.Adapter
}

type Persister interface {
	Store() store.Store
}

type Robot interface {
    Runner
    Chatter
    Persister

    HTTP() *mux.Router
}

type robot struct {
	*Dispatch
	name     string
	http     *httpserver.Server
	httpAddr string
	router   *mux.Router
	store    store.Store
	adapter  chat.Adapter
	incoming chan chat.Message
	stop     chan struct{}
}

// New returns a robot
func New(adapterName, robotName, httpAddr string) *robot {
	initFunc, err := chat.Load(adapterName)

	if err != nil {
		log.Println(err)
		os.Exit(1)
	}

	bot := &robot{
		name:     robotName,
		http:     httpserver.New(),
		store:    store.NewMemoryStore(),
		incoming: make(chan chat.Message),
		httpAddr: httpAddr,
		stop:     make(chan struct{}),
	}

	bot.Dispatch = NewDispatch(bot)
	bot.adapter = initFunc(bot)
	bot.router = handlers(bot)

	defaults(bot)
	return bot
}

// Direct wraps a regexp pattern in the necessary pattern
// for a direct command to the bot.
func (r *robot) Direct(exp string) string {
	return strings.Join([]string{
		"(?i)", // flags
		"\\A",  // begin
		"(?:(?:@)?" + r.name + "[:,]?\\s*|/)", // bot name
		"(?:" + exp + ")",                     // expression
		"\\z",                                 // end
	}, "")
}

func (r *robot) Receive(m chat.Message) {
	r.incoming <- m
}

// Run starts the robot.
func (r *robot) Run() error {
	go r.adapter.Run()
	go func() {
		for {
			select {
			case <-r.stop:
				return
			case m := <-r.incoming:
				if strings.ToLower(m.UserName()) != r.name {
					go r.ProcessMessage(m)
				}
			}
		}
	}()

	r.http.Handle("/", r.router)
	return r.http.ListenAndServe(r.httpAddr)
}

func (r *robot) Stop() {
	r.adapter.Stop()
	r.stop <- struct{}{}
	close(r.incoming)
	r.http.Stop()
}

func (r *robot) Name() string {
	return r.name
}

func (r *robot) Store() store.Store {
	return r.store
}

func (r *robot) HTTP() *mux.Router {
	return r.router
}

func (r *robot) Chat() chat.Adapter {
	return r.adapter
}

func OnlyAllow(userNames []string, action func(s State)) func(State) {
	return func(s State) {
		actual := s.Message().UserName()
		for _, name := range userNames {
			if name == actual {
				action(s)
				return
			}
		}

		s.Chat().Send(s.Message().ChannelID(), fmt.Sprintf("Sorry, %s. I can't let you do that.", actual))
	}
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"github.com/microcosm-cc/bluemonday"
	"html"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/validate"
)

type ReplyModel struct {
	Uid         uint
	Ib          uint
	Thread      uint
	PostNum     uint
	Ip          string
	Comment     string
	Filename    string
	Thumbnail   string
	MD5         string
	OrigWidth   int
	OrigHeight  int
	ThumbWidth  int
	ThumbHeight int
	Image       bool
}

// check struct validity
func (r *ReplyModel) IsValid() bool {

	if r.Uid == 0 {
		return false
	}

	if r.Ib == 0 {
		return false
	}

	if r.Thread == 0 {
		return false
	}

	if r.PostNum == 0 {
		return false
	}

	if r.Ip == "" {
		return false
	}

	if !r.Image && r.Comment == "" {
		return false
	}

	if r.Image {

		if r.Filename == "" {
			return false
		}

		if r.Thumbnail == "" {
			return false
		}

		if r.MD5 == "" {
			return false
		}

		if r.OrigWidth == 0 {
			return false
		}

		if r.OrigHeight == 0 {
			return false
		}

		if r.ThumbWidth == 0 {
			return false
		}

		if r.ThumbHeight == 0 {
			return false
		}

	}

	return true

}

// ValidateInput will make sure all the parameters are valid
func (i *ReplyModel) ValidateInput() (err error) {
	if i.Thread == 0 {
		return e.ErrInvalidParam
	}

	// Initialize bluemonday
	p := bluemonday.StrictPolicy()

	// Sanitize comment for html and xss
	i.Comment = p.Sanitize(i.Comment)

	i.Comment = html.UnescapeString(i.Comment)

	// There must either be a comment, an image, or an image with a comment
	// If theres no image a comment is required
	comment := validate.Validate{Input: i.Comment, Max: config.Settings.Limits.CommentMaxLength, Min: config.Settings.Limits.CommentMinLength}

	if !i.Image {
		if comment.IsEmpty() {
			return e.ErrNoComment
		} else if comment.MinLength() {
			return e.ErrCommentShort
		} else if comment.MaxLength() {
			return e.ErrCommentLong
		}
	}

	// If theres an image and a comment validate comment
	if i.Image && !comment.IsEmpty() {
		if comment.MinLength() {
			return e.ErrCommentShort
		} else if comment.MaxLength() {
			return e.ErrCommentLong
		}
	}

	return

}

// Status will return info about the thread
func (i *ReplyModel) Status() (err error) {

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	var closed bool
	var total uint

	// Check if thread is closed and get the total amount of posts
	err = dbase.QueryRow(`SELECT ib_id,thread_closed,count(post_num)
	FROM ( SELECT ib_id,threads.thread_id,thread_closed,post_num
	FROM threads
	INNER JOIN posts on threads.thread_id = posts.thread_id
	WHERE threads.thread_id = ? AND post_deleted != 1
	GROUP BY post_num DESC) AS b`, i.Thread).Scan(&i.Ib, &closed, &total)
	if err != nil {
		return
	}

	// Error if thread is closed
	if closed {
		return e.ErrThreadClosed
	}

	// Close thread if above max posts
	if total > config.Settings.Limits.PostsMax {
		updatestatus, err := dbase.Prepare("UPDATE threads SET thread_closed=1 WHERE thread_id = ?")
		if err != nil {
			return err
		}
		defer updatestatus.Close()

		_, err = updatestatus.Exec(i.Thread)
		if err != nil {
			return err
		}

		return e.ErrThreadClosed
	}

	return

}

// Post will add the reply to the database with a transaction
func (i *ReplyModel) Post() (err error) {

	// check model validity
	if !i.IsValid() {
		return errors.New("ReplyModel is not valid")
	}

	// Get transaction handle
	tx, err := db.GetTransaction()
	if err != nil {
		return
	}
	defer tx.Rollback()

	// Insert data into posts table
	ps1, err := tx.Prepare(`INSERT INTO posts (thread_id,user_id,post_num,post_time,post_ip,post_text)
    SELECT ?,?,max(post_num)+1,NOW(),?,?
    FROM posts WHERE thread_id = ?`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// Update thread last post time
	ps2, err := tx.Prepare("UPDATE threads SET thread_last_post = NOW() WHERE thread_id = ?")
	if err != nil {
		return
	}
	defer ps2.Close()

	e1, err := ps1.Exec(i.Thread, i.Uid, i.Ip, i.Comment, i.Thread)
	if err != nil {
		return
	}

	_, err = ps2.Exec(i.Thread)
	if err != nil {
		return
	}

	if i.Image {

		// Insert data into images table
		ps3, err := tx.Prepare("INSERT INTO images (post_id,image_file,image_thumbnail,image_hash,image_orig_height,image_orig_width,image_tn_height,image_tn_width) VALUES (?,?,?,?,?,?,?,?)")
		if err != nil {
			return err
		}
		defer ps2.Close()

		p_id, err := e1.LastInsertId()
		if err != nil {
			return err
		}

		_, err = ps3.Exec(p_id, i.Filename, i.Thumbnail, i.MD5, i.OrigHeight, i.OrigWidth, i.ThumbHeight, i.ThumbWidth)
		if err != nil {
			return err
		}

	}

	// Commit transaction
	err = tx.Commit()
	if err != nil {
		return
	}

	return

}
<commit_msg>all database inserts should have struct data validated<commit_after>package models

import (
	"errors"
	"github.com/microcosm-cc/bluemonday"
	"html"

	"github.com/eirka/eirka-libs/config"
	"github.com/eirka/eirka-libs/db"
	e "github.com/eirka/eirka-libs/errors"
	"github.com/eirka/eirka-libs/validate"
)

type ReplyModel struct {
	Uid         uint
	Ib          uint
	Thread      uint
	Ip          string
	Comment     string
	Filename    string
	Thumbnail   string
	MD5         string
	OrigWidth   int
	OrigHeight  int
	ThumbWidth  int
	ThumbHeight int
	Image       bool
}

// check struct validity
func (r *ReplyModel) IsValid() bool {

	if r.Uid == 0 {
		return false
	}

	if r.Ib == 0 {
		return false
	}

	if r.Thread == 0 {
		return false
	}

	if r.Ip == "" {
		return false
	}

	if !r.Image && r.Comment == "" {
		return false
	}

	if r.Image {

		if r.Filename == "" {
			return false
		}

		if r.Thumbnail == "" {
			return false
		}

		if r.MD5 == "" {
			return false
		}

		if r.OrigWidth == 0 {
			return false
		}

		if r.OrigHeight == 0 {
			return false
		}

		if r.ThumbWidth == 0 {
			return false
		}

		if r.ThumbHeight == 0 {
			return false
		}

	}

	return true

}

// ValidateInput will make sure all the parameters are valid
func (i *ReplyModel) ValidateInput() (err error) {
	if i.Thread == 0 {
		return e.ErrInvalidParam
	}

	// Initialize bluemonday
	p := bluemonday.StrictPolicy()

	// Sanitize comment for html and xss
	i.Comment = p.Sanitize(i.Comment)

	i.Comment = html.UnescapeString(i.Comment)

	// There must either be a comment, an image, or an image with a comment
	// If theres no image a comment is required
	comment := validate.Validate{Input: i.Comment, Max: config.Settings.Limits.CommentMaxLength, Min: config.Settings.Limits.CommentMinLength}

	if !i.Image {
		if comment.IsEmpty() {
			return e.ErrNoComment
		} else if comment.MinLength() {
			return e.ErrCommentShort
		} else if comment.MaxLength() {
			return e.ErrCommentLong
		}
	}

	// If theres an image and a comment validate comment
	if i.Image && !comment.IsEmpty() {
		if comment.MinLength() {
			return e.ErrCommentShort
		} else if comment.MaxLength() {
			return e.ErrCommentLong
		}
	}

	return

}

// Status will return info about the thread
func (i *ReplyModel) Status() (err error) {

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	var closed bool
	var total uint

	// Check if thread is closed and get the total amount of posts
	err = dbase.QueryRow(`SELECT ib_id,thread_closed,count(post_num)
	FROM ( SELECT ib_id,threads.thread_id,thread_closed,post_num
	FROM threads
	INNER JOIN posts on threads.thread_id = posts.thread_id
	WHERE threads.thread_id = ? AND post_deleted != 1
	GROUP BY post_num DESC) AS b`, i.Thread).Scan(&i.Ib, &closed, &total)
	if err != nil {
		return
	}

	// Error if thread is closed
	if closed {
		return e.ErrThreadClosed
	}

	// Close thread if above max posts
	if total > config.Settings.Limits.PostsMax {
		updatestatus, err := dbase.Prepare("UPDATE threads SET thread_closed=1 WHERE thread_id = ?")
		if err != nil {
			return err
		}
		defer updatestatus.Close()

		_, err = updatestatus.Exec(i.Thread)
		if err != nil {
			return err
		}

		return e.ErrThreadClosed
	}

	return

}

// Post will add the reply to the database with a transaction
func (i *ReplyModel) Post() (err error) {

	// check model validity
	if !i.IsValid() {
		return errors.New("ReplyModel is not valid")
	}

	// Get transaction handle
	tx, err := db.GetTransaction()
	if err != nil {
		return
	}
	defer tx.Rollback()

	// Insert data into posts table
	ps1, err := tx.Prepare(`INSERT INTO posts (thread_id,user_id,post_num,post_time,post_ip,post_text)
    SELECT ?,?,max(post_num)+1,NOW(),?,?
    FROM posts WHERE thread_id = ?`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// Update thread last post time
	ps2, err := tx.Prepare("UPDATE threads SET thread_last_post = NOW() WHERE thread_id = ?")
	if err != nil {
		return
	}
	defer ps2.Close()

	e1, err := ps1.Exec(i.Thread, i.Uid, i.Ip, i.Comment, i.Thread)
	if err != nil {
		return
	}

	_, err = ps2.Exec(i.Thread)
	if err != nil {
		return
	}

	if i.Image {

		// Insert data into images table
		ps3, err := tx.Prepare("INSERT INTO images (post_id,image_file,image_thumbnail,image_hash,image_orig_height,image_orig_width,image_tn_height,image_tn_width) VALUES (?,?,?,?,?,?,?,?)")
		if err != nil {
			return err
		}
		defer ps2.Close()

		p_id, err := e1.LastInsertId()
		if err != nil {
			return err
		}

		_, err = ps3.Exec(p_id, i.Filename, i.Thumbnail, i.MD5, i.OrigHeight, i.OrigWidth, i.ThumbHeight, i.ThumbWidth)
		if err != nil {
			return err
		}

	}

	// Commit transaction
	err = tx.Commit()
	if err != nil {
		return
	}

	return

}
<|endoftext|>


<commit_before>package models

import (
	"github.com/eirka/eirka-libs/db"
	"time"
)

// NewModel holds the parameters from the request and also the key for the cache
type StatisticsModel struct {
	Ib     uint
	Result NewType
}

type StatisticsType struct {
	Labels []*time.Time `json:"labels"`
	Series []Series     `json:"series"`
}

type Series struct {
	Name string `json:"name"`
	Data []uint `json:"data"`
}

// Get will gather the information from the database and return it as JSON serialized data
func (i *StatisticsModel) Get() (err error) {

	// Initialize response header
	response := StatisticsType{}

	visitors := Series{
		Name: "Visitors",
	}

	hits := Series{
		Name: "Hits",
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	ps1, err := dbase.Prepare(`SELECT (now() - interval ? hour) as time,
    COUNT(DISTINCT request_ip) as visitors, COUNT(request_itemkey) as hits
    FROM analytics
    WHERE request_time BETWEEN (now() - interval ? hour) AND (now() - interval ? hour) AND ib_id = ?`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// loop through every two hours
	for i := 1; i <= 24; i++ {
		if i%2 == 0 {

			var label time.Time
			var visitor_count, hit_count uint

			// period minus two hours
			previous := (i - 2)

			rows, err := ps1.QueryRow(i, i, previous, i.Ib).Scan(&label, &visitor_count, &hit_count)
			if err != nil {
				return
			}

			response.Labels = append(response.Labels, label)
			visitors.Data = append(visitors.Data, visitor_count)
			hits.Data = append(hits.Data, hit_count)

		}
	}

	response.Series = append(response.Series, visitors, hits)

	// This is the data we will serialize
	i.Result = response

	return

}
<commit_msg>add statistics controller<commit_after>package models

import (
	"github.com/eirka/eirka-libs/db"
	"time"
)

// NewModel holds the parameters from the request and also the key for the cache
type StatisticsModel struct {
	Ib     uint
	Result StatisticsType
}

type StatisticsType struct {
	Labels []*time.Time `json:"labels"`
	Series []Series     `json:"series"`
}

type Series struct {
	Name string `json:"name"`
	Data []uint `json:"data"`
}

// Get will gather the information from the database and return it as JSON serialized data
func (i *StatisticsModel) Get() (err error) {

	// Initialize response header
	response := StatisticsType{}

	visitors := Series{
		Name: "Visitors",
	}

	hits := Series{
		Name: "Hits",
	}

	// Get Database handle
	dbase, err := db.GetDb()
	if err != nil {
		return
	}

	ps1, err := dbase.Prepare(`SELECT (now() - interval ? hour) as time,
    COUNT(DISTINCT request_ip) as visitors, COUNT(request_itemkey) as hits
    FROM analytics
    WHERE request_time BETWEEN (now() - interval ? hour) AND (now() - interval ? hour) AND ib_id = ?`)
	if err != nil {
		return
	}
	defer ps1.Close()

	// loop through every two hours
	for i := 1; i <= 24; i++ {
		if i%2 == 0 {

			var label time.Time
			var visitor_count, hit_count uint

			// period minus two hours
			previous := (i - 2)

			rows, err := ps1.QueryRow(i, i, previous, i.Ib).Scan(&label, &visitor_count, &hit_count)
			if err != nil {
				return err
			}

			response.Labels = append(response.Labels, label)
			visitors.Data = append(visitors.Data, visitor_count)
			hits.Data = append(hits.Data, hit_count)

		}
	}

	response.Series = append(response.Series, visitors, hits)

	// This is the data we will serialize
	i.Result = response

	return

}
<|endoftext|>