<commit_before>// Copyright 2015 CoreOS, Inc.
// Copyright 2015 The Go 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 platform

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/coreos/mantle/Godeps/_workspace/src/github.com/coreos/coreos-cloudinit/config"
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/compute/v1"
	"github.com/coreos/mantle/auth"
	"github.com/coreos/mantle/network"
	"github.com/coreos/mantle/util"
)

type GCEOptions struct {
	Image       string
	Project     string
	Zone        string
	MachineType string
	DiskType    string
	BaseName    string
	Network     string
	ServiceAuth bool
}

type gceCluster struct {
	api      *compute.Service
	sshAgent *network.SSHAgent
	conf     *GCEOptions
	machines map[string]*gceMachine
	mu       sync.Mutex // protects concurrent access to machines
}

type gceMachine struct {
	gc        *gceCluster
	name      string
	intIP     string
	extIP     string
	sshClient *ssh.Client
}

func NewGCECluster(conf GCEOptions) (Cluster, error) {
	var client *http.Client
	var err error
	if conf.ServiceAuth {
		client = auth.GoogleServiceClient()
	} else {
		client, err = auth.GoogleClient()
	}
	if err != nil {
		return nil, err
	}

	api, err := compute.New(client)
	if err != nil {
		return nil, err
	}

	gc := &gceCluster{
		api:      api,
		conf:     &conf,
		machines: make(map[string]*gceMachine),
	}

	gc.sshAgent, err = network.NewSSHAgent(&net.Dialer{})
	if err != nil {
		return nil, err
	}

	return gc, nil
}

func (gc *gceCluster) NewCommand(name string, arg ...string) util.Cmd {
	return util.NewCommand(name, arg...)
}

func (gc *gceCluster) EtcdEndpoint() string {
	return ""
}

func (gc *gceCluster) Machines() []Machine {
	machines := make([]Machine, 0, len(gc.machines))

	gc.mu.Lock()
	for _, m := range gc.machines {
		machines = append(machines, m)
	}
	gc.mu.Unlock()

	return machines
}

func (gc *gceCluster) Destroy() error {
	for _, gm := range gc.machines {
		gm.Destroy()
	}
	gc.sshAgent.Close()
	return nil
}

// Calling in parallel is ok
func (gc *gceCluster) NewMachine(cloudConfig string) (Machine, error) {
	cconfig, err := config.NewCloudConfig(cloudConfig)
	if err != nil {
		return nil, err
	}
	if err = gc.sshAgent.UpdateConfig(cconfig); err != nil {
		return nil, err
	}
	cloudConfig = cconfig.String()

	// Create gce VM and wait for creation to succeed.
	gm, err := GCECreateVM(gc.api, gc.conf, cloudConfig)
	if err != nil {
		return nil, err
	}
	gm.gc = gc

	err = sshCheck(gm)
	if err != nil {
		gm.Destroy()
		return nil, err
	}

	gc.mu.Lock()
	gc.machines[gm.ID()] = gm
	gc.mu.Unlock()

	return Machine(gm), nil
}

func (gce *gceCluster) GetDiscoveryURL(size int) (string, error) {
	resp, err := http.Get(fmt.Sprintf("https://discovery.etcd.io/new?size=%v", size))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

func (gm *gceMachine) ID() string {
	return gm.name
}

func (gm *gceMachine) IP() string {
	return gm.extIP
}

func (gm *gceMachine) PrivateIP() string {
	return gm.intIP
}

func (gm *gceMachine) SSHSession() (*ssh.Session, error) {
	session, err := gm.sshClient.NewSession()
	if err != nil {
		return nil, err
	}

	return session, nil
}

func (gm *gceMachine) SSH(cmd string) ([]byte, error) {
	session, err := gm.SSHSession()
	if err != nil {
		return []byte{}, err
	}
	defer session.Close()

	session.Stderr = os.Stderr
	out, err := session.Output(cmd)
	out = bytes.TrimSpace(out)
	return out, err
}

func (gm *gceMachine) StartJournal() error {
	s, err := gm.SSHSession()
	if err != nil {
		return fmt.Errorf("SSH session failed: %v", err)
	}

	s.Stdout = os.Stdout
	s.Stderr = os.Stderr
	go func() {
		s.Run("journalctl -f")
		s.Close()
	}()

	return nil
}

func (gm *gceMachine) Destroy() error {
	if gm.sshClient != nil {
		gm.sshClient.Close()
	}

	_, err := gm.gc.api.Instances.Delete(gm.gc.conf.Project, gm.gc.conf.Zone, gm.name).Do()
	if err != nil {
		return err
	}

	gm.gc.mu.Lock()
	delete(gm.gc.machines, gm.ID())
	gm.gc.mu.Unlock()

	return nil
}

func GCECreateVM(api *compute.Service, opts *GCEOptions, userdata string) (*gceMachine, error) {
	if userdata != "" {
		_, err := config.NewCloudConfig(userdata)
		if err != nil {
			return nil, err
		}
	}

	// generate name
	name, err := newName(opts)
	if err != nil {
		return nil, fmt.Errorf("Failed allocating unique name for vm: %v\n", err)
	}

	instance, err := gceMakeInstance(opts, userdata, name)
	if err != nil {
		return nil, err
	}

	// request instance
	op, err := api.Instances.Insert(opts.Project, opts.Zone, instance).Do()
	if err != nil {
		return nil, fmt.Errorf("Failed to create new VM: %v\n", err)
	}

	fmt.Fprintf(os.Stderr, "Instance %v requested\n", name)
	fmt.Fprintf(os.Stderr, "Waiting for creation to finish...\n")

	// wait for creation to finish
	err = gceWaitVM(api, opts.Project, opts.Zone, op.Name)
	if err != nil {
		return nil, err
	}

	inst, err := api.Instances.Get(opts.Project, opts.Zone, name).Do()
	if err != nil {
		return nil, fmt.Errorf("Error getting instance %s details after creation: %v", name, err)
	}
	intIP, extIP := instanceIPs(inst)

	gm := &gceMachine{
		name:  name,
		extIP: extIP,
		intIP: intIP,
	}

	return gm, nil
}

func GCEDestroyVM(api *compute.Service, proj, zone, name string) error {
	_, err := api.Instances.Delete(proj, zone, name).Do()
	if err != nil {
		return err
	}
	return nil
}

// Create image on GCE and return. Will not overwrite existing image.
func GCECreateImage(api *compute.Service, proj, name, source string) error {
	image := &compute.Image{
		Name: name,
		RawDisk: &compute.ImageRawDisk{
			Source: source,
		},
	}
	_, err := api.Images.Insert(proj, image).Do()
	if err != nil {
		return err
	}
	return nil
}

// Delete image on GCE and then recreate it.
func GCEForceCreateImage(api *compute.Service, proj, name, source string) error {
	_, err := api.Images.Delete(proj, name).Do()

	// don't return error when delete fails because image doesn't exist
	if err != nil && !strings.HasSuffix(err.Error(), "notFound") {
		return fmt.Errorf("deleting image: %v", err)
	}

	// create
	return GCECreateImage(api, proj, name, source)
}

func GCEListVMs(api *compute.Service, opts *GCEOptions, prefix string) ([]Machine, error) {
	var vms []Machine

	list, err := api.Instances.List(opts.Project, opts.Zone).Do()
	if err != nil {
		return nil, err
	}

	for _, inst := range list.Items {
		if !strings.HasPrefix(inst.Name, prefix) {
			continue
		}
		intIP, extIP := instanceIPs(inst)
		gm := &gceMachine{
			name:  inst.Name,
			extIP: extIP,
			intIP: intIP,
		}

		vms = append(vms, gm)
	}
	return vms, nil
}

func GCEListImages(client *http.Client, proj, prefix string) ([]string, error) {
	var images []string
	computeService, err := compute.New(client)
	list, err := computeService.Images.List(proj).Do()
	if err != nil {
		return nil, err
	}

	for _, image := range list.Items {
		if !strings.HasPrefix(image.Name, prefix) {
			continue
		}
		images = append(images, image.Name)
	}
	return images, nil
}

//Some code taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
func gceMakeInstance(opts *GCEOptions, userdata string, name string) (*compute.Instance, error) {
	prefix := "https://www.googleapis.com/compute/v1/projects/" + opts.Project
	instance := &compute.Instance{
		Name:        name,
		MachineType: prefix + "/zones/" + opts.Zone + "/machineTypes/" + opts.MachineType,
		Metadata:    &compute.Metadata{},
		Disks: []*compute.AttachedDisk{
			{
				AutoDelete: true,
				Boot:       true,
				Type:       "PERSISTENT",
				InitializeParams: &compute.AttachedDiskInitializeParams{
					DiskName:    name,
					SourceImage: prefix + "/global/images/" + opts.Image,
					DiskType:    "/zones/" + opts.Zone + "/diskTypes/" + opts.DiskType,
				},
			},
		},
		NetworkInterfaces: []*compute.NetworkInterface{
			&compute.NetworkInterface{
				AccessConfigs: []*compute.AccessConfig{
					&compute.AccessConfig{
						Type: "ONE_TO_ONE_NAT",
						Name: "External NAT",
					},
				},
				Network: prefix + "/global/networks/" + opts.Network,
			},
		},
	}
	// add cloud config
	if userdata != "" {
		instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
			Key:   "user-data",
			Value: userdata,
		})
	}

	return instance, nil
}

//Some code taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
func gceWaitVM(service *compute.Service, proj, zone, opname string) error {
OpLoop:
	for {
		time.Sleep(2 * time.Second)

		op, err := service.ZoneOperations.Get(proj, zone, opname).Do()
		if err != nil {
			return fmt.Errorf("Failed to get op %s: %v", opname, err)
		}
		switch op.Status {
		case "PENDING", "RUNNING":
			continue
		case "DONE":
			if op.Error != nil {
				for _, operr := range op.Error.Errors {
					return fmt.Errorf("Error creating instance: %+v", operr)
				}
				return fmt.Errorf("Failed to start.")
			}
			break OpLoop
		default:
			return fmt.Errorf("Unknown create status %q: %+v", op.Status, op)
		}
	}

	return nil
}

// newName returns a random name prefixed by BaseName
func newName(opts *GCEOptions) (string, error) {
	base := opts.BaseName

	randBytes := make([]byte, 16) //128 bits of entropy
	_, err := rand.Read(randBytes)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("%v-%x", base, randBytes), nil
}

// Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go#L323
func instanceIPs(inst *compute.Instance) (intIP, extIP string) {
	for _, iface := range inst.NetworkInterfaces {
		if strings.HasPrefix(iface.NetworkIP, "10.") {
			intIP = iface.NetworkIP
		}
		for _, accessConfig := range iface.AccessConfigs {
			if accessConfig.Type == "ONE_TO_ONE_NAT" {
				extIP = accessConfig.NatIP
			}
		}
	}
	return
}

func sshCheck(gm *gceMachine) error {
	var err error

	// Allow a few authentication failures in case setup is slow.
	sshchecker := func() error {
		gm.sshClient, err = gm.gc.sshAgent.NewClient(gm.IP())
		if err != nil {
			return err
		}
		return nil
	}

	if err := util.Retry(sshRetries, sshTimeout, sshchecker); err != nil {
		return err
	}

	// sanity check
	out, err := gm.SSH("grep ^ID= /etc/os-release")
	if err != nil {
		return err
	}

	if !bytes.Equal(out, []byte("ID=coreos")) {
		return fmt.Errorf("Unexpected SSH output: %s", out)
	}

	return nil
}
<commit_msg>platform/gce: wait for image creation to finish on request<commit_after>// Copyright 2015 CoreOS, Inc.
// Copyright 2015 The Go 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 platform

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/coreos/mantle/Godeps/_workspace/src/github.com/coreos/coreos-cloudinit/config"
	"github.com/coreos/mantle/Godeps/_workspace/src/golang.org/x/crypto/ssh"
	"github.com/coreos/mantle/Godeps/_workspace/src/google.golang.org/api/compute/v1"
	"github.com/coreos/mantle/auth"
	"github.com/coreos/mantle/network"
	"github.com/coreos/mantle/util"
)

type GCEOptions struct {
	Image       string
	Project     string
	Zone        string
	MachineType string
	DiskType    string
	BaseName    string
	Network     string
	ServiceAuth bool
}

type gceCluster struct {
	api      *compute.Service
	sshAgent *network.SSHAgent
	conf     *GCEOptions
	machines map[string]*gceMachine
	mu       sync.Mutex // protects concurrent access to machines
}

type gceMachine struct {
	gc        *gceCluster
	name      string
	intIP     string
	extIP     string
	sshClient *ssh.Client
}

func NewGCECluster(conf GCEOptions) (Cluster, error) {
	var client *http.Client
	var err error
	if conf.ServiceAuth {
		client = auth.GoogleServiceClient()
	} else {
		client, err = auth.GoogleClient()
	}
	if err != nil {
		return nil, err
	}

	api, err := compute.New(client)
	if err != nil {
		return nil, err
	}

	gc := &gceCluster{
		api:      api,
		conf:     &conf,
		machines: make(map[string]*gceMachine),
	}

	gc.sshAgent, err = network.NewSSHAgent(&net.Dialer{})
	if err != nil {
		return nil, err
	}

	return gc, nil
}

func (gc *gceCluster) NewCommand(name string, arg ...string) util.Cmd {
	return util.NewCommand(name, arg...)
}

func (gc *gceCluster) EtcdEndpoint() string {
	return ""
}

func (gc *gceCluster) Machines() []Machine {
	machines := make([]Machine, 0, len(gc.machines))

	gc.mu.Lock()
	for _, m := range gc.machines {
		machines = append(machines, m)
	}
	gc.mu.Unlock()

	return machines
}

func (gc *gceCluster) Destroy() error {
	for _, gm := range gc.machines {
		gm.Destroy()
	}
	gc.sshAgent.Close()
	return nil
}

// Calling in parallel is ok
func (gc *gceCluster) NewMachine(cloudConfig string) (Machine, error) {
	cconfig, err := config.NewCloudConfig(cloudConfig)
	if err != nil {
		return nil, err
	}
	if err = gc.sshAgent.UpdateConfig(cconfig); err != nil {
		return nil, err
	}
	cloudConfig = cconfig.String()

	// Create gce VM and wait for creation to succeed.
	gm, err := GCECreateVM(gc.api, gc.conf, cloudConfig)
	if err != nil {
		return nil, err
	}
	gm.gc = gc

	err = sshCheck(gm)
	if err != nil {
		gm.Destroy()
		return nil, err
	}

	gc.mu.Lock()
	gc.machines[gm.ID()] = gm
	gc.mu.Unlock()

	return Machine(gm), nil
}

func (gce *gceCluster) GetDiscoveryURL(size int) (string, error) {
	resp, err := http.Get(fmt.Sprintf("https://discovery.etcd.io/new?size=%v", size))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

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

func (gm *gceMachine) ID() string {
	return gm.name
}

func (gm *gceMachine) IP() string {
	return gm.extIP
}

func (gm *gceMachine) PrivateIP() string {
	return gm.intIP
}

func (gm *gceMachine) SSHSession() (*ssh.Session, error) {
	session, err := gm.sshClient.NewSession()
	if err != nil {
		return nil, err
	}

	return session, nil
}

func (gm *gceMachine) SSH(cmd string) ([]byte, error) {
	session, err := gm.SSHSession()
	if err != nil {
		return []byte{}, err
	}
	defer session.Close()

	session.Stderr = os.Stderr
	out, err := session.Output(cmd)
	out = bytes.TrimSpace(out)
	return out, err
}

func (gm *gceMachine) StartJournal() error {
	s, err := gm.SSHSession()
	if err != nil {
		return fmt.Errorf("SSH session failed: %v", err)
	}

	s.Stdout = os.Stdout
	s.Stderr = os.Stderr
	go func() {
		s.Run("journalctl -f")
		s.Close()
	}()

	return nil
}

func (gm *gceMachine) Destroy() error {
	if gm.sshClient != nil {
		gm.sshClient.Close()
	}

	_, err := gm.gc.api.Instances.Delete(gm.gc.conf.Project, gm.gc.conf.Zone, gm.name).Do()
	if err != nil {
		return err
	}

	gm.gc.mu.Lock()
	delete(gm.gc.machines, gm.ID())
	gm.gc.mu.Unlock()

	return nil
}

func GCECreateVM(api *compute.Service, opts *GCEOptions, userdata string) (*gceMachine, error) {
	if userdata != "" {
		_, err := config.NewCloudConfig(userdata)
		if err != nil {
			return nil, err
		}
	}

	// generate name
	name, err := newName(opts)
	if err != nil {
		return nil, fmt.Errorf("Failed allocating unique name for vm: %v\n", err)
	}

	instance, err := gceMakeInstance(opts, userdata, name)
	if err != nil {
		return nil, err
	}

	// request instance
	op, err := api.Instances.Insert(opts.Project, opts.Zone, instance).Do()
	if err != nil {
		return nil, fmt.Errorf("Failed to create new VM: %v\n", err)
	}

	fmt.Fprintf(os.Stderr, "Instance %v requested\n", name)
	fmt.Fprintf(os.Stderr, "Waiting for creation to finish...\n")

	// wait for creation to finish
	err = gceWaitVM(api, opts.Project, opts.Zone, op.Name)
	if err != nil {
		return nil, err
	}

	inst, err := api.Instances.Get(opts.Project, opts.Zone, name).Do()
	if err != nil {
		return nil, fmt.Errorf("Error getting instance %s details after creation: %v", name, err)
	}
	intIP, extIP := instanceIPs(inst)

	gm := &gceMachine{
		name:  name,
		extIP: extIP,
		intIP: intIP,
	}

	return gm, nil
}

func GCEDestroyVM(api *compute.Service, proj, zone, name string) error {
	_, err := api.Instances.Delete(proj, zone, name).Do()
	if err != nil {
		return err
	}
	return nil
}

// Create image on GCE and and wait for completion. Will not overwrite
// existing image.
func GCECreateImage(api *compute.Service, proj, name, source string) error {
	image := &compute.Image{
		Name: name,
		RawDisk: &compute.ImageRawDisk{
			Source: source,
		},
	}

	fmt.Fprintf(os.Stderr, "Image %v requested\n", name)
	fmt.Fprintf(os.Stderr, "Waiting for image creation to finish...\n")

	op, err := api.Images.Insert(proj, image).Do()
	if err != nil {
		return err
	}

	err = gceWaitOp(api, proj, op.Name)
	if err != nil {
		return err
	}
	return nil
}

// Delete image on GCE and then recreate it.
func GCEForceCreateImage(api *compute.Service, proj, name, source string) error {
	_, err := api.Images.Delete(proj, name).Do()

	// don't return error when delete fails because image doesn't exist
	if err != nil && !strings.HasSuffix(err.Error(), "notFound") {
		return fmt.Errorf("deleting image: %v", err)
	}

	// create
	return GCECreateImage(api, proj, name, source)
}

func GCEListVMs(api *compute.Service, opts *GCEOptions, prefix string) ([]Machine, error) {
	var vms []Machine

	list, err := api.Instances.List(opts.Project, opts.Zone).Do()
	if err != nil {
		return nil, err
	}

	for _, inst := range list.Items {
		if !strings.HasPrefix(inst.Name, prefix) {
			continue
		}
		intIP, extIP := instanceIPs(inst)
		gm := &gceMachine{
			name:  inst.Name,
			extIP: extIP,
			intIP: intIP,
		}

		vms = append(vms, gm)
	}
	return vms, nil
}

func GCEListImages(client *http.Client, proj, prefix string) ([]string, error) {
	var images []string
	computeService, err := compute.New(client)
	list, err := computeService.Images.List(proj).Do()
	if err != nil {
		return nil, err
	}

	for _, image := range list.Items {
		if !strings.HasPrefix(image.Name, prefix) {
			continue
		}
		images = append(images, image.Name)
	}
	return images, nil
}

//Some code taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
func gceMakeInstance(opts *GCEOptions, userdata string, name string) (*compute.Instance, error) {
	prefix := "https://www.googleapis.com/compute/v1/projects/" + opts.Project
	instance := &compute.Instance{
		Name:        name,
		MachineType: prefix + "/zones/" + opts.Zone + "/machineTypes/" + opts.MachineType,
		Metadata:    &compute.Metadata{},
		Disks: []*compute.AttachedDisk{
			{
				AutoDelete: true,
				Boot:       true,
				Type:       "PERSISTENT",
				InitializeParams: &compute.AttachedDiskInitializeParams{
					DiskName:    name,
					SourceImage: prefix + "/global/images/" + opts.Image,
					DiskType:    "/zones/" + opts.Zone + "/diskTypes/" + opts.DiskType,
				},
			},
		},
		NetworkInterfaces: []*compute.NetworkInterface{
			&compute.NetworkInterface{
				AccessConfigs: []*compute.AccessConfig{
					&compute.AccessConfig{
						Type: "ONE_TO_ONE_NAT",
						Name: "External NAT",
					},
				},
				Network: prefix + "/global/networks/" + opts.Network,
			},
		},
	}
	// add cloud config
	if userdata != "" {
		instance.Metadata.Items = append(instance.Metadata.Items, &compute.MetadataItems{
			Key:   "user-data",
			Value: userdata,
		})
	}

	return instance, nil
}

//Some code taken from: https://github.com/golang/build/blob/master/buildlet/gce.go
func gceWaitVM(api *compute.Service, proj, zone, opname string) error {
OpLoop:
	for {
		time.Sleep(2 * time.Second)

		op, err := api.ZoneOperations.Get(proj, zone, opname).Do()
		if err != nil {
			return fmt.Errorf("Failed to get op %s: %v", opname, err)
		}
		switch op.Status {
		case "PENDING", "RUNNING":
			continue
		case "DONE":
			if op.Error != nil {
				for _, operr := range op.Error.Errors {
					return fmt.Errorf("Error creating instance: %+v", operr)
				}
				return fmt.Errorf("Failed to start.")
			}
			break OpLoop
		default:
			return fmt.Errorf("Unknown create status %q: %+v", op.Status, op)
		}
	}

	return nil
}

func gceWaitOp(api *compute.Service, proj, opname string) error {
OpLoop:
	for {
		time.Sleep(2 * time.Second)

		op, err := api.GlobalOperations.Get(proj, opname).Do()
		if err != nil {
			return fmt.Errorf("Failed to get op %s: %v", opname, err)
		}
		switch op.Status {
		case "PENDING", "RUNNING":
			continue
		case "DONE":
			if op.Error != nil {
				for _, operr := range op.Error.Errors {
					return fmt.Errorf("Error creating instance: %+v", operr)
				}
				return fmt.Errorf("Failed to start.")
			}
			break OpLoop
		default:
			return fmt.Errorf("Unknown create status %q: %+v", op.Status, op)
		}
	}

	return nil

}

// newName returns a random name prefixed by BaseName
func newName(opts *GCEOptions) (string, error) {
	base := opts.BaseName

	randBytes := make([]byte, 16) //128 bits of entropy
	_, err := rand.Read(randBytes)
	if err != nil {
		return "", err
	}

	return fmt.Sprintf("%v-%x", base, randBytes), nil
}

// Taken from: https://github.com/golang/build/blob/master/buildlet/gce.go#L323
func instanceIPs(inst *compute.Instance) (intIP, extIP string) {
	for _, iface := range inst.NetworkInterfaces {
		if strings.HasPrefix(iface.NetworkIP, "10.") {
			intIP = iface.NetworkIP
		}
		for _, accessConfig := range iface.AccessConfigs {
			if accessConfig.Type == "ONE_TO_ONE_NAT" {
				extIP = accessConfig.NatIP
			}
		}
	}
	return
}

func sshCheck(gm *gceMachine) error {
	var err error

	// Allow a few authentication failures in case setup is slow.
	sshchecker := func() error {
		gm.sshClient, err = gm.gc.sshAgent.NewClient(gm.IP())
		if err != nil {
			return err
		}
		return nil
	}

	if err := util.Retry(sshRetries, sshTimeout, sshchecker); err != nil {
		return err
	}

	// sanity check
	out, err := gm.SSH("grep ^ID= /etc/os-release")
	if err != nil {
		return err
	}

	if !bytes.Equal(out, []byte("ID=coreos")) {
		return fmt.Errorf("Unexpected SSH output: %s", out)
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Google 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 stackdriver

import (
	"errors"
	"fmt"
	"math"
	"strings"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/golang/glog"
	timestamp_pb "github.com/golang/protobuf/ptypes/timestamp"
	"github.com/jkohen/prometheus/retrieval"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/prometheus/pkg/timestamp"
	distribution_pb "google.golang.org/genproto/googleapis/api/distribution"
	metric_pb "google.golang.org/genproto/googleapis/api/metric"
	monitoredres_pb "google.golang.org/genproto/googleapis/api/monitoredres"
	monitoring_pb "google.golang.org/genproto/googleapis/monitoring/v3"
)

var supportedMetricTypes = map[dto.MetricType]bool{
	dto.MetricType_COUNTER:   true,
	dto.MetricType_GAUGE:     true,
	dto.MetricType_HISTOGRAM: true,
}

const (
	falseValueEpsilon = 0.001
	maxLabelCount     = 10
)

type unsupportedTypeError struct {
	metricType dto.MetricType
}

func (e *unsupportedTypeError) Error() string {
	return e.metricType.String()
}

// Translator allows converting Prometheus samples to Stackdriver TimeSeries.
type Translator struct {
	logger           log.Logger
	metricsPrefix    string
	resourceMappings []ResourceMap
}

// NewTranslator creates a new Translator.
func NewTranslator(logger log.Logger, metricsPrefix string, resourceMappings []ResourceMap) *Translator {
	return &Translator{
		logger:           logger,
		metricsPrefix:    metricsPrefix,
		resourceMappings: resourceMappings,
	}
}

// ToCreateTimeSeriesRequest translates metrics in Prometheus format to Stackdriver format.
func (t *Translator) ToCreateTimeSeriesRequest(
	metrics []*retrieval.MetricFamily) *monitoring_pb.CreateTimeSeriesRequest {

	// TODO(jkohen): See if it's possible for Prometheus to pass two points
	// for the same time series, which isn't accepted by the Stackdriver
	// Monitoring API.
	request := &monitoring_pb.CreateTimeSeriesRequest{
		TimeSeries: []*monitoring_pb.TimeSeries{},
	}
	for _, family := range metrics {
		tss, err := t.translateFamily(family)
		if err != nil {
			// Ignore unsupported type errors, they're just noise.
			if _, ok := err.(*unsupportedTypeError); !ok {
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"metric", family.GetName(),
					"err", err)
			}
		} else {
			request.TimeSeries = append(request.TimeSeries, tss...)
		}
	}
	return request
}

func (t *Translator) translateFamily(family *retrieval.MetricFamily) ([]*monitoring_pb.TimeSeries, error) {
	var tss []*monitoring_pb.TimeSeries
	if _, found := supportedMetricTypes[family.GetType()]; !found {
		return tss, &unsupportedTypeError{family.GetType()}
	}
	for i, metric := range family.GetMetric() {
		startTime := timestamp.Time(family.MetricResetTimestampMs[i])
		ts, err := t.translateOne(family.GetName(), family.GetType(), metric, startTime)
		if err != nil {
			// Metrics are usually independent, so just drop this one.
			level.Warn(t.logger).Log(
				"msg", "error while processing metric",
				"family", family.GetName(),
				"metric", metric,
				"err", err)
			continue
		}
		tss = append(tss, ts)
	}
	return tss, nil
}

// getMetricType creates metric type name base on the metric prefix, and metric name.
func getMetricType(metricsPrefix string, name string) string {
	// This does no allocations, versus 12 with fmt.Sprintf.
	return metricsPrefix + "/" + name
}

func getTimestamp(ts time.Time) *timestamp_pb.Timestamp {
	return &timestamp_pb.Timestamp{
		Seconds: ts.Truncate(time.Second).Unix(),
		Nanos:   int32(ts.Nanosecond()),
	}
}

// assumes that mType is Counter, Gauge or Histogram. Returns nil on error.
func (t *Translator) translateOne(name string,
	mType dto.MetricType,
	metric *dto.Metric,
	start time.Time) (*monitoring_pb.TimeSeries, error) {
	monitoredResource := t.getMonitoredResource(metric)
	if monitoredResource == nil {
		return nil, errors.New("cannot extract Stackdriver monitored resource from metric")
	}
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	metricKind := extractMetricKind(mType)
	if metricKind == metric_pb.MetricDescriptor_CUMULATIVE {
		interval.StartTime = getTimestamp(start.UTC())
	}
	valueType := extractValueType(mType)
	point := &monitoring_pb.Point{
		Interval: interval,
	}
	setValue(mType, valueType, metric, point)

	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}
	return &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   getMetricType(t.metricsPrefix, name),
		},
		Resource:   monitoredResource,
		MetricKind: metricKind,
		ValueType:  valueType,
		Points:     []*monitoring_pb.Point{point},
	}, nil
}

func setValue(
	mType dto.MetricType, valueType metric_pb.MetricDescriptor_ValueType,
	metric *dto.Metric, point *monitoring_pb.Point) {

	point.Value = &monitoring_pb.TypedValue{}
	if mType == dto.MetricType_GAUGE {
		setValueBaseOnSimpleType(metric.GetGauge().GetValue(), valueType, point)
	} else if mType == dto.MetricType_HISTOGRAM {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DistributionValue{
				DistributionValue: convertToDistributionValue(metric.GetHistogram()),
			},
		}
	} else {
		setValueBaseOnSimpleType(metric.GetCounter().GetValue(), valueType, point)
	}
}

func setValueBaseOnSimpleType(
	value float64, valueType metric_pb.MetricDescriptor_ValueType,
	point *monitoring_pb.Point) {

	if valueType == metric_pb.MetricDescriptor_INT64 {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_Int64Value{Int64Value: int64(value)},
		}
	} else if valueType == metric_pb.MetricDescriptor_DOUBLE {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DoubleValue{DoubleValue: value},
		}
	} else if valueType == metric_pb.MetricDescriptor_BOOL {
		var val = math.Abs(value) > falseValueEpsilon
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_BoolValue{BoolValue: val},
		}
	} else {
		glog.Errorf("Value type '%s' is not supported yet.", valueType)
	}
}

func convertToDistributionValue(h *dto.Histogram) *distribution_pb.Distribution {
	count := int64(h.GetSampleCount())
	mean := float64(0)
	dev := float64(0)
	bounds := []float64{}
	values := []int64{}

	if count > 0 {
		mean = h.GetSampleSum() / float64(count)
	}

	prevVal := uint64(0)
	lower := float64(0)
	for _, b := range h.Bucket {
		upper := b.GetUpperBound()
		if !math.IsInf(b.GetUpperBound(), 1) {
			bounds = append(bounds, b.GetUpperBound())
		} else {
			upper = lower
		}
		val := b.GetCumulativeCount() - prevVal
		x := (lower + upper) / float64(2)
		dev += float64(val) * (x - mean) * (x - mean)

		values = append(values, int64(b.GetCumulativeCount()-prevVal))

		lower = b.GetUpperBound()
		prevVal = b.GetCumulativeCount()
	}

	return &distribution_pb.Distribution{
		Count: count,
		Mean:  mean,
		SumOfSquaredDeviation: dev,
		BucketOptions: &distribution_pb.Distribution_BucketOptions{
			Options: &distribution_pb.Distribution_BucketOptions_ExplicitBuckets{
				ExplicitBuckets: &distribution_pb.Distribution_BucketOptions_Explicit{
					Bounds: bounds,
				},
			},
		},
		BucketCounts: values,
	}
}

// getMetricLabels returns a Stackdriver label map from the label.
//
// By convention it excludes any Prometheus labels with "_" prefix, which
// includes the labels that correspond to Stackdriver resource labels.
func getMetricLabels(labels []*dto.LabelPair) map[string]string {
	metricLabels := map[string]string{}
	for _, label := range labels {
		if strings.HasPrefix(string(label.GetName()), "_") {
			continue
		}
		metricLabels[label.GetName()] = label.GetValue()
	}
	return metricLabels
}

func extractMetricKind(mType dto.MetricType) metric_pb.MetricDescriptor_MetricKind {
	if mType == dto.MetricType_COUNTER || mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_CUMULATIVE
	}
	return metric_pb.MetricDescriptor_GAUGE
}

func extractValueType(mType dto.MetricType) metric_pb.MetricDescriptor_ValueType {
	if mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_DISTRIBUTION
	}
	return metric_pb.MetricDescriptor_DOUBLE
}

func (t *Translator) getMonitoredResource(metric *dto.Metric) *monitoredres_pb.MonitoredResource {
	for _, resource := range t.resourceMappings {
		if labels := resource.Translate(metric); labels != nil {
			return &monitoredres_pb.MonitoredResource{
				Type:   resource.Type,
				Labels: labels,
			}
		}
	}
	return nil
}
<commit_msg>Removed unnecessary cast to string.<commit_after>/*
Copyright 2017 Google 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 stackdriver

import (
	"errors"
	"fmt"
	"math"
	"strings"
	"time"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/golang/glog"
	timestamp_pb "github.com/golang/protobuf/ptypes/timestamp"
	"github.com/jkohen/prometheus/retrieval"
	dto "github.com/prometheus/client_model/go"
	"github.com/prometheus/prometheus/pkg/timestamp"
	distribution_pb "google.golang.org/genproto/googleapis/api/distribution"
	metric_pb "google.golang.org/genproto/googleapis/api/metric"
	monitoredres_pb "google.golang.org/genproto/googleapis/api/monitoredres"
	monitoring_pb "google.golang.org/genproto/googleapis/monitoring/v3"
)

var supportedMetricTypes = map[dto.MetricType]bool{
	dto.MetricType_COUNTER:   true,
	dto.MetricType_GAUGE:     true,
	dto.MetricType_HISTOGRAM: true,
}

const (
	falseValueEpsilon = 0.001
	maxLabelCount     = 10
)

type unsupportedTypeError struct {
	metricType dto.MetricType
}

func (e *unsupportedTypeError) Error() string {
	return e.metricType.String()
}

// Translator allows converting Prometheus samples to Stackdriver TimeSeries.
type Translator struct {
	logger           log.Logger
	metricsPrefix    string
	resourceMappings []ResourceMap
}

// NewTranslator creates a new Translator.
func NewTranslator(logger log.Logger, metricsPrefix string, resourceMappings []ResourceMap) *Translator {
	return &Translator{
		logger:           logger,
		metricsPrefix:    metricsPrefix,
		resourceMappings: resourceMappings,
	}
}

// ToCreateTimeSeriesRequest translates metrics in Prometheus format to Stackdriver format.
func (t *Translator) ToCreateTimeSeriesRequest(
	metrics []*retrieval.MetricFamily) *monitoring_pb.CreateTimeSeriesRequest {

	// TODO(jkohen): See if it's possible for Prometheus to pass two points
	// for the same time series, which isn't accepted by the Stackdriver
	// Monitoring API.
	request := &monitoring_pb.CreateTimeSeriesRequest{
		TimeSeries: []*monitoring_pb.TimeSeries{},
	}
	for _, family := range metrics {
		tss, err := t.translateFamily(family)
		if err != nil {
			// Ignore unsupported type errors, they're just noise.
			if _, ok := err.(*unsupportedTypeError); !ok {
				level.Warn(t.logger).Log(
					"msg", "error while processing metric",
					"metric", family.GetName(),
					"err", err)
			}
		} else {
			request.TimeSeries = append(request.TimeSeries, tss...)
		}
	}
	return request
}

func (t *Translator) translateFamily(family *retrieval.MetricFamily) ([]*monitoring_pb.TimeSeries, error) {
	var tss []*monitoring_pb.TimeSeries
	if _, found := supportedMetricTypes[family.GetType()]; !found {
		return tss, &unsupportedTypeError{family.GetType()}
	}
	for i, metric := range family.GetMetric() {
		startTime := timestamp.Time(family.MetricResetTimestampMs[i])
		ts, err := t.translateOne(family.GetName(), family.GetType(), metric, startTime)
		if err != nil {
			// Metrics are usually independent, so just drop this one.
			level.Warn(t.logger).Log(
				"msg", "error while processing metric",
				"family", family.GetName(),
				"metric", metric,
				"err", err)
			continue
		}
		tss = append(tss, ts)
	}
	return tss, nil
}

// getMetricType creates metric type name base on the metric prefix, and metric name.
func getMetricType(metricsPrefix string, name string) string {
	// This does no allocations, versus 12 with fmt.Sprintf.
	return metricsPrefix + "/" + name
}

func getTimestamp(ts time.Time) *timestamp_pb.Timestamp {
	return &timestamp_pb.Timestamp{
		Seconds: ts.Truncate(time.Second).Unix(),
		Nanos:   int32(ts.Nanosecond()),
	}
}

// assumes that mType is Counter, Gauge or Histogram. Returns nil on error.
func (t *Translator) translateOne(name string,
	mType dto.MetricType,
	metric *dto.Metric,
	start time.Time) (*monitoring_pb.TimeSeries, error) {
	monitoredResource := t.getMonitoredResource(metric)
	if monitoredResource == nil {
		return nil, errors.New("cannot extract Stackdriver monitored resource from metric")
	}
	interval := &monitoring_pb.TimeInterval{
		EndTime: getTimestamp(timestamp.Time(metric.GetTimestampMs()).UTC()),
	}
	metricKind := extractMetricKind(mType)
	if metricKind == metric_pb.MetricDescriptor_CUMULATIVE {
		interval.StartTime = getTimestamp(start.UTC())
	}
	valueType := extractValueType(mType)
	point := &monitoring_pb.Point{
		Interval: interval,
	}
	setValue(mType, valueType, metric, point)

	tsLabels := getMetricLabels(metric.GetLabel())
	if len(tsLabels) > maxLabelCount {
		return nil, fmt.Errorf(
			"dropping metric because it has more than %v labels, and Stackdriver would reject it",
			maxLabelCount)
	}
	return &monitoring_pb.TimeSeries{
		Metric: &metric_pb.Metric{
			Labels: tsLabels,
			Type:   getMetricType(t.metricsPrefix, name),
		},
		Resource:   monitoredResource,
		MetricKind: metricKind,
		ValueType:  valueType,
		Points:     []*monitoring_pb.Point{point},
	}, nil
}

func setValue(
	mType dto.MetricType, valueType metric_pb.MetricDescriptor_ValueType,
	metric *dto.Metric, point *monitoring_pb.Point) {

	point.Value = &monitoring_pb.TypedValue{}
	if mType == dto.MetricType_GAUGE {
		setValueBaseOnSimpleType(metric.GetGauge().GetValue(), valueType, point)
	} else if mType == dto.MetricType_HISTOGRAM {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DistributionValue{
				DistributionValue: convertToDistributionValue(metric.GetHistogram()),
			},
		}
	} else {
		setValueBaseOnSimpleType(metric.GetCounter().GetValue(), valueType, point)
	}
}

func setValueBaseOnSimpleType(
	value float64, valueType metric_pb.MetricDescriptor_ValueType,
	point *monitoring_pb.Point) {

	if valueType == metric_pb.MetricDescriptor_INT64 {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_Int64Value{Int64Value: int64(value)},
		}
	} else if valueType == metric_pb.MetricDescriptor_DOUBLE {
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_DoubleValue{DoubleValue: value},
		}
	} else if valueType == metric_pb.MetricDescriptor_BOOL {
		var val = math.Abs(value) > falseValueEpsilon
		point.Value = &monitoring_pb.TypedValue{
			Value: &monitoring_pb.TypedValue_BoolValue{BoolValue: val},
		}
	} else {
		glog.Errorf("Value type '%s' is not supported yet.", valueType)
	}
}

func convertToDistributionValue(h *dto.Histogram) *distribution_pb.Distribution {
	count := int64(h.GetSampleCount())
	mean := float64(0)
	dev := float64(0)
	bounds := []float64{}
	values := []int64{}

	if count > 0 {
		mean = h.GetSampleSum() / float64(count)
	}

	prevVal := uint64(0)
	lower := float64(0)
	for _, b := range h.Bucket {
		upper := b.GetUpperBound()
		if !math.IsInf(b.GetUpperBound(), 1) {
			bounds = append(bounds, b.GetUpperBound())
		} else {
			upper = lower
		}
		val := b.GetCumulativeCount() - prevVal
		x := (lower + upper) / float64(2)
		dev += float64(val) * (x - mean) * (x - mean)

		values = append(values, int64(b.GetCumulativeCount()-prevVal))

		lower = b.GetUpperBound()
		prevVal = b.GetCumulativeCount()
	}

	return &distribution_pb.Distribution{
		Count: count,
		Mean:  mean,
		SumOfSquaredDeviation: dev,
		BucketOptions: &distribution_pb.Distribution_BucketOptions{
			Options: &distribution_pb.Distribution_BucketOptions_ExplicitBuckets{
				ExplicitBuckets: &distribution_pb.Distribution_BucketOptions_Explicit{
					Bounds: bounds,
				},
			},
		},
		BucketCounts: values,
	}
}

// getMetricLabels returns a Stackdriver label map from the label.
//
// By convention it excludes any Prometheus labels with "_" prefix, which
// includes the labels that correspond to Stackdriver resource labels.
func getMetricLabels(labels []*dto.LabelPair) map[string]string {
	metricLabels := map[string]string{}
	for _, label := range labels {
		if strings.HasPrefix(label.GetName(), "_") {
			continue
		}
		metricLabels[label.GetName()] = label.GetValue()
	}
	return metricLabels
}

func extractMetricKind(mType dto.MetricType) metric_pb.MetricDescriptor_MetricKind {
	if mType == dto.MetricType_COUNTER || mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_CUMULATIVE
	}
	return metric_pb.MetricDescriptor_GAUGE
}

func extractValueType(mType dto.MetricType) metric_pb.MetricDescriptor_ValueType {
	if mType == dto.MetricType_HISTOGRAM {
		return metric_pb.MetricDescriptor_DISTRIBUTION
	}
	return metric_pb.MetricDescriptor_DOUBLE
}

func (t *Translator) getMonitoredResource(metric *dto.Metric) *monitoredres_pb.MonitoredResource {
	for _, resource := range t.resourceMappings {
		if labels := resource.Translate(metric); labels != nil {
			return &monitoredres_pb.MonitoredResource{
				Type:   resource.Type,
				Labels: labels,
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package sirpent

import (
	"encoding/json"
	//"errors"
	"fmt"
)

type PlayerState struct {
	Player *Player
	// Player-chosen Direction this tick.
	Move Direction
	// Player alive after this tick?
	Alive bool
	// The current state of the snake.
	Snake Snake
	// Food item eaten this tick.
	//EatenFood *Food
}

func NewPlayerState(p *Player, s Snake) *PlayerState {
	return &PlayerState{
		Player: p,
		Alive:  true,
		Snake:  s,
	}
}

func (ps PlayerState) Successor(gs *GameState) (*PlayerState, error) {
	direction, err := ps.Player.requestMove(gs)

	// Get Alive from the Player, in case the player was killed by RequestMove/etc.
	ps2 := &PlayerState{
		Player: ps.Player,
		Move:   direction,
		Alive:  ps.Player.Alive,
	}
	fmt.Printf("player id=%s direction=%s previous snake=%+v ", ps2.Player.ID, ps2.Move, ps.Snake)

	// If there was an error in RequestMove or the player isn't alive (which often go together)
	// then no further change of state needed.
	if err != nil || !ps2.Alive {
		return ps2, err
	}

	// Update snake position.
	// @TODO: How should StepInDirection grow_extra_segment argument come in?
	ps2.Snake = ps.Snake.Move(ps2.Move, false)
	fmt.Printf("next snake=%+v\n", ps2.Snake)

	return ps2, err
}

func (ps *PlayerState) MarshalJSON() ([]byte, error) {
	ps_for_json := struct {
		PlayerID UUID
		Move     Direction
		Alive    bool
		Snake    Snake
	}{
		PlayerID: ps.Player.ID,
		Move:     ps.Move,
		Alive:    ps.Alive,
		Snake:    ps.Snake,
	}

	return json.Marshal(ps_for_json)
}

func (ps *PlayerState) UnmarshalJSON(b []byte) error {
	ps_for_json := struct {
		PlayerID UUID
		Move     Direction
		Alive    bool
		Snake    Snake
	}{}

	err := json.Unmarshal(b, &ps_for_json)
	if err == nil {
		// @TODO: Need to document that this creates barebones player structs.
		ps.Player = &Player{ID: ps_for_json.PlayerID}
		ps.Move = ps_for_json.Move
		ps.Alive = ps_for_json.Alive
		ps.Snake = ps_for_json.Snake
	}

	return err
}
<commit_msg>Fixed PlayerState's MarshalJSON.<commit_after>package sirpent

import (
	"encoding/json"
	//"errors"
	"fmt"
)

type PlayerState struct {
	Player *Player
	// Player-chosen Direction this tick.
	Move Direction
	// Player alive after this tick?
	Alive bool
	// The current state of the snake.
	Snake Snake
	// Food item eaten this tick.
	//EatenFood *Food
}

func NewPlayerState(p *Player, s Snake) *PlayerState {
	return &PlayerState{
		Player: p,
		Alive:  true,
		Snake:  s,
	}
}

func (ps PlayerState) Successor(gs *GameState) (*PlayerState, error) {
	direction, err := ps.Player.requestMove(gs)

	// Get Alive from the Player, in case the player was killed by RequestMove/etc.
	ps2 := &PlayerState{
		Player: ps.Player,
		Move:   direction,
		Alive:  ps.Player.Alive,
	}
	fmt.Printf("player id=%s direction=%s previous snake=%+v ", ps2.Player.ID, ps2.Move, ps.Snake)

	// If there was an error in RequestMove or the player isn't alive (which often go together)
	// then no further change of state needed.
	if err != nil || !ps2.Alive {
		return ps2, err
	}

	// Update snake position.
	// @TODO: How should StepInDirection grow_extra_segment argument come in?
	ps2.Snake = ps.Snake.Move(ps2.Move, false)
	fmt.Printf("next snake=%+v\n", ps2.Snake)

	return ps2, err
}

func (ps PlayerState) MarshalJSON() ([]byte, error) {
	ps_for_json := struct {
		PlayerID UUID
		Move     Direction
		Alive    bool
		Snake    Snake
	}{
		PlayerID: ps.Player.ID,
		Move:     ps.Move,
		Alive:    ps.Alive,
		Snake:    ps.Snake,
	}

	return json.Marshal(ps_for_json)
}

func (ps *PlayerState) UnmarshalJSON(b []byte) error {
	ps_for_json := struct {
		PlayerID UUID
		Move     Direction
		Alive    bool
		Snake    Snake
	}{}

	err := json.Unmarshal(b, &ps_for_json)
	if err == nil {
		// @TODO: Need to document that this creates barebones player structs.
		ps.Player = &Player{ID: ps_for_json.PlayerID}
		ps.Move = ps_for_json.Move
		ps.Alive = ps_for_json.Alive
		ps.Snake = ps_for_json.Snake
	}

	return err
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced 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 cmd

import (
	"fmt"
	"strings"
)

var (
	treeCharset map[string]string
	treeUTF8    map[string]string
	treeASCII   map[string]string
	treeSPACE   map[string]string
)

func init() {
	treeUTF8 = map[string]string{
		"bar":    "│ ",
		"middle": "├─",
		"last":   "└─",
	}

	treeASCII = map[string]string{
		"bar":    "| ",
		"middle": "|-",
		"last":   "+-",
	}

	treeSPACE = map[string]string{
		"bar":    "  ",
		"middle": "  ",
		"last":   "  ",
	}

	treeCharset = treeUTF8
}

type Table struct {
	Fields     []string
	Padding    int
	rows       []map[string]string
	fieldSize  map[string]int
	treeIndent []int
}

func NewTable(fields []string) *Table {
	return &Table{
		Fields:     fields,
		Padding:    1,
		rows:       make([]map[string]string, 0),
		fieldSize:  make(map[string]int),
		treeIndent: make([]int, 0),
	}
}
func (t *Table) AddRow(row map[string]interface{}) {
	tblrow := make(map[string]string)
	for name, value := range row {
		v := fmt.Sprintf("%v", value)
		tblrow[name] = v
		if maxWidth := len(v); t.fieldSize[name] < maxWidth {
			t.fieldSize[name] = maxWidth
		}
	}
	t.rows = append(t.rows, tblrow)
	if len(t.rows) > len(t.treeIndent) {
		t.treeIndent = append(t.treeIndent, 0)
	}
}
func (t *Table) IndentRow() {
	t.treeIndent = append(t.treeIndent, 1)
}
func (t *Table) DedentRow() {
	t.treeIndent = append(t.treeIndent, -1)
}
func (t *Table) Print() {
	if len(t.Fields) == 0 {
		return
	}
	// compute the padding
	padding := fmt.Sprintf("%"+fmt.Sprintf("%d", t.Padding)+"s", "")
	// compute the first column width and output
	col0width, col0rows := t.getIndents(t.Fields[0])
	// display the headers
	for i, field := range t.Fields {
		var fieldSize int
		if i > 0 {
			if width := len(field); t.fieldSize[field] < width {
				t.fieldSize[field] = width
			}
			fieldSize = t.fieldSize[field]
		} else {
			if width := len(field); col0width < width {
				col0width = width
			}
			fieldSize = col0width
		}
		fmt.Printf("%-"+fmt.Sprintf("%d", fieldSize)+"s"+padding, field)
	}
	fmt.Println()
	// display the rows
	for i, row := range t.rows {
		for j, field := range t.Fields {
			if j > 0 {
				fmt.Printf("%-"+fmt.Sprintf("%d", t.fieldSize[field])+"s"+padding, row[field])
			} else {
				fmt.Printf("%-"+fmt.Sprintf("%d", col0width)+"s"+padding, col0rows[i])
			}
		}
		fmt.Println()
	}
}
func (t *Table) getIndents(field string) (int, []string) {
	// determines if the row is the last parent in the tree
	isLastIndex := func(index int) bool {
		if index+1 < len(t.rows) {
			switch t.treeIndent[index+1] {
			case -1:
				return true
			case 0:
				return false
			case 1:
				level := t.treeIndent[index]
				for _, ind := range t.treeIndent[index+1 : len(t.rows)] {
					if level = level + ind; level == 0 {
						return false
					}
				}
			}
		}
		return true
	}
	maxWidth := t.fieldSize[field]
	rows := make([]string, len(t.rows))
	offset := 0
	indent := ""
	for i, row := range t.rows {
		level := t.treeIndent[i]
		if offset += level; offset <= 0 {
			rows[i] = row[field]
			offset = 0
			continue
		}
		lastIndex := isLastIndex(i)
		if lastIndex {
			rows[i] = indent + treeCharset["last"] + row[field]
		} else {
			rows[i] = indent + treeCharset["middle"] + row[field]
		}
		if i+1 < len(t.rows) {
			switch t.treeIndent[i+1] {
			case -1:
				size := len(indent)
				indent = strings.TrimSuffix(indent, " ")
				if size == len(indent) {
					indent = strings.TrimSuffix(indent, treeCharset["bar"])
				}
			case 1:
				if lastIndex {
					indent += " "
				} else {
					indent += treeCharset["bar"]
				}
			}
		}
		// compute the max width of the column
		if width := len(rows[i]); width > maxWidth {
			maxWidth = width
		}
	}
	return maxWidth, rows
}
<commit_msg>Fixed tree spacing<commit_after>// Copyright 2014 The Serviced 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 cmd

import (
	"fmt"
	"strings"
)

var (
	treeCharset map[string]string
	treeUTF8    map[string]string
	treeASCII   map[string]string
	treeSPACE   map[string]string
)

func init() {
	treeUTF8 = map[string]string{
		"bar":    "│ ",
		"middle": "├─",
		"last":   "└─",
	}

	treeASCII = map[string]string{
		"bar":    "| ",
		"middle": "|-",
		"last":   "+-",
	}

	treeSPACE = map[string]string{
		"bar":    "  ",
		"middle": "  ",
		"last":   "  ",
	}

	treeCharset = treeUTF8
}

type Table struct {
	Fields     []string
	Padding    int
	rows       []map[string]string
	fieldSize  map[string]int
	treeIndent []int
}

func NewTable(fields []string) *Table {
	return &Table{
		Fields:     fields,
		Padding:    1,
		rows:       make([]map[string]string, 0),
		fieldSize:  make(map[string]int),
		treeIndent: make([]int, 0),
	}
}
func (t *Table) AddRow(row map[string]interface{}) {
	tblrow := make(map[string]string)
	for name, value := range row {
		v := fmt.Sprintf("%v", value)
		tblrow[name] = v
		if maxWidth := len(v); t.fieldSize[name] < maxWidth {
			t.fieldSize[name] = maxWidth
		}
	}
	t.rows = append(t.rows, tblrow)
	if len(t.rows) > len(t.treeIndent) {
		t.treeIndent = append(t.treeIndent, 0)
	}
}
func (t *Table) IndentRow() {
	t.treeIndent = append(t.treeIndent, 1)
}
func (t *Table) DedentRow() {
	t.treeIndent = append(t.treeIndent, -1)
}
func (t *Table) Print() {
	if len(t.Fields) == 0 {
		return
	}
	// compute the padding
	padding := fmt.Sprintf("%"+fmt.Sprintf("%d", t.Padding)+"s", "")
	// compute the first column width and output
	col0width, col0rows := t.getIndents(t.Fields[0])
	// display the headers
	for i, field := range t.Fields {
		var fieldSize int
		if i > 0 {
			if width := len(field); t.fieldSize[field] < width {
				t.fieldSize[field] = width
			}
			fieldSize = t.fieldSize[field]
		} else {
			if width := len(field); col0width < width {
				col0width = width
			}
			fieldSize = col0width
		}
		fmt.Printf("%-"+fmt.Sprintf("%d", fieldSize)+"s"+padding, field)
	}
	fmt.Println()
	// display the rows
	for i, row := range t.rows {
		for j, field := range t.Fields {
			if j > 0 {
				fmt.Printf("%-"+fmt.Sprintf("%d", t.fieldSize[field])+"s"+padding, row[field])
			} else {
				fmt.Printf("%-"+fmt.Sprintf("%d", col0width)+"s"+padding, col0rows[i])
			}
		}
		fmt.Println()
	}
}
func (t *Table) getIndents(field string) (int, []string) {
	// determines if the row is the last parent in the tree
	isLastIndex := func(index int) bool {
		if index+1 < len(t.rows) {
			switch t.treeIndent[index+1] {
			case -1:
				return true
			case 0:
				return false
			case 1:
				level := t.treeIndent[index]
				for _, ind := range t.treeIndent[index+1 : len(t.rows)] {
					if level = level + ind; level == 0 {
						return false
					}
				}
			}
		}
		return true
	}
	maxWidth := t.fieldSize[field]
	rows := make([]string, len(t.rows))
	offset := 0
	indent := ""
	for i, row := range t.rows {
		level := t.treeIndent[i]
		if offset += level; offset <= 0 {
			rows[i] = row[field]
			offset = 0
			continue
		}
		lastIndex := isLastIndex(i)
		if lastIndex {
			rows[i] = indent + treeCharset["last"] + row[field]
		} else {
			rows[i] = indent + treeCharset["middle"] + row[field]
		}
		if i+1 < len(t.rows) {
			switch t.treeIndent[i+1] {
			case -1:
				size := len(indent)
				indent = strings.TrimSuffix(indent, "  ")
				if size == len(indent) {
					indent = strings.TrimSuffix(indent, treeCharset["bar"])
				}
			case 1:
				if lastIndex {
					indent += "  "
				} else {
					indent += treeCharset["bar"]
				}
			}
		}
		// compute the max width of the column
		if width := len(rows[i]); width > maxWidth {
			maxWidth = width
		}
	}
	return maxWidth, rows
}
<|endoftext|>


<commit_before>package sqlite3

// #include <sqlite3.h>
import "C"
import (
	"fmt"
	"strconv"
	"time"
)

type Errno int

func (e Errno) Error() (err string) {
	if err = errText[e]; err == "" {
		err = fmt.Sprintf("errno %v", int(e))
	}
	return
}

const(
	OK			= Errno(iota)
	ERROR
	INTERNAL
	PERM
	ABORT
	BUSY
	LOCKED
	NOMEM
	READONLY
	INTERRUPT
	IOERR
	CORRUPT
	NOTFOUND
	FULL
	CANTOPEN
	PROTOCOL
	EMPTY
	SCHEMA
	TOOBIG
	CONSTRAINT
	MISMATCH
	MISUSE
	NOLFS
	AUTH
	FORMAT
	RANGE
	NOTDB
	ROW			= Errno(100)
	DONE		= Errno(101)
	ENCODER		= Errno(1000)
	SAVEPOINT	= Errno(1001)
)

var errText = map[Errno]string {
	ERROR:		"SQL error or missing database",
	INTERNAL:	"Internal logic error in SQLite",
	PERM:		"Access permission denied",
	ABORT:		"Callback routine requested an abort",
	BUSY:		"The database file is locked",
	LOCKED:		"A table in the database is locked",
	NOMEM:		"A malloc() failed",
	READONLY:	"Attempt to write a readonly database",
	INTERRUPT:	"Operation terminated by sqlite3_interrupt()",
	IOERR:		"Some kind of disk I/O error occurred",
	CORRUPT:	"The database disk image is malformed",
	NOTFOUND:	"NOT USED. Table or record not found",
	FULL:		"Insertion failed because database is full",
	CANTOPEN:	"Unable to open the database file",
	PROTOCOL:	"NOT USED. Database lock protocol error",
	EMPTY:		"Database is empty",
	SCHEMA:		"The database schema changed",
	TOOBIG:		"String or BLOB exceeds size limit",
	CONSTRAINT:	"Abort due to constraint violation",
	MISMATCH:	"Data type mismatch",
	MISUSE:		"Library used incorrectly",
	NOLFS:		"Uses OS features not supported on host",
	AUTH:		"Authorization denied",
	FORMAT:		"Auxiliary database format error",
	RANGE:		"2nd parameter to sqlite3_bind out of range",
	NOTDB:		"File opened that is not a database file",
	ROW:		"sqlite3_step() has another row ready",
	DONE:		"sqlite3_step() has finished executing",
	ENCODER:	"blob encoding failed",
	SAVEPOINT:	"invalid or unknown savepoint identifier",
}

type Database struct {
	handle		*C.sqlite3
	Filename	string
	Flags		C.int
	Savepoints	[]interface{}
}

func TransientDatabase() (db *Database) {
	return &Database{ Filename: ":memory:" }
}

func Open(filename string, flags... int) (db *Database, e error) {
	defer func() {
		if x := recover(); x != nil {
			db.Close()
			db = nil
			e = MISUSE
		}
	}()
	db = &Database{ Filename: filename }
	if len(flags) == 0 {
		e = db.Open(C.SQLITE_OPEN_FULLMUTEX, C.SQLITE_OPEN_READWRITE, C.SQLITE_OPEN_CREATE)
	} else {
		e = db.Open(flags...)
	}
	return
}

func (db *Database) Open(flags... int) (e error) {
	if C.sqlite3_threadsafe() == 0 {
		panic("sqlite library is not thread-safe")
	}
	if db.handle != nil {
		e = CANTOPEN
	} else {
		db.Flags = 0
		for _, v := range flags { db.Flags = db.Flags | C.int(v) }
		if err := Errno(C.sqlite3_open_v2(C.CString(db.Filename), &db.handle, db.Flags, nil)); err != OK {
			e = err
		} else if db.handle == nil {
			e = CANTOPEN
		}
	}
	return
}

func (db *Database) Close() {
	C.sqlite3_close(db.handle)
	db.handle = nil
}

func (db *Database) LastInsertRowID() int64 {
	return int64(C.sqlite3_last_insert_rowid(db.handle))
}

func (db *Database) Changes() int {
	return int(C.sqlite3_changes(db.handle))
}

func (db *Database) TotalChanges() int {
	return int(C.sqlite3_total_changes(db.handle))
}

func (db *Database) Error() error {
	return Errno(C.sqlite3_errcode(db.handle))
}

func (db *Database) Prepare(sql string, values... interface{}) (s *Statement, e error) {
	s = &Statement{ db: db, timestamp: time.Now().UnixNano() }
	if rv := Errno(C.sqlite3_prepare_v2(db.handle, C.CString(sql), -1, &s.cptr, nil)); rv != OK {
		s, e = nil, rv
	} else {
		if len(values) > 0 {
			e, _ = s.BindAll(values...)
		}
	}
	return
}

func (db *Database) Execute(sql string, f... func(*Statement, ...interface{})) (c int, e error) {
	var st	*Statement
	st, e = db.Prepare(sql)
	if e == nil {
		c, e = st.All(f...)
	}
	if e == OK {
		e = nil
	}
	return
}

func (db *Database) Begin() (e error) {
	_, e = db.Execute("BEGIN")
	return
}

func (db *Database) Rollback() (e error) {
	_, e = db.Execute("ROLLBACK")
	return
}

func (db *Database) Commit() (e error) {
	_, e = db.Execute("COMMIT")
	return
}

func savepointID(id interface{}) (s string) {
	switch id := id.(type) {
	case string:		s = id
	case []byte:		s = string(id)
	case fmt.Stringer:	s = id.String()
	case int:			s = strconv.Itoa(id)
	case uint:			s = strconv.FormatUint(uint64(id), 10)
	default:			panic(SAVEPOINT)
	}
	return
}

func (db *Database) Mark(id interface{}) (e error) {
	if st, err := db.Prepare("SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

func (db *Database) MergeSteps(id interface{}) (e error) {
	if st, err := db.Prepare("RELEASE SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

func (db *Database) Release(id interface{}) (e error) {
	if st, err := db.Prepare("ROLLBACK TRANSACTION TO SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

func (db *Database) SavePoints() (s []interface{}) {
	s = make([]interface{}, len(db.Savepoints))
	copy(s, db.Savepoints)
	return
}

func (db *Database) Load(source *Database, dbname string) (e error) {
	if dbname == "" {
		dbname = "main"
	}
	if backup, rv := NewBackup(db, dbname, source, dbname); rv == nil {
		e = backup.Full()
   	} else {
		e = rv
	}
	return
}

func (db *Database) Save(target *Database, dbname string) (e error) {
	return target.Load(db, dbname)
}

type Reporter chan *ProgressReport

type BackupParameters struct {
	Target			string
	PagesPerStep	int
	QueueLength		int
	Interval		time.Duration
}

func (db *Database) Backup(p BackupParameters) (r Reporter, e error) {
	if target, e := Open(p.Target); e == nil {
		if backup, e := NewBackup(target, "main", db, "main"); e == nil && p.PagesPerStep > 0 {
			r = make(Reporter, p.QueueLength)
			go func() {
				defer target.Close()
				defer backup.Finish()
				defer close(r)
				for {
					report := &ProgressReport{
								Source: db.Filename,
								Target: p.Target,
								Error: backup.Step(p.PagesPerStep),
								Total: backup.PageCount(),
								Remaining: backup.Remaining(),
								}
					r <- report
					if e, ok := report.Error.(Errno); ok && !(e == OK || e == BUSY || e == LOCKED) {
						break
					}
					if p.Interval > 0 {
						time.Sleep(p.Interval)
					}
				}
			}()
		} else {
			target.Close()
			e = target.Error()
		}
	}
	return
}<commit_msg>Documetation for database.go added.<commit_after>package sqlite3

// #include <sqlite3.h>
import "C"
import (
	"fmt"
	"strconv"
	"time"
)

type Errno int

func (e Errno) Error() (err string) {
	if err = errText[e]; err == "" {
		err = fmt.Sprintf("errno %v", int(e))
	}
	return
}

const (
	OK = Errno(iota)
	ERROR
	INTERNAL
	PERM
	ABORT
	BUSY
	LOCKED
	NOMEM
	READONLY
	INTERRUPT
	IOERR
	CORRUPT
	NOTFOUND
	FULL
	CANTOPEN
	PROTOCOL
	EMPTY
	SCHEMA
	TOOBIG
	CONSTRAINT
	MISMATCH
	MISUSE
	NOLFS
	AUTH
	FORMAT
	RANGE
	NOTDB
	ROW       = Errno(100)
	DONE      = Errno(101)
	ENCODER   = Errno(1000)
	SAVEPOINT = Errno(1001)
)

var errText = map[Errno]string{
	ERROR:      "SQL error or missing database",
	INTERNAL:   "Internal logic error in SQLite",
	PERM:       "Access permission denied",
	ABORT:      "Callback routine requested an abort",
	BUSY:       "The database file is locked",
	LOCKED:     "A table in the database is locked",
	NOMEM:      "A malloc() failed",
	READONLY:   "Attempt to write a readonly database",
	INTERRUPT:  "Operation terminated by sqlite3_interrupt()",
	IOERR:      "Some kind of disk I/O error occurred",
	CORRUPT:    "The database disk image is malformed",
	NOTFOUND:   "NOT USED. Table or record not found",
	FULL:       "Insertion failed because database is full",
	CANTOPEN:   "Unable to open the database file",
	PROTOCOL:   "NOT USED. Database lock protocol error",
	EMPTY:      "Database is empty",
	SCHEMA:     "The database schema changed",
	TOOBIG:     "String or BLOB exceeds size limit",
	CONSTRAINT: "Abort due to constraint violation",
	MISMATCH:   "Data type mismatch",
	MISUSE:     "Library used incorrectly",
	NOLFS:      "Uses OS features not supported on host",
	AUTH:       "Authorization denied",
	FORMAT:     "Auxiliary database format error",
	RANGE:      "2nd parameter to sqlite3_bind out of range",
	NOTDB:      "File opened that is not a database file",
	ROW:        "sqlite3_step() has another row ready",
	DONE:       "sqlite3_step() has finished executing",
	ENCODER:    "blob encoding failed",
	SAVEPOINT:  "invalid or unknown savepoint identifier",
}

type Database struct {
	handle     *C.sqlite3
	Filename   string
	Flags      C.int
	Savepoints []interface{}
}

// TransientDatabase returns a handle to an in-memory database.
func TransientDatabase() (db *Database) {
	return &Database{Filename: ":memory:"}
}

// Open returns a handle to the sqlite3 database specified by filename.
func Open(filename string, flags ...int) (db *Database, e error) {
	defer func() {
		if x := recover(); x != nil {
			db.Close()
			db = nil
			e = MISUSE
		}
	}()
	db = &Database{Filename: filename}
	if len(flags) == 0 {
		e = db.Open(C.SQLITE_OPEN_FULLMUTEX, C.SQLITE_OPEN_READWRITE, C.SQLITE_OPEN_CREATE)
	} else {
		e = db.Open(flags...)
	}
	return
}

// Open initializes and opens the database.
func (db *Database) Open(flags ...int) (e error) {
	if C.sqlite3_threadsafe() == 0 {
		panic("sqlite library is not thread-safe")
	}
	if db.handle != nil {
		e = CANTOPEN
	} else {
		db.Flags = 0
		for _, v := range flags {
			db.Flags = db.Flags | C.int(v)
		}
		if err := Errno(C.sqlite3_open_v2(C.CString(db.Filename), &db.handle, db.Flags, nil)); err != OK {
			e = err
		} else if db.handle == nil {
			e = CANTOPEN
		}
	}
	return
}

// Close shuts down the database engine for this database.
func (db *Database) Close() {
	C.sqlite3_close(db.handle)
	db.handle = nil
}

// LastInsertRowID returns the id of the most recent successful INSERT.
//
// Each entry in an SQLite table has a unique 64-bit signed integer key
// called the "rowid". The rowid is always available as an undeclared column
// named ROWID, OID, or _ROWID_ as long as those names are not also used by
// explicitly declared columns. If the table has a column of type
// INTEGER PRIMARY KEY then that column is another alias for the rowid.
//
// This routine returns the rowid of the most recent successful INSERT into
// the database from the database connection in the first argument. As of
// SQLite version 3.7.7, this routines records the last insert rowid of both
// ordinary tables and virtual tables. If no successful INSERTs have ever
// occurred on that database connection, zero is returned.
func (db *Database) LastInsertRowID() int64 {
	return int64(C.sqlite3_last_insert_rowid(db.handle))
}

// Changes returns the number of database rows that were changed or inserted
// or deleted by the most recently completed SQL statement.
func (db *Database) Changes() int {
	return int(C.sqlite3_changes(db.handle))
}

// TotalChanges retruns the number of row changes.
//
// This function returns the number of row changes caused by INSERT, UPDATE
// or DELETE statements since the database connection was opened. The count
// returned by TotalChanges includes all changes from all trigger contexts
// and changes made by foreign key actions. However, the count does not
// include changes used to implement REPLACE constraints, do rollbacks or
// ABORT processing, or DROP TABLE processing. The count does not include
// rows of views that fire an INSTEAD OF trigger, though if the INSTEAD OF
// trigger makes changes of its own, those changes are counted. The
// TotalChanges function counts the changes as soon as the statement that
// makes them is completed.
func (db *Database) TotalChanges() int {
	return int(C.sqlite3_total_changes(db.handle))
}

// Error returns the numeric result code for the most recent failed database
// call.
func (db *Database) Error() error {
	return Errno(C.sqlite3_errcode(db.handle))
}

// Prepare compiles the SQL query into a byte-code program and binds the
// supplied values.
func (db *Database) Prepare(sql string, values ...interface{}) (s *Statement, e error) {
	s = &Statement{db: db, timestamp: time.Now().UnixNano()}
	if rv := Errno(C.sqlite3_prepare_v2(db.handle, C.CString(sql), -1, &s.cptr, nil)); rv != OK {
		s, e = nil, rv
	} else {
		if len(values) > 0 {
			e, _ = s.BindAll(values...)
		}
	}
	return
}

// Execute runs the SQL statement.
func (db *Database) Execute(sql string, f ...func(*Statement, ...interface{})) (c int, e error) {
	var st *Statement
	st, e = db.Prepare(sql)
	if e == nil {
		c, e = st.All(f...)
	}
	if e == OK {
		e = nil
	}
	return
}

// Begin initializes a SQL Transaction block.
func (db *Database) Begin() (e error) {
	_, e = db.Execute("BEGIN")
	return
}

// Rollback reverts the changes since the most recent Begin() call.
func (db *Database) Rollback() (e error) {
	_, e = db.Execute("ROLLBACK")
	return
}

// Commit ends the current transaction and makes all changes performed in the
// transaction permanent.
func (db *Database) Commit() (e error) {
	_, e = db.Execute("COMMIT")
	return
}

func savepointID(id interface{}) (s string) {
	switch id := id.(type) {
	case string:
		s = id
	case []byte:
		s = string(id)
	case fmt.Stringer:
		s = id.String()
	case int:
		s = strconv.Itoa(id)
	case uint:
		s = strconv.FormatUint(uint64(id), 10)
	default:
		panic(SAVEPOINT)
	}
	return
}

// Mark creates a SAVEPOINT.
//
// A SAVEPOINT is a method of creating transactions, similar to BEGIN and
// COMMIT, except that Mark and MergeSteps are named and may be nested.
func (db *Database) Mark(id interface{}) (e error) {
	if st, err := db.Prepare("SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

// MergeSteps can be seen as the equivalent of COMMIT for a Mark command.
//
//   More specificly ...
//   - Some people view RELEASE as the equivalent of COMMIT for a SAVEPOINT.
//     This is an acceptable point of view as long as one remembers that the
//     changes committed by an inner transaction might later be undone by a
//     rollback in an outer transaction.
//   - Another view of RELEASE is that it merges a named transaction into its
//     parent transaction, so that the named transaction and its parent
//     become the same transaction. After RELEASE, the named transaction and
//     its parent will commit or rollback together, whatever their fate may
//     be.
//   - One can also think of savepoints as "marks" in the transaction
//     timeline. In this view, the SAVEPOINT command creates a new mark, the
//     ROLLBACK TO command rewinds the timeline back to a point just after
//     the named mark, and the RELEASE command erases marks from the timeline
//     without actually making any changes to the database.
func (db *Database) MergeSteps(id interface{}) (e error) {
	if st, err := db.Prepare("RELEASE SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

// Release rolls back all transactions to the specified SAVEPOINT (Mark).
func (db *Database) Release(id interface{}) (e error) {
	if st, err := db.Prepare("ROLLBACK TRANSACTION TO SAVEPOINT ?", savepointID(id)); err == nil {
		_, e = st.All()
	} else {
		e = err
	}
	return
}

// SavePoints returns the currently active SAVEPOINTs created using Mark.
func (db *Database) SavePoints() (s []interface{}) {
	s = make([]interface{}, len(db.Savepoints))
	copy(s, db.Savepoints)
	return
}

// Load creates a backup of the source database and loads that.
func (db *Database) Load(source *Database, dbname string) (e error) {
	if dbname == "" {
		dbname = "main"
	}
	if backup, rv := NewBackup(db, dbname, source, dbname); rv == nil {
		e = backup.Full()
	} else {
		e = rv
	}
	return
}

// Save stores the content of the database in the target database.
func (db *Database) Save(target *Database, dbname string) (e error) {
	return target.Load(db, dbname)
}

type Reporter chan *ProgressReport

type BackupParameters struct {
	Target       string
	PagesPerStep int
	QueueLength  int
	Interval     time.Duration
}

// Backup creates a copy (backup) of the current database to the Target file
// specified in BackupParameters.
func (db *Database) Backup(p BackupParameters) (r Reporter, e error) {
	if target, e := Open(p.Target); e == nil {
		if backup, e := NewBackup(target, "main", db, "main"); e == nil && p.PagesPerStep > 0 {
			r = make(Reporter, p.QueueLength)
			go func() {
				defer target.Close()
				defer backup.Finish()
				defer close(r)
				for {
					report := &ProgressReport{
						Source:    db.Filename,
						Target:    p.Target,
						Error:     backup.Step(p.PagesPerStep),
						Total:     backup.PageCount(),
						Remaining: backup.Remaining(),
					}
					r <- report
					if e, ok := report.Error.(Errno); ok && !(e == OK || e == BUSY || e == LOCKED) {
						break
					}
					if p.Interval > 0 {
						time.Sleep(p.Interval)
					}
				}
			}()
		} else {
			target.Close()
			e = target.Error()
		}
	}
	return
}
<|endoftext|>


<commit_before>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014-2015  Alex Mayfield <alexmax2742@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package charon

import (
	"strings"
	"sync"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
)

type Database struct {
	db    *sqlx.DB
	mutex sync.Mutex
}

// Schema for sqlite3.
var schema = `
CREATE TABLE IF NOT EXISTS users(
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	username TEXT,
	email TEXT,
	verifier BLOB,
	salt BLOB,
	access TEXT,
	active INTEGER
);
CREATE TABLE IF NOT EXISTS profiles(
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	user_id INTEGER,
	clan TEXT,
	contactinfo TEXT,
	country TEXT,
	gravatar TEXT,
	location TEXT,
	message TEXT,
	username TEXT,
	visible INTEGER,
	visible_lastplayed INTEGER,
	FOREIGN KEY(user_id) REFERENCES users(id)
);`

func NewDatabase() (database *Database, err error) {
	// Create a database connection.
	db, err := sqlx.Connect("sqlite3", ":memory:")
	if err != nil {
		return
	}

	// Create the database schema.
	_ = db.MustExec("PRAGMA foreign_keys = ON;")
	_ = db.MustExec(schema)

	database = new(Database)
	database.db = db
	return
}

// A representation of a user.
type User struct {
	Id       uint
	Username string
	Email    string
	Verifier []byte
	Salt     []byte
	Access   string
	Active   bool
}

const (
	UserAccessUnverified string = "UNVERIFIED"
	UserAccessUser       string = "USER"
	UserAccessOp         string = "OP"
	UserAccessMaster     string = "MASTER"
	UserAccessOwner      string = "OWNER"
)

// Add a new user
func (self *Database) AddUser(username string, email string, password string) (err error) {
	user := new(User)
	user.Username = username
	user.Email = email
	user.Verifier = []byte{0x01, 0x23, 0x45, 0x67}
	user.Salt = []byte{0x01, 0x23, 0x45, 0x67}
	user.Access = UserAccessUnverified
	user.Active = false

	self.mutex.Lock()
	_, err = self.db.NamedExec("INSERT INTO users (Username, Email, Verifier, Salt, Access, Active) VALUES (:username, :email, :verifier, :salt, :access, :active)", user)
	self.mutex.Unlock()
	return
}

// Try to find a specific user by name or email address.
func (self *Database) FindUser(username string) (user *User, err error) {
	user = &User{}
	self.mutex.Lock()
	err = self.db.Get(user, "SELECT * FROM users WHERE username LIKE $1 OR email LIKE $1", strings.ToLower(username))
	self.mutex.Unlock()
	return
}

// A representation of a user's profile.
type Profile struct {
	Id                 uint
	User_id            uint
	Clan               string
	Contactinfo        string
	Country            string
	Gravatar           string
	Location           string
	Message            string
	Username           string
	Visible            bool
	Visible_lastplayed bool
}
<commit_msg>Add SRP verifier and salt generation to user.<commit_after>/*
 *  Charon: A game authentication server
 *  Copyright (C) 2014-2015  Alex Mayfield <alexmax2742@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package charon

import (
	"crypto/sha256"
	"strings"
	"sync"

	"github.com/jmoiron/sqlx"
	_ "github.com/mattn/go-sqlite3"
	"github.com/tadglines/go-pkgs/crypto/srp"
)

type Database struct {
	db    *sqlx.DB
	mutex sync.Mutex
}

// Schema for sqlite3.
var schema = `
CREATE TABLE IF NOT EXISTS users(
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	username TEXT UNIQUE NOT NULL,
	email TEXT NOT NULL,
	verifier BLOB NOT NULL,
	salt BLOB NOT NULL,
	access TEXT NOT NULL,
	active INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS profiles(
	id INTEGER PRIMARY KEY AUTOINCREMENT,
	user_id INTEGER,
	clan TEXT,
	contactinfo TEXT,
	country TEXT,
	gravatar TEXT,
	location TEXT,
	message TEXT,
	username TEXT,
	visible INTEGER,
	visible_lastplayed INTEGER,
	FOREIGN KEY(user_id) REFERENCES users(id)
);`

func NewDatabase() (database *Database, err error) {
	// Create a database connection.
	db, err := sqlx.Connect("sqlite3", ":memory:")
	if err != nil {
		return
	}

	// Create the database schema.
	_ = db.MustExec("PRAGMA foreign_keys = ON;")
	_ = db.MustExec(schema)

	database = new(Database)
	database.db = db
	return
}

// A representation of a user.
type User struct {
	Id       uint
	Username string
	Email    string
	Verifier []byte
	Salt     []byte
	Access   string
	Active   bool
}

const (
	UserAccessUnverified string = "UNVERIFIED"
	UserAccessUser       string = "USER"
	UserAccessOp         string = "OP"
	UserAccessMaster     string = "MASTER"
	UserAccessOwner      string = "OWNER"
)

// Add a new user
func (self *Database) AddUser(username string, email string, password string) (err error) {
	srp, err := srp.NewSRP("rfc5054.2048", sha256.New, nil)
	if err != nil {
		return err
	}

	user := new(User)
	user.Username = username
	user.Email = email
	user.Access = UserAccessUnverified
	user.Active = false

	user.Salt, user.Verifier, err = srp.ComputeVerifier([]byte(password))
	if err != nil {
		return err
	}

	self.mutex.Lock()
	_, err = self.db.NamedExec("INSERT INTO users (Username, Email, Verifier, Salt, Access, Active) VALUES (:username, :email, :verifier, :salt, :access, :active)", user)
	self.mutex.Unlock()
	return
}

// Try to find a specific user by name or email address.
func (self *Database) FindUser(username string) (user *User, err error) {
	user = &User{}
	self.mutex.Lock()
	err = self.db.Get(user, "SELECT * FROM users WHERE username LIKE $1 OR email LIKE $1", strings.ToLower(username))
	self.mutex.Unlock()
	return
}

// A representation of a user's profile.
type Profile struct {
	Id                 uint
	User_id            uint
	Clan               string
	Contactinfo        string
	Country            string
	Gravatar           string
	Location           string
	Message            string
	Username           string
	Visible            bool
	Visible_lastplayed bool
}
<|endoftext|>


<commit_before>package photoshare

import (
	"database/sql"
	"fmt"
	"github.com/coopernurse/gorp"
	"github.com/juju/errgo"
	_ "github.com/lib/pq" // PostgreSQL library
	"log"
	"os"
	"strings"
)

func initDB(db *sql.DB, logSql bool) (*gorp.DbMap, error) {
	dbMap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}

	if logSql {
		dbMap.TraceOn("[sql]", log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds))
	}

	dbMap.AddTableWithName(user{}, "users").SetKeys(true, "ID")
	dbMap.AddTableWithName(photo{}, "photos").SetKeys(true, "ID")
	dbMap.AddTableWithName(tag{}, "tags").SetKeys(true, "ID")

	return dbMap, nil
}

type dataStore struct {
	*gorp.DbMap
	photos photoDataManager
	users  userDataManager
}

func newDataStore(dbMap *gorp.DbMap) *dataStore {
	ds := &dataStore{DbMap: dbMap}
	ds.photos = &defaultPhotoDataManager{dbMap}
	ds.users = &defaultUserDataManager{dbMap}
	return ds
}

type photoDataManager interface {
	create(*photo) error
	update(*photo) error
	remove(*photo) error
	get(int64) (*photo, error)
	getDetail(int64, *user) (*photoDetail, error)
	getTagCounts() ([]tagCount, error)
	all(*page, string) (*photoList, error)
	byOwnerID(*page, int64) (*photoList, error)
	search(*page, string) (*photoList, error)
	updateVotes(*photo, *user) error
	updateTags(*photo) error
}

type defaultPhotoDataManager struct {
	dbMap *gorp.DbMap
}

func (m *defaultPhotoDataManager) remove(photo *photo) error {
	_, err := m.dbMap.Delete(photo)
	return errgo.Mask(err)
}

func (m *defaultPhotoDataManager) update(photo *photo) error {
	_, err := m.dbMap.Update(photo)
	return errgo.Mask(err)
}

func (m *defaultPhotoDataManager) create(photo *photo) error {
	t, err := m.dbMap.Begin()
	if err != nil {
		return errgo.Mask(err)
	}
	if err := m.dbMap.Insert(photo); err != nil {
		return errgo.Mask(err)
	}
	if err := m.updateTags(photo); err != nil {
		return errgo.Mask(err)
	}
	return t.Commit()
}

func (m *defaultPhotoDataManager) updateVotes(photo *photo, user *user) error {

	t, err := m.dbMap.Begin()
	if err != nil {
		return err
	}

	if _, err = t.Update(photo); err != nil {
		return errgo.Mask(err)
	}

	user.registerVote(photo.ID)

	if _, err = t.Update(user); err != nil {
		return errgo.Mask(err)
	}

	return t.Commit()

}

func (m *defaultPhotoDataManager) updateTags(photo *photo) error {

	var (
		args    = []string{"$1"}
		params  = []interface{}{interface{}(photo.ID)}
		isEmpty = true
	)
	for i, name := range photo.Tags {
		name = strings.TrimSpace(name)
		if name != "" {
			args = append(args, fmt.Sprintf("$%d", i+2))
			params = append(params, interface{}(strings.ToLower(name)))
			isEmpty = false
		}
	}
	if isEmpty && photo.ID != 0 {
		_, err := m.dbMap.Exec("delete FROM photo_tags WHERE photo_id=$1", photo.ID)
		return errgo.Mask(err)
	}
	_, err := m.dbMap.Exec(fmt.Sprintf("SELECT add_tags(%s)", strings.Join(args, ",")), params...)
	return errgo.Mask(err)

}

func (m *defaultPhotoDataManager) get(photoID int64) (*photo, error) {

	p := &photo{}

	if photoID == 0 {
		return p, sql.ErrNoRows
	}

	obj, err := m.dbMap.Get(p, photoID)
	if err != nil {
		return p, errgo.Mask(err)
	}
	if obj == nil {
		return p, sql.ErrNoRows
	}
	return obj.(*photo), nil
}

func (m *defaultPhotoDataManager) getDetail(photoID int64, user *user) (*photoDetail, error) {

	photo := &photoDetail{}

	if photoID == 0 {
		return photo, sql.ErrNoRows
	}

	q := "SELECT p.*, u.name AS owner_name " +
		"FROM photos p JOIN users u ON u.id = p.owner_id " +
		"WHERE p.id=$1"

	if err := m.dbMap.SelectOne(photo, q, photoID); err != nil {
		return photo, errgo.Mask(err)
	}

	var tags []tag

	if _, err := m.dbMap.Select(&tags,
		"SELECT t.* FROM tags t JOIN photo_tags pt ON pt.tag_id=t.id "+
			"WHERE pt.photo_id=$1", photo.ID); err != nil {
		return photo, errgo.Mask(err)
	}
	for _, tag := range tags {
		photo.Tags = append(photo.Tags, tag.Name)
	}

	photo.Permissions = &permissions{
		photo.canEdit(user),
		photo.canDelete(user),
		photo.canVote(user),
	}
	return photo, nil

}

func (m *defaultPhotoDataManager) byOwnerID(page *page, ownerID int64) (*photoList, error) {
	var (
		photos []photo
		err    error
		total  int64
	)

	if ownerID == 0 {
		return nil, sql.ErrNoRows
	}
	if total, err = m.dbMap.SelectInt("SELECT COUNT(id) FROM photos WHERE owner_id=$1", ownerID); err != nil {
		return nil, errgo.Mask(err)
	}

	if _, err = m.dbMap.Select(&photos,
		"SELECT * FROM photos WHERE owner_id = $1"+
			"ORDER BY (up_votes - down_votes) DESC, created_at DESC LIMIT $2 OFFSET $3",
		ownerID, page.size, page.offset); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil

}

func (m *defaultPhotoDataManager) search(page *page, q string) (*photoList, error) {

	var (
		clauses []string
		params  []interface{}
		err     error
		photos  []photo
		total   int64
	)

	if q == "" {
		return nil, nil
	}

	for num, word := range strings.Split(q, " ") {
		word = strings.TrimSpace(word)
		if word == "" || num > 6 {
			break
		}

		num++

		if strings.HasPrefix(word, "@") {
			word = word[1:]
			clauses = append(clauses, fmt.Sprintf(
				"SELECT p.* FROM photos p "+
					"INNER JOIN users u ON u.id = p.owner_id  "+
					"WHERE UPPER(u.name::text) = UPPER($%d)", num))
		} else if strings.HasPrefix(word, "#") {
			word = word[1:]
			clauses = append(clauses, fmt.Sprintf(
				"SELECT p.* FROM photos p "+
					"INNER JOIN photo_tags pt ON pt.photo_id = p.id "+
					"INNER JOIN tags t ON pt.tag_id=t.id "+
					"WHERE UPPER(t.name::text) = UPPER($%d)", num))
		} else {
			word = "%" + word + "%"
			clauses = append(clauses, fmt.Sprintf(
				"SELECT DISTINCT p.* FROM photos p "+
					"INNER JOIN users u ON u.id = p.owner_id  "+
					"LEFT JOIN photo_tags pt ON pt.photo_id = p.id "+
					"LEFT JOIN tags t ON pt.tag_id=t.id "+
					"WHERE UPPER(p.title::text) LIKE UPPER($%d) OR "+
					"UPPER(u.name::text) LIKE UPPER($%d) OR t.name LIKE $%d",
				num, num, num))
		}

		params = append(params, interface{}(word))
	}

	clausesSql := strings.Join(clauses, " INTERSECT ")

	countSql := fmt.Sprintf("SELECT COUNT(id) FROM (%s) q", clausesSql)

	if total, err = m.dbMap.SelectInt(countSql, params...); err != nil {
		return nil, errgo.Mask(err)
	}

	numParams := len(params)

	sql := fmt.Sprintf("SELECT * FROM (%s) q ORDER BY (up_votes - down_votes) DESC, created_at DESC LIMIT $%d OFFSET $%d",
		clausesSql, numParams+1, numParams+2)

	params = append(params, interface{}(page.size))
	params = append(params, interface{}(page.offset))

	if _, err = m.dbMap.Select(&photos, sql, params...); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil
}

func (m *defaultPhotoDataManager) all(page *page, orderBy string) (*photoList, error) {

	var (
		total  int64
		photos []photo
		err    error
	)
	if orderBy == "votes" {
		orderBy = "(up_votes - down_votes)"
	} else {
		orderBy = "created_at"
	}

	if total, err = m.dbMap.SelectInt("SELECT COUNT(id) FROM photos"); err != nil {
		return nil, errgo.Mask(err)
	}

	if _, err = m.dbMap.Select(&photos,
		"SELECT * FROM photos "+
			"ORDER BY "+orderBy+" DESC LIMIT $1 OFFSET $2", page.size, page.offset); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil
}

func (m *defaultPhotoDataManager) getTagCounts() ([]tagCount, error) {
	var tags []tagCount
	if _, err := m.dbMap.Select(&tags, "SELECT name, photo, num_photos FROM tag_counts"); err != nil {
		return tags, errgo.Mask(err)
	}
	return tags, nil
}

type userDataManager interface {
	create(user *user) error
	update(user *user) error
	isNameAvailable(user *user) (bool, error)
	isEmailAvailable(user *user) (bool, error)
	getActive(userID int64) (*user, error)
	getByRecoveryCode(string) (*user, error)
	getByEmail(string) (*user, error)
	getByNameOrEmail(identifier string) (*user, error)
}

type defaultUserDataManager struct {
	dbMap *gorp.DbMap
}

func (m *defaultUserDataManager) create(user *user) error {
	return errgo.Mask(m.dbMap.Insert(user))
}

func (m *defaultUserDataManager) update(user *user) error {
	_, err := m.dbMap.Update(user)
	return errgo.Mask(err)
}

func (m *defaultUserDataManager) isNameAvailable(user *user) (bool, error) {
	var (
		num int64
		err error
	)
	q := "SELECT COUNT(id) FROM users WHERE name=$1"
	if user.ID == 0 {
		num, err = m.dbMap.SelectInt(q, user.Name)
	} else {
		q += " AND id != $2"
		num, err = m.dbMap.SelectInt(q, user.Name, user.ID)
	}
	if err != nil {
		return false, errgo.Mask(err)
	}
	return num == 0, nil
}

func (m *defaultUserDataManager) isEmailAvailable(user *user) (bool, error) {
	var (
		num int64
		err error
	)
	q := "SELECT COUNT(id) FROM users WHERE email=$1"
	if user.ID == 0 {
		num, err = m.dbMap.SelectInt(q, user.Email)
	} else {
		q += " AND id != $2"
		num, err = m.dbMap.SelectInt(q, user.Email, user.ID)
	}
	if err != nil {
		return false, errgo.Mask(err)
	}
	return num == 0, nil
}

func (m *defaultUserDataManager) getActive(userID int64) (*user, error) {

	user := &user{}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND id=$2", true, userID); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil

}

func (m *defaultUserDataManager) getByRecoveryCode(code string) (*user, error) {

	user := &user{}
	if code == "" {
		return user, sql.ErrNoRows
	}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND recovery_code=$2", true, code); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil

}
func (m *defaultUserDataManager) getByEmail(email string) (*user, error) {
	user := &user{}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND email=$2", true, email); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil
}

func (m *defaultUserDataManager) getByNameOrEmail(identifier string) (*user, error) {
	user := &user{}

	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND (email=$2 OR name=$2)", true, identifier); err != nil {
		return user, errgo.Mask(err)
	}

	return user, nil
}
<commit_msg>Sane transaction handling<commit_after>package photoshare

import (
	"database/sql"
	"fmt"
	"github.com/coopernurse/gorp"
	"github.com/juju/errgo"
	_ "github.com/lib/pq" // PostgreSQL library
	"log"
	"os"
	"strings"
)

func initDB(db *sql.DB, logSql bool) (*gorp.DbMap, error) {
	dbMap := &gorp.DbMap{Db: db, Dialect: gorp.PostgresDialect{}}

	if logSql {
		dbMap.TraceOn("[sql]", log.New(os.Stdout, "", log.Ldate|log.Lmicroseconds))
	}

	dbMap.AddTableWithName(user{}, "users").SetKeys(true, "ID")
	dbMap.AddTableWithName(photo{}, "photos").SetKeys(true, "ID")
	dbMap.AddTableWithName(tag{}, "tags").SetKeys(true, "ID")

	return dbMap, nil
}

type dataStore struct {
	*gorp.DbMap
	photos photoDataManager
	users  userDataManager
}

func newDataStore(dbMap *gorp.DbMap) *dataStore {
	ds := &dataStore{DbMap: dbMap}
	ds.photos = &defaultPhotoDataManager{dbMap}
	ds.users = &defaultUserDataManager{dbMap}
	return ds
}

type photoDataManager interface {
	create(*photo) error
	update(*photo) error
	remove(*photo) error
	get(int64) (*photo, error)
	getDetail(int64, *user) (*photoDetail, error)
	getTagCounts() ([]tagCount, error)
	all(*page, string) (*photoList, error)
	byOwnerID(*page, int64) (*photoList, error)
	search(*page, string) (*photoList, error)
	updateVotes(*photo, *user) error
	updateTags(*photo) error
}

type defaultPhotoDataManager struct {
	dbMap *gorp.DbMap
}

func (m *defaultPhotoDataManager) remove(photo *photo) error {
	_, err := m.dbMap.Delete(photo)
	return errgo.Mask(err)
}

func (m *defaultPhotoDataManager) update(photo *photo) error {
	_, err := m.dbMap.Update(photo)
	return errgo.Mask(err)
}

func (m *defaultPhotoDataManager) create(photo *photo) error {
	t, err := m.dbMap.Begin()
	if err != nil {
		return errgo.Mask(err)
	}
	if err := t.Insert(photo); err != nil {
		t.Rollback()
		return errgo.Mask(err)
	}
	if err := m.txUpdateTags(t, photo); err != nil {
		return err
	}
	return errgo.Mask(t.Commit())
}

func (m *defaultPhotoDataManager) updateVotes(photo *photo, user *user) error {

	t, err := m.dbMap.Begin()
	if err != nil {
		return err
	}

	if _, err = t.Update(photo); err != nil {
		t.Rollback()
		return errgo.Mask(err)
	}

	user.registerVote(photo.ID)

	if _, err = t.Update(user); err != nil {
		t.Rollback()
		return errgo.Mask(err)
	}

	return errgo.Mask(t.Commit())

}

func (m *defaultPhotoDataManager) updateTags(photo *photo) error {
	t, err := m.dbMap.Begin()
	if err != nil {
		return err
	}

	if err := m.txUpdateTags(t, photo); err != nil {
		return err
	}
	return errgo.Mask(t.Commit())
}

// handles delete/update of tags within a transaction
func (m *defaultPhotoDataManager) txUpdateTags(t *gorp.Transaction, photo *photo) error {

	var (
		args    = []string{"$1"}
		params  = []interface{}{interface{}(photo.ID)}
		isEmpty = true
	)
	for i, name := range photo.Tags {
		name = strings.TrimSpace(name)
		if name != "" {
			args = append(args, fmt.Sprintf("$%d", i+2))
			params = append(params, interface{}(strings.ToLower(name)))
			isEmpty = false
		}
	}

	if isEmpty && photo.ID != 0 {
		_, err := t.Exec("delete FROM photo_tags WHERE photo_id=$1", photo.ID)
		t.Rollback()
		return errgo.Mask(err)
	}
	if _, err := t.Exec(fmt.Sprintf("SELECT add_tags(%s)", strings.Join(args, ",")), params...); err != nil {
		t.Rollback()
		return errgo.Mask(err)
	}
	return nil

}

func (m *defaultPhotoDataManager) get(photoID int64) (*photo, error) {

	p := &photo{}

	if photoID == 0 {
		return p, sql.ErrNoRows
	}

	obj, err := m.dbMap.Get(p, photoID)
	if err != nil {
		return p, errgo.Mask(err)
	}
	if obj == nil {
		return p, sql.ErrNoRows
	}
	return obj.(*photo), nil
}

func (m *defaultPhotoDataManager) getDetail(photoID int64, user *user) (*photoDetail, error) {

	photo := &photoDetail{}

	if photoID == 0 {
		return photo, sql.ErrNoRows
	}

	q := "SELECT p.*, u.name AS owner_name " +
		"FROM photos p JOIN users u ON u.id = p.owner_id " +
		"WHERE p.id=$1"

	if err := m.dbMap.SelectOne(photo, q, photoID); err != nil {
		return photo, errgo.Mask(err)
	}

	var tags []tag

	if _, err := m.dbMap.Select(&tags,
		"SELECT t.* FROM tags t JOIN photo_tags pt ON pt.tag_id=t.id "+
			"WHERE pt.photo_id=$1", photo.ID); err != nil {
		return photo, errgo.Mask(err)
	}
	for _, tag := range tags {
		photo.Tags = append(photo.Tags, tag.Name)
	}

	photo.Permissions = &permissions{
		photo.canEdit(user),
		photo.canDelete(user),
		photo.canVote(user),
	}
	return photo, nil

}

func (m *defaultPhotoDataManager) byOwnerID(page *page, ownerID int64) (*photoList, error) {
	var (
		photos []photo
		err    error
		total  int64
	)

	if ownerID == 0 {
		return nil, sql.ErrNoRows
	}
	if total, err = m.dbMap.SelectInt("SELECT COUNT(id) FROM photos WHERE owner_id=$1", ownerID); err != nil {
		return nil, errgo.Mask(err)
	}

	if _, err = m.dbMap.Select(&photos,
		"SELECT * FROM photos WHERE owner_id = $1"+
			"ORDER BY (up_votes - down_votes) DESC, created_at DESC LIMIT $2 OFFSET $3",
		ownerID, page.size, page.offset); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil

}

func (m *defaultPhotoDataManager) search(page *page, q string) (*photoList, error) {

	var (
		clauses []string
		params  []interface{}
		err     error
		photos  []photo
		total   int64
	)

	if q == "" {
		return nil, nil
	}

	for num, word := range strings.Split(q, " ") {
		word = strings.TrimSpace(word)
		if word == "" || num > 6 {
			break
		}

		num++

		if strings.HasPrefix(word, "@") {
			word = word[1:]
			clauses = append(clauses, fmt.Sprintf(
				"SELECT p.* FROM photos p "+
					"INNER JOIN users u ON u.id = p.owner_id  "+
					"WHERE UPPER(u.name::text) = UPPER($%d)", num))
		} else if strings.HasPrefix(word, "#") {
			word = word[1:]
			clauses = append(clauses, fmt.Sprintf(
				"SELECT p.* FROM photos p "+
					"INNER JOIN photo_tags pt ON pt.photo_id = p.id "+
					"INNER JOIN tags t ON pt.tag_id=t.id "+
					"WHERE UPPER(t.name::text) = UPPER($%d)", num))
		} else {
			word = "%" + word + "%"
			clauses = append(clauses, fmt.Sprintf(
				"SELECT DISTINCT p.* FROM photos p "+
					"INNER JOIN users u ON u.id = p.owner_id  "+
					"LEFT JOIN photo_tags pt ON pt.photo_id = p.id "+
					"LEFT JOIN tags t ON pt.tag_id=t.id "+
					"WHERE UPPER(p.title::text) LIKE UPPER($%d) OR "+
					"UPPER(u.name::text) LIKE UPPER($%d) OR t.name LIKE $%d",
				num, num, num))
		}

		params = append(params, interface{}(word))
	}

	clausesSql := strings.Join(clauses, " INTERSECT ")

	countSql := fmt.Sprintf("SELECT COUNT(id) FROM (%s) q", clausesSql)

	if total, err = m.dbMap.SelectInt(countSql, params...); err != nil {
		return nil, errgo.Mask(err)
	}

	numParams := len(params)

	sql := fmt.Sprintf("SELECT * FROM (%s) q ORDER BY (up_votes - down_votes) DESC, created_at DESC LIMIT $%d OFFSET $%d",
		clausesSql, numParams+1, numParams+2)

	params = append(params, interface{}(page.size))
	params = append(params, interface{}(page.offset))

	if _, err = m.dbMap.Select(&photos, sql, params...); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil
}

func (m *defaultPhotoDataManager) all(page *page, orderBy string) (*photoList, error) {

	var (
		total  int64
		photos []photo
		err    error
	)
	if orderBy == "votes" {
		orderBy = "(up_votes - down_votes)"
	} else {
		orderBy = "created_at"
	}

	if total, err = m.dbMap.SelectInt("SELECT COUNT(id) FROM photos"); err != nil {
		return nil, errgo.Mask(err)
	}

	if _, err = m.dbMap.Select(&photos,
		"SELECT * FROM photos "+
			"ORDER BY "+orderBy+" DESC LIMIT $1 OFFSET $2", page.size, page.offset); err != nil {
		return nil, errgo.Mask(err)
	}
	return newPhotoList(photos, total, page.index), nil
}

func (m *defaultPhotoDataManager) getTagCounts() ([]tagCount, error) {
	var tags []tagCount
	if _, err := m.dbMap.Select(&tags, "SELECT name, photo, num_photos FROM tag_counts"); err != nil {
		return tags, errgo.Mask(err)
	}
	return tags, nil
}

type userDataManager interface {
	create(user *user) error
	update(user *user) error
	isNameAvailable(user *user) (bool, error)
	isEmailAvailable(user *user) (bool, error)
	getActive(userID int64) (*user, error)
	getByRecoveryCode(string) (*user, error)
	getByEmail(string) (*user, error)
	getByNameOrEmail(identifier string) (*user, error)
}

type defaultUserDataManager struct {
	dbMap *gorp.DbMap
}

func (m *defaultUserDataManager) create(user *user) error {
	return errgo.Mask(m.dbMap.Insert(user))
}

func (m *defaultUserDataManager) update(user *user) error {
	_, err := m.dbMap.Update(user)
	return errgo.Mask(err)
}

func (m *defaultUserDataManager) isNameAvailable(user *user) (bool, error) {
	var (
		num int64
		err error
	)
	q := "SELECT COUNT(id) FROM users WHERE name=$1"
	if user.ID == 0 {
		num, err = m.dbMap.SelectInt(q, user.Name)
	} else {
		q += " AND id != $2"
		num, err = m.dbMap.SelectInt(q, user.Name, user.ID)
	}
	if err != nil {
		return false, errgo.Mask(err)
	}
	return num == 0, nil
}

func (m *defaultUserDataManager) isEmailAvailable(user *user) (bool, error) {
	var (
		num int64
		err error
	)
	q := "SELECT COUNT(id) FROM users WHERE email=$1"
	if user.ID == 0 {
		num, err = m.dbMap.SelectInt(q, user.Email)
	} else {
		q += " AND id != $2"
		num, err = m.dbMap.SelectInt(q, user.Email, user.ID)
	}
	if err != nil {
		return false, errgo.Mask(err)
	}
	return num == 0, nil
}

func (m *defaultUserDataManager) getActive(userID int64) (*user, error) {

	user := &user{}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND id=$2", true, userID); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil

}

func (m *defaultUserDataManager) getByRecoveryCode(code string) (*user, error) {

	user := &user{}
	if code == "" {
		return user, sql.ErrNoRows
	}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND recovery_code=$2", true, code); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil

}
func (m *defaultUserDataManager) getByEmail(email string) (*user, error) {
	user := &user{}
	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND email=$2", true, email); err != nil {
		return user, errgo.Mask(err)
	}
	return user, nil
}

func (m *defaultUserDataManager) getByNameOrEmail(identifier string) (*user, error) {
	user := &user{}

	if err := m.dbMap.SelectOne(user, "SELECT * FROM users WHERE active=$1 AND (email=$2 OR name=$2)", true, identifier); err != nil {
		return user, errgo.Mask(err)
	}

	return user, nil
}
<|endoftext|>