<commit_before>package aws

import (
	mach "github.com/jeffjen/machine/lib/machine"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/urfave/cli"

	"fmt"
	"math/rand"
	"net"
	"os"
	"strings"
	"sync"
)

var (
	DEVICE_NAME = []string{"b", "c", "d", "e", "f", "g", "h", "i"}
)

type ec2state struct {
	*ec2.Instance
	name string
	err  error
}

func newEc2State(inst *ec2.Instance, err error) ec2state {
	return ec2state{inst, "", err}
}

func getEc2InstanceName(inst *ec2.Instance) (name string) {
	for _, t := range inst.Tags {
		if *t.Key == "Name" {
			return *t.Value
		}
	}
	return
}

func ec2Init() error {
	var resp = new(ec2.DescribeInstancesOutput)

	for more := true; more; {
		params := &ec2.DescribeInstancesInput{}
		if resp.NextToken != nil {
			params.NextToken = resp.NextToken
		}
		resp, err := svc.DescribeInstances(params)
		if err != nil {
			more = false
		} else if len(resp.Reservations) < 1 {
			more = false
		} else {
			for _, r := range resp.Reservations {
				for _, inst := range r.Instances {
					var instanceName = getEc2InstanceName(inst)
					info, ok := instList[instanceName]
					if !ok {
						info = &mach.Instance{Id: *inst.InstanceId}
					}
					if *inst.State.Name == "terminated" {
						if info.Id == *inst.InstanceId {
							delete(instList, instanceName)
						} else {
							continue // ignore
						}
					} else {
						info.Driver = "aws"
						info.State = *inst.State.Name
						info.Id = *inst.InstanceId
						func() {
							var addr *net.TCPAddr
							if inst.PublicIpAddress != nil {
								addr, _ = net.ResolveTCPAddr("tcp", *inst.PublicIpAddress+":2376")
							}
							info.DockerHost = addr
						}()
						instList[instanceName] = info
					}
				}
			}
			more = (resp.NextToken != nil)
		}
	}

	return nil
}

func ec2_getSubnet(profile *VPCProfile, public bool) (subnetId *string) {
	var collection []*string
	for _, subnet := range profile.Subnet {
		if public && *subnet.Public {
			collection = append(collection, subnet.Id)
		} else if !public && !*subnet.Public {
			collection = append(collection, subnet.Id)
		}
	}
	idx := rand.Intn(len(collection))
	return collection[idx]
}

func ec2_findSecurityGroup(profile *VPCProfile, name ...string) (sgId []*string) {
	sgId = make([]*string, 0)
	for _, grp := range name {
		for _, sgrp := range profile.SecurityGroup {
			if *sgrp.Name == grp {
				sgId = append(sgId, sgrp.Id)
			}
		}
	}
	return
}

func ec2_tagInstanceParam(tags []string) (*ec2.CreateTagsInput, error) {
	tagparam := &ec2.CreateTagsInput{
		Tags:      make([]*ec2.Tag, 0),
		Resources: make([]*string, 0),
	}
	for _, tag := range tags {
		var parts = strings.SplitN(tag, "=", 2)
		if len(parts) == 2 {
			tagparam.Tags = append(tagparam.Tags, &ec2.Tag{
				Key:   aws.String(parts[0]),
				Value: aws.String(parts[1]),
			})
		} else {
			return nil, fmt.Errorf("Skipping bad tag spec - %s\n", tag)
		}
	}
	return tagparam, nil
}

func ec2_EbsRoot(size int) (mapping *ec2.BlockDeviceMapping) {
	return &ec2.BlockDeviceMapping{
		DeviceName: aws.String("/dev/sda1"),
		Ebs: &ec2.EbsBlockDevice{
			DeleteOnTermination: aws.Bool(true),
			VolumeSize:          aws.Int64(int64(size)),
			VolumeType:          aws.String(ec2.VolumeTypeGp2),
		},
	}
}

func ec2_EbsVols(size ...int) (mapping []*ec2.BlockDeviceMapping, err error) {
	mapping = make([]*ec2.BlockDeviceMapping, 0)
	for i, volSize := range size {
		if volSize <= 0 {
			fmt.Fprintln(os.Stderr, "Skipping bad volume size", volSize)
			continue
		}
		if i >= len(DEVICE_NAME) {
			err = fmt.Errorf("You had more volumes then AWS allowed")
			return
		}
		mapping = append(mapping, &ec2.BlockDeviceMapping{
			DeviceName: aws.String("xvd" + DEVICE_NAME[i]),
			Ebs: &ec2.EbsBlockDevice{
				DeleteOnTermination: aws.Bool(true),
				VolumeSize:          aws.Int64(int64(volSize)),
				VolumeType:          aws.String(ec2.VolumeTypeGp2),
			},
		})
	}
	return
}

func ec2_WaitForReady(instId *string) <-chan ec2state {
	out := make(chan ec2state)
	go func() {
		defer close(out)
		param := &ec2.DescribeInstancesInput{InstanceIds: []*string{instId}}
		if err := svc.WaitUntilInstanceRunning(param); err != nil {
			out <- newEc2State(nil, fmt.Errorf("%s - %s", *instId, err))
			return
		}
		resp, err := svc.DescribeInstances(param)
		if err != nil {
			out <- newEc2State(nil, fmt.Errorf("%s - %s", *instId, err))
		} else {
			out <- newEc2State(resp.Reservations[0].Instances[0], nil)
		}
		// NOTE: this should end here
	}()
	return out
}

func newEC2Inst(c *cli.Context, profile *Profile, num2Launch int) (instances []*ec2.Instance, err error) {
	var (
		amiId            = c.String("ami-id")
		networkACLGroups = c.StringSlice("group")
		iamProfile       = c.String("iam-role")
		instVolRoot      = c.Int("root-size")
		keyName          = c.String("ssh-key")
		isPrivate        = c.Bool("subnet-private")
		subnetId         = c.String("subnet-id")
		instTags         = c.StringSlice("tag")
		instType         = c.String("type")
		instVols         = c.IntSlice("volume-size")

		ec2param = &ec2.RunInstancesInput{
			InstanceType:     aws.String(instType),
			MaxCount:         aws.Int64(int64(num2Launch)),
			MinCount:         aws.Int64(1),
			SecurityGroupIds: ec2_findSecurityGroup(&profile.VPC, networkACLGroups...),
		}
	)

	// Step 1: determine the Amazone Machine Image ID
	if amiId != "" {
		ec2param.ImageId = aws.String(amiId)
	} else if len(profile.Ami) != 0 {
		ec2param.ImageId = profile.Ami[0].Id
	} else {
		return nil, fmt.Errorf("Cannot proceed without an AMI")
	}

	// Step 2: determine keypair to use for remote access
	if keyName != "" {
		ec2param.KeyName = aws.String(keyName)
	} else if len(profile.KeyPair) != 0 {
		ec2param.KeyName = profile.KeyPair[0].Name
	} else {
		return nil, fmt.Errorf("Cannot proceed without SSH keypair")
	}

	// Step 3: determine EBS Volume configuration
	ec2param.BlockDeviceMappings = make([]*ec2.BlockDeviceMapping, 0)
	if instVolRoot > 0 {
		ec2param.BlockDeviceMappings = append(ec2param.BlockDeviceMappings, ec2_EbsRoot(instVolRoot))
	}
	if mapping, brr := ec2_EbsVols(instVols...); brr != nil {
		return nil, brr
	} else {
		ec2param.BlockDeviceMappings = append(ec2param.BlockDeviceMappings, mapping...)
	}

	// Step 4: assign IAM role for the EC2 machine
	if strings.HasPrefix(iamProfile, "arn:aws:iam") {
		ec2param.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
			Arn: aws.String(iamProfile),
		}
	} else if iamProfile != "" {
		ec2param.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
			Name: aws.String(iamProfile),
		}
	}

	// Step 5: assign accessibility of EC2 instance by subnet
	if subnetId != "" {
		ec2param.SubnetId = aws.String(subnetId)
	} else {
		ec2param.SubnetId = ec2_getSubnet(&profile.VPC, !isPrivate)
	}

	// Step 6: create tags from spec
	tag, err := ec2_tagInstanceParam(instTags)
	if err != nil {
		return
	}

	// Last step: launch + tag instances
	resp, err := svc.RunInstances(ec2param)
	if err != nil {
		return
	}
	if len(tag.Tags) > 0 {
		for _, inst := range resp.Instances {
			tag.Resources = append(tag.Resources, inst.InstanceId)
		}
		_, err = svc.CreateTags(tag)
		if err != nil {
			return
		}
	}

	fmt.Println("Launched instances...")
	return resp.Instances, nil
}

func deployEC2Inst(user, cert, name, org, certpath string, num2Launch int, useDocker bool, instances []*ec2.Instance) <-chan ec2state {
	var wg sync.WaitGroup
	out := make(chan ec2state)
	go func() {
		defer close(out)
		wg.Add(len(instances))
		for _, inst := range instances {
			go func(ch <-chan ec2state) {
				var state = <-ch
				if state.Instance == nil {
					state.err = fmt.Errorf("Unexpected Instance launch failure")
				} else {
					state.name = fmt.Sprintf("%s-%s", name, *state.InstanceId)
					tagparam := &ec2.CreateTagsInput{
						Tags: []*ec2.Tag{
							{
								Key:   aws.String("Name"),
								Value: aws.String(state.name),
							},
						},
						Resources: []*string{
							state.InstanceId,
						},
					}
					_, state.err = svc.CreateTags(tagparam)
					if useDocker {
						host := mach.NewDockerHost(org, certpath, user, cert)
						if state.err == nil {
							state.err = host.InstallDockerEngine(*state.PublicIpAddress)
						}
						if state.err == nil {
							state.err = host.InstallDockerEngineCertificate(*state.PublicIpAddress, *state.PrivateIpAddress)
						}
					}
				}
				out <- state
				wg.Done()
			}(ec2_WaitForReady(inst.InstanceId))
		}
		wg.Wait()
	}()
	return out
}
<commit_msg>UPDATE: aws config sync use <name>-<id> for identification<commit_after>package aws

import (
	mach "github.com/jeffjen/machine/lib/machine"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/urfave/cli"

	"fmt"
	"math/rand"
	"net"
	"os"
	"strings"
	"sync"
)

var (
	DEVICE_NAME = []string{"b", "c", "d", "e", "f", "g", "h", "i"}
)

type ec2state struct {
	*ec2.Instance
	name string
	err  error
}

func newEc2State(inst *ec2.Instance, err error) ec2state {
	return ec2state{inst, "", err}
}

func getEc2InstanceName(inst *ec2.Instance) (name string) {
	for _, t := range inst.Tags {
		if *t.Key == "Name" {
			return *t.Value
		}
	}
	return
}

func ec2Init() error {
	var resp = new(ec2.DescribeInstancesOutput)

	for more := true; more; {
		params := &ec2.DescribeInstancesInput{}
		if resp.NextToken != nil {
			params.NextToken = resp.NextToken
		}
		resp, err := svc.DescribeInstances(params)
		if err != nil {
			more = false
		} else if len(resp.Reservations) < 1 {
			more = false
		} else {
			for _, r := range resp.Reservations {
				for _, inst := range r.Instances {
					var instanceName = fmt.Sprintf("%s-%s", getEc2InstanceName(inst), *inst.InstanceId)
					info, ok := instList[instanceName]
					if !ok {
						info = &mach.Instance{Id: *inst.InstanceId}
					}
					if *inst.State.Name == "terminated" {
						if info.Id == *inst.InstanceId {
							delete(instList, instanceName)
						} else {
							continue // ignore
						}
					} else {
						info.Driver = "aws"
						info.State = *inst.State.Name
						info.Id = *inst.InstanceId
						func() {
							var addr *net.TCPAddr
							if inst.PublicIpAddress != nil {
								addr, _ = net.ResolveTCPAddr("tcp", *inst.PublicIpAddress+":2376")
							}
							info.DockerHost = addr
						}()
						instList[instanceName] = info
					}
				}
			}
			more = (resp.NextToken != nil)
		}
	}

	return nil
}

func ec2_getSubnet(profile *VPCProfile, public bool) (subnetId *string) {
	var collection []*string
	for _, subnet := range profile.Subnet {
		if public && *subnet.Public {
			collection = append(collection, subnet.Id)
		} else if !public && !*subnet.Public {
			collection = append(collection, subnet.Id)
		}
	}
	idx := rand.Intn(len(collection))
	return collection[idx]
}

func ec2_findSecurityGroup(profile *VPCProfile, name ...string) (sgId []*string) {
	sgId = make([]*string, 0)
	for _, grp := range name {
		for _, sgrp := range profile.SecurityGroup {
			if *sgrp.Name == grp {
				sgId = append(sgId, sgrp.Id)
			}
		}
	}
	return
}

func ec2_tagInstanceParam(tags []string) (*ec2.CreateTagsInput, error) {
	tagparam := &ec2.CreateTagsInput{
		Tags:      make([]*ec2.Tag, 0),
		Resources: make([]*string, 0),
	}
	for _, tag := range tags {
		var parts = strings.SplitN(tag, "=", 2)
		if len(parts) == 2 {
			tagparam.Tags = append(tagparam.Tags, &ec2.Tag{
				Key:   aws.String(parts[0]),
				Value: aws.String(parts[1]),
			})
		} else {
			return nil, fmt.Errorf("Skipping bad tag spec - %s\n", tag)
		}
	}
	return tagparam, nil
}

func ec2_EbsRoot(size int) (mapping *ec2.BlockDeviceMapping) {
	return &ec2.BlockDeviceMapping{
		DeviceName: aws.String("/dev/sda1"),
		Ebs: &ec2.EbsBlockDevice{
			DeleteOnTermination: aws.Bool(true),
			VolumeSize:          aws.Int64(int64(size)),
			VolumeType:          aws.String(ec2.VolumeTypeGp2),
		},
	}
}

func ec2_EbsVols(size ...int) (mapping []*ec2.BlockDeviceMapping, err error) {
	mapping = make([]*ec2.BlockDeviceMapping, 0)
	for i, volSize := range size {
		if volSize <= 0 {
			fmt.Fprintln(os.Stderr, "Skipping bad volume size", volSize)
			continue
		}
		if i >= len(DEVICE_NAME) {
			err = fmt.Errorf("You had more volumes then AWS allowed")
			return
		}
		mapping = append(mapping, &ec2.BlockDeviceMapping{
			DeviceName: aws.String("xvd" + DEVICE_NAME[i]),
			Ebs: &ec2.EbsBlockDevice{
				DeleteOnTermination: aws.Bool(true),
				VolumeSize:          aws.Int64(int64(volSize)),
				VolumeType:          aws.String(ec2.VolumeTypeGp2),
			},
		})
	}
	return
}

func ec2_WaitForReady(instId *string) <-chan ec2state {
	out := make(chan ec2state)
	go func() {
		defer close(out)
		param := &ec2.DescribeInstancesInput{InstanceIds: []*string{instId}}
		if err := svc.WaitUntilInstanceRunning(param); err != nil {
			out <- newEc2State(nil, fmt.Errorf("%s - %s", *instId, err))
			return
		}
		resp, err := svc.DescribeInstances(param)
		if err != nil {
			out <- newEc2State(nil, fmt.Errorf("%s - %s", *instId, err))
		} else {
			out <- newEc2State(resp.Reservations[0].Instances[0], nil)
		}
		// NOTE: this should end here
	}()
	return out
}

func newEC2Inst(c *cli.Context, profile *Profile, num2Launch int) (instances []*ec2.Instance, err error) {
	var (
		amiId            = c.String("ami-id")
		networkACLGroups = c.StringSlice("group")
		iamProfile       = c.String("iam-role")
		instVolRoot      = c.Int("root-size")
		keyName          = c.String("ssh-key")
		isPrivate        = c.Bool("subnet-private")
		subnetId         = c.String("subnet-id")
		instTags         = c.StringSlice("tag")
		instType         = c.String("type")
		instVols         = c.IntSlice("volume-size")

		ec2param = &ec2.RunInstancesInput{
			InstanceType:     aws.String(instType),
			MaxCount:         aws.Int64(int64(num2Launch)),
			MinCount:         aws.Int64(1),
			SecurityGroupIds: ec2_findSecurityGroup(&profile.VPC, networkACLGroups...),
		}
	)

	// Step 1: determine the Amazone Machine Image ID
	if amiId != "" {
		ec2param.ImageId = aws.String(amiId)
	} else if len(profile.Ami) != 0 {
		ec2param.ImageId = profile.Ami[0].Id
	} else {
		return nil, fmt.Errorf("Cannot proceed without an AMI")
	}

	// Step 2: determine keypair to use for remote access
	if keyName != "" {
		ec2param.KeyName = aws.String(keyName)
	} else if len(profile.KeyPair) != 0 {
		ec2param.KeyName = profile.KeyPair[0].Name
	} else {
		return nil, fmt.Errorf("Cannot proceed without SSH keypair")
	}

	// Step 3: determine EBS Volume configuration
	ec2param.BlockDeviceMappings = make([]*ec2.BlockDeviceMapping, 0)
	if instVolRoot > 0 {
		ec2param.BlockDeviceMappings = append(ec2param.BlockDeviceMappings, ec2_EbsRoot(instVolRoot))
	}
	if mapping, brr := ec2_EbsVols(instVols...); brr != nil {
		return nil, brr
	} else {
		ec2param.BlockDeviceMappings = append(ec2param.BlockDeviceMappings, mapping...)
	}

	// Step 4: assign IAM role for the EC2 machine
	if strings.HasPrefix(iamProfile, "arn:aws:iam") {
		ec2param.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
			Arn: aws.String(iamProfile),
		}
	} else if iamProfile != "" {
		ec2param.IamInstanceProfile = &ec2.IamInstanceProfileSpecification{
			Name: aws.String(iamProfile),
		}
	}

	// Step 5: assign accessibility of EC2 instance by subnet
	if subnetId != "" {
		ec2param.SubnetId = aws.String(subnetId)
	} else {
		ec2param.SubnetId = ec2_getSubnet(&profile.VPC, !isPrivate)
	}

	// Step 6: create tags from spec
	tag, err := ec2_tagInstanceParam(instTags)
	if err != nil {
		return
	}

	// Last step: launch + tag instances
	resp, err := svc.RunInstances(ec2param)
	if err != nil {
		return
	}
	if len(tag.Tags) > 0 {
		for _, inst := range resp.Instances {
			tag.Resources = append(tag.Resources, inst.InstanceId)
		}
		_, err = svc.CreateTags(tag)
		if err != nil {
			return
		}
	}

	fmt.Println("Launched instances...")
	return resp.Instances, nil
}

func deployEC2Inst(user, cert, name, org, certpath string, num2Launch int, useDocker bool, instances []*ec2.Instance) <-chan ec2state {
	var wg sync.WaitGroup
	out := make(chan ec2state)
	go func() {
		defer close(out)
		wg.Add(len(instances))
		for _, inst := range instances {
			go func(ch <-chan ec2state) {
				var state = <-ch
				if state.Instance == nil {
					state.err = fmt.Errorf("Unexpected Instance launch failure")
				} else {
					state.name = fmt.Sprintf("%s-%s", name, *state.InstanceId)
					tagparam := &ec2.CreateTagsInput{
						Tags: []*ec2.Tag{
							{
								Key:   aws.String("Name"),
								Value: aws.String(state.name),
							},
						},
						Resources: []*string{
							state.InstanceId,
						},
					}
					_, state.err = svc.CreateTags(tagparam)
					if useDocker {
						host := mach.NewDockerHost(org, certpath, user, cert)
						if state.err == nil {
							state.err = host.InstallDockerEngine(*state.PublicIpAddress)
						}
						if state.err == nil {
							state.err = host.InstallDockerEngineCertificate(*state.PublicIpAddress, *state.PrivateIpAddress)
						}
					}
				}
				out <- state
				wg.Done()
			}(ec2_WaitForReady(inst.InstanceId))
		}
		wg.Wait()
	}()
	return out
}
<|endoftext|>


<commit_before>// Copyright 2015 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 rpcutils

package queue

import (
	"fmt"
	"sync/atomic"
	"time"
)

// Queue is a bounded blocking queue
type Queue interface {
	// Take pops an item off the head of the queue, waits indefinitely
	Take() interface{}

	// TakeChan pops an item off the head of the queue, waits time.Duration. If duration <0 waits indefinitely
	// Return chan interface for item in quey, and error chan for timeout
	TakeChan(time.Duration) (<-chan interface{}, <-chan error)

	// Poll removes item off the head of the queue, returns immediately. Returns item, bool is true if item was in
	// deque, false otherwise
	Poll() (interface{}, bool)

	// Put puts item in deque, waits indefinitely
	Put(item interface{})

	// Offer puts the item in queue. If successful return true, if queue is full return false
	Offer(item interface{}) bool

	//Returns the current number of items in the queue
	Size() int32

	//Returns the current number of items the queue can hold
	Capacity() int32
}

// NewChannelQueue creates a queue with a given capacity. If capacity <=0, error is returned
func NewChannelQueue(capacity int) (Queue, error) {
	cap := int32(capacity)
	if capacity <= 0 {
		return nil, fmt.Errorf("Invalid size for queue: %d", capacity)
	}
	qChan := make(chan interface{}, cap)

	return &chanQueue{capacity: cap, qChan: qChan}, nil
}

type chanQueue struct {
	qChan    chan interface{}
	capacity int32
	size     int32
}

func (q *chanQueue) TakeChan(timeout time.Duration) (<-chan interface{}, <-chan error) {
	timeoutChan := make(chan error, 1)
	resultChan := make(chan interface{}, 1)
	go func() {
		if timeout < 0 {
			item := <-q.qChan
			atomic.AddInt32(&q.size, -1)
			resultChan <- item
		} else {
			select {
			case item := <-q.qChan:
				atomic.AddInt32(&q.size, -1)
				resultChan <- item
			case <-time.After(timeout):
				timeoutChan <- fmt.Errorf("Timeout waiting on queue item: %s", timeout)
			}
		}
	}()
	return resultChan, timeoutChan
}

func (q *chanQueue) Take() interface{} {
	itemChan, _ := q.TakeChan(0)
	select {
	case item := <-itemChan:
		return item
	}
}
func (q *chanQueue) Poll() (interface{}, bool) {
	select {
	case item := <-q.qChan:
		atomic.AddInt32(&q.size, -1)
		return item, true
	default:
		return nil, false
	}
}

func (q *chanQueue) Put(item interface{}) {
	q.qChan <- item
	atomic.AddInt32(&q.size, 1)
}
func (q *chanQueue) Offer(item interface{}) bool {
	select {
	case q.qChan <- item:
		atomic.AddInt32(&q.size, 1)
		return true
	default:
		return false
	}
}
func (q *chanQueue) Capacity() int32 {
	return q.capacity
}

func (q *chanQueue) Size() int32 {
	return q.size
}
<commit_msg>CC-4119 memory leak in commons/queue/queue.go<commit_after>// Copyright 2015 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 rpcutils

package queue

import (
	"fmt"
	"sync/atomic"
	"time"
)

// Queue is a bounded blocking queue
type Queue interface {
	// Take pops an item off the head of the queue, waits indefinitely
	Take() interface{}

	// TakeChan pops an item off the head of the queue, waits time.Duration. If duration <0 waits indefinitely
	// Return chan interface for item in quey, and error chan for timeout
	TakeChan(time.Duration) (<-chan interface{}, <-chan error)

	// Poll removes item off the head of the queue, returns immediately. Returns item, bool is true if item was in
	// deque, false otherwise
	Poll() (interface{}, bool)

	// Put puts item in deque, waits indefinitely
	Put(item interface{})

	// Offer puts the item in queue. If successful return true, if queue is full return false
	Offer(item interface{}) bool

	//Returns the current number of items in the queue
	Size() int32

	//Returns the current number of items the queue can hold
	Capacity() int32
}

// NewChannelQueue creates a queue with a given capacity. If capacity <=0, error is returned
func NewChannelQueue(capacity int) (Queue, error) {
	cap := int32(capacity)
	if capacity <= 0 {
		return nil, fmt.Errorf("Invalid size for queue: %d", capacity)
	}
	qChan := make(chan interface{}, cap)

	return &chanQueue{capacity: cap, qChan: qChan}, nil
}

type chanQueue struct {
	qChan    chan interface{}
	capacity int32
	size     int32
}

func (q *chanQueue) TakeChan(timeout time.Duration) (<-chan interface{}, <-chan error) {
	timeoutChan := make(chan error, 1)
	resultChan := make(chan interface{}, 1)
	go func() {
		if timeout < 0 {
			item := <-q.qChan
			atomic.AddInt32(&q.size, -1)
			resultChan <- item
		} else {
			timer := time.NewTimer(timeout)
			// just to be sure we won't miss anything
			defer timer.Stop()
			select {
			case item := <-q.qChan:
				atomic.AddInt32(&q.size, -1)
				resultChan <- item
			// CC-4119 Calling time.After(timeout) caused a memory leak
			case <-timer.C:
				timeoutChan <- fmt.Errorf("Timeout waiting on queue item: %s", timeout)
			}
		}
	}()
	return resultChan, timeoutChan
}

func (q *chanQueue) Take() interface{} {
	itemChan, _ := q.TakeChan(0)
	select {
	case item := <-itemChan:
		return item
	}
}
func (q *chanQueue) Poll() (interface{}, bool) {
	select {
	case item := <-q.qChan:
		atomic.AddInt32(&q.size, -1)
		return item, true
	default:
		return nil, false
	}
}

func (q *chanQueue) Put(item interface{}) {
	q.qChan <- item
	atomic.AddInt32(&q.size, 1)
}
func (q *chanQueue) Offer(item interface{}) bool {
	select {
	case q.qChan <- item:
		atomic.AddInt32(&q.size, 1)
		return true
	default:
		return false
	}
}
func (q *chanQueue) Capacity() int32 {
	return q.capacity
}

func (q *chanQueue) Size() int32 {
	return q.size
}
<|endoftext|>


<commit_before>package easyvk

// An UserObject contains information about user.
// https://vk.com/dev/objects/user
type UserObject struct {
	ID         int `json:"id"`
	FirstName  string `json:"first_name"`
	LastName   string `json:"last_name"`
	Sex        int `json:"sex"`
	Nickname   string `json:"nickname"`
	MaidenName string `json:"maiden_name"`
	Domain     string `json:"domain"`
	ScreenName string `json:"screen_name"`
	Bdate      string `json:"bdate"`
	City struct {
		ID    int `json:"id"`
		Title string `json:"title"`
	} `json:"city"`
	Country struct {
		ID    int `json:"id"`
		Title string `json:"title"`
	} `json:"country"`
	Photo50                string `json:"photo_50"`
	Photo100               string `json:"photo_100"`
	Photo200               string `json:"photo_200"`
	PhotoMax               string `json:"photo_max"`
	Photo200Orig           string `json:"photo_200_orig"`
	Photo400Orig           string `json:"photo_400_orig"`
	PhotoMaxOrig           string `json:"photo_max_orig"`
	PhotoID                string `json:"photo_id"`
	HasPhoto               int `json:"has_photo"`
	HasMobile              int `json:"has_mobile"`
	IsFriend               int `json:"is_friend"`
	FriendStatus           int `json:"friend_status"`
	Online                 int `json:"online"`
	WallComments           int `json:"wall_comments"`
	CanPost                int `json:"can_post"`
	CanSeeAllPosts         int `json:"can_see_all_posts"`
	CanSeeAudio            int `json:"can_see_audio"`
	CanWritePrivateMessage int `json:"can_write_private_message"`
	CanSendFriendRequest   int `json:"can_send_friend_request"`
	MobilePhone            string `json:"mobile_phone"`
	HomePhone              string `json:"home_phone"`
	Site                   string `json:"site"`
	Status                 string `json:"status"`
	LastSeen struct {
		Time     int `json:"time"`
		Platform int `json:"platform"`
	} `json:"last_seen"`
	CropPhoto struct {
		Photo struct {
			ID       int `json:"id"`
			AlbumID  int `json:"album_id"`
			OwnerID  int `json:"owner_id"`
			Photo75  string `json:"photo_75"`
			Photo130 string `json:"photo_130"`
			Photo604 string `json:"photo_604"`
			Width    int `json:"width"`
			Height   int `json:"height"`
			Text     string `json:"text"`
			Date     int `json:"date"`
			PostID   int `json:"post_id"`
		} `json:"photo"`
		Crop struct {
			X  float64 `json:"x"`
			Y  float64 `json:"y"`
			X2 float64 `json:"x2"`
			Y2 float64 `json:"y2"`
		} `json:"crop"`
		Rect struct {
			X  int `json:"x"`
			Y  int `json:"y"`
			X2 int `json:"x2"`
			Y2 int `json:"y2"`
		} `json:"rect"`
	} `json:"crop_photo"`
	Verified         int `json:"verified"`
	FollowersCount   int `json:"followers_count"`
	Blacklisted      int `json:"blacklisted"`
	BlacklistedByMe  int `json:"blacklisted_by_me"`
	IsFavorite       int `json:"is_favorite"`
	IsHiddenFromFeed int `json:"is_hidden_from_feed"`
	CommonCount      int `json:"common_count"`
	Career           []interface{} `json:"career"`
	Military         []interface{} `json:"military"`
	University       int `json:"university"`
	UniversityName   string `json:"university_name"`
	Faculty          int `json:"faculty"`
	FacultyName      string `json:"faculty_name"`
	Graduation       int `json:"graduation"`
	HomeTown         string `json:"home_town"`
	Relation         int `json:"relation"`
	Personal struct {
		Religion   string `json:"religion"`
		InspiredBy string `json:"inspired_by"`
		PeopleMain int `json:"people_main"`
		LifeMain   int `json:"life_main"`
		Smoking    int `json:"smoking"`
		Alcohol    int `json:"alcohol"`
	} `json:"personal"`
	Interests    string `json:"interests"`
	Music        string `json:"music"`
	Activities   string `json:"activities"`
	Movies       string `json:"movies"`
	Tv           string `json:"tv"`
	Books        string `json:"books"`
	Games        string `json:"games"`
	Universities []interface{} `json:"universities"`
	Schools      []interface{} `json:"schools"`
	About        string `json:"about"`
	Relatives    []interface{} `json:"relatives"`
	Quotes       string `json:"quotes"`
	Deactivated  string `json:"deactivated"`
}

// A PhotoObject contains information about photo.
// https://vk.com/dev/objects/photo
type PhotoObject struct {
	ID      int `json:"id"`
	AlbumID int `json:"album_id"`
	OwnerID int `json:"owner_id"`
	UserID  int `json:"user_id"`
	Sizes []struct {
		Src    string `json:"src"`
		Width  int `json:"width"`
		Height int `json:"height"`
		Type   string `json:"type"`
	} `json:"sizes"`
	Text   string `json:"text"`
	Date   int `json:"date"`
	PostID int `json:"post_id"`
	Likes struct {
		UserLikes int `json:"user_likes"`
		Count     int `json:"count"`
	} `json:"likes"`
	Reposts struct {
		Count int `json:"count"`
	} `json:"reposts"`
	Comments struct {
		Count int `json:"count"`
	} `json:"comments"`
	CanComment int `json:"can_comment"`
	Tags struct {
		Count int `json:"count"`
	} `json:"tags"`
}

// A VideoObject contains information about video.
// https://vk.com/dev/objects/video
type VideoObject struct {
	ID          int `json:"id"`
	OwnerID     int `json:"owner_id"`
	Title       string `json:"title"`
	Duration    int `json:"duration"`
	Description string `json:"description"`
	Date        int `json:"date"`
	Comments    int `json:"comments"`
	Views       int `json:"views"`
	Width       int `json:"width"`
	Height      int `json:"height"`
	Photo130    string `json:"photo_130"`
	Photo320    string `json:"photo_320"`
	Photo800    string `json:"photo_800"`
	AddingDate  int `json:"adding_date"`
	Files struct {
		Mp4240 string `json:"mp4_240"`
		Mp4360 string `json:"mp4_360"`
		Mp4480 string `json:"mp4_480"`
		Mp4720 string `json:"mp4_720"`
	} `json:"files"`
	Player     string `json:"player"`
	CanAdd     int `json:"can_add"`
	CanComment int `json:"can_comment"`
	CanRepost  int `json:"can_repost"`
	Likes struct {
		UserLikes int `json:"user_likes"`
		Count     int `json:"count"`
	} `json:"likes"`
	Reposts struct {
		Count        int `json:"count"`
		UserReposted int `json:"user_reposted"`
	} `json:"reposts"`
	Repeat int `json:"repeat"`
}
<commit_msg>Complete photo object structure<commit_after>package easyvk

// An UserObject contains information about user.
// https://vk.com/dev/objects/user
type UserObject struct {
	ID         int `json:"id"`
	FirstName  string `json:"first_name"`
	LastName   string `json:"last_name"`
	Sex        int `json:"sex"`
	Nickname   string `json:"nickname"`
	MaidenName string `json:"maiden_name"`
	Domain     string `json:"domain"`
	ScreenName string `json:"screen_name"`
	Bdate      string `json:"bdate"`
	City struct {
		ID    int `json:"id"`
		Title string `json:"title"`
	} `json:"city"`
	Country struct {
		ID    int `json:"id"`
		Title string `json:"title"`
	} `json:"country"`
	Photo50                string `json:"photo_50"`
	Photo100               string `json:"photo_100"`
	Photo200               string `json:"photo_200"`
	PhotoMax               string `json:"photo_max"`
	Photo200Orig           string `json:"photo_200_orig"`
	Photo400Orig           string `json:"photo_400_orig"`
	PhotoMaxOrig           string `json:"photo_max_orig"`
	PhotoID                string `json:"photo_id"`
	HasPhoto               int `json:"has_photo"`
	HasMobile              int `json:"has_mobile"`
	IsFriend               int `json:"is_friend"`
	FriendStatus           int `json:"friend_status"`
	Online                 int `json:"online"`
	WallComments           int `json:"wall_comments"`
	CanPost                int `json:"can_post"`
	CanSeeAllPosts         int `json:"can_see_all_posts"`
	CanSeeAudio            int `json:"can_see_audio"`
	CanWritePrivateMessage int `json:"can_write_private_message"`
	CanSendFriendRequest   int `json:"can_send_friend_request"`
	MobilePhone            string `json:"mobile_phone"`
	HomePhone              string `json:"home_phone"`
	Site                   string `json:"site"`
	Status                 string `json:"status"`
	LastSeen struct {
		Time     int `json:"time"`
		Platform int `json:"platform"`
	} `json:"last_seen"`
	CropPhoto struct {
		Photo struct {
			ID       int `json:"id"`
			AlbumID  int `json:"album_id"`
			OwnerID  int `json:"owner_id"`
			Photo75  string `json:"photo_75"`
			Photo130 string `json:"photo_130"`
			Photo604 string `json:"photo_604"`
			Width    int `json:"width"`
			Height   int `json:"height"`
			Text     string `json:"text"`
			Date     int `json:"date"`
			PostID   int `json:"post_id"`
		} `json:"photo"`
		Crop struct {
			X  float64 `json:"x"`
			Y  float64 `json:"y"`
			X2 float64 `json:"x2"`
			Y2 float64 `json:"y2"`
		} `json:"crop"`
		Rect struct {
			X  int `json:"x"`
			Y  int `json:"y"`
			X2 int `json:"x2"`
			Y2 int `json:"y2"`
		} `json:"rect"`
	} `json:"crop_photo"`
	Verified         int `json:"verified"`
	FollowersCount   int `json:"followers_count"`
	Blacklisted      int `json:"blacklisted"`
	BlacklistedByMe  int `json:"blacklisted_by_me"`
	IsFavorite       int `json:"is_favorite"`
	IsHiddenFromFeed int `json:"is_hidden_from_feed"`
	CommonCount      int `json:"common_count"`
	Career           []interface{} `json:"career"`
	Military         []interface{} `json:"military"`
	University       int `json:"university"`
	UniversityName   string `json:"university_name"`
	Faculty          int `json:"faculty"`
	FacultyName      string `json:"faculty_name"`
	Graduation       int `json:"graduation"`
	HomeTown         string `json:"home_town"`
	Relation         int `json:"relation"`
	Personal struct {
		Religion   string `json:"religion"`
		InspiredBy string `json:"inspired_by"`
		PeopleMain int `json:"people_main"`
		LifeMain   int `json:"life_main"`
		Smoking    int `json:"smoking"`
		Alcohol    int `json:"alcohol"`
	} `json:"personal"`
	Interests    string `json:"interests"`
	Music        string `json:"music"`
	Activities   string `json:"activities"`
	Movies       string `json:"movies"`
	Tv           string `json:"tv"`
	Books        string `json:"books"`
	Games        string `json:"games"`
	Universities []interface{} `json:"universities"`
	Schools      []interface{} `json:"schools"`
	About        string `json:"about"`
	Relatives    []interface{} `json:"relatives"`
	Quotes       string `json:"quotes"`
	Deactivated  string `json:"deactivated"`
}

// A PhotoObject contains information about photo.
// https://vk.com/dev/objects/photo
type PhotoObject struct {
	ID      int `json:"id"`
	AlbumID int `json:"album_id"`
	OwnerID int `json:"owner_id"`
	UserID  int `json:"user_id"`
	Sizes []struct {
		Src    string `json:"src"`
		Width  int `json:"width"`
		Height int `json:"height"`
		Type   string `json:"type"`
	} `json:"sizes"`
	Text      string `json:"text"`
	Date      int `json:"date"`
	PostID    int `json:"post_id"`
	Width     int `json:"width"`
	Height    int `json:"height"`
	Photo75   string `json:"photo_75"`
	Photo130  string `json:"photo_130"`
	Photo604  string `json:"photo_604"`
	Photo807  string `json:"photo_807"`
	Photo1280 string `json:"photo_1280"`
	Photo2560 string `json:"photo_2560"`
	Likes struct {
		UserLikes int `json:"user_likes"`
		Count     int `json:"count"`
	} `json:"likes"`
	Reposts struct {
		Count int `json:"count"`
	} `json:"reposts"`
	Comments struct {
		Count int `json:"count"`
	} `json:"comments"`
	CanComment int `json:"can_comment"`
	Tags struct {
		Count int `json:"count"`
	} `json:"tags"`
}

// A VideoObject contains information about video.
// https://vk.com/dev/objects/video
type VideoObject struct {
	ID          int `json:"id"`
	OwnerID     int `json:"owner_id"`
	Title       string `json:"title"`
	Duration    int `json:"duration"`
	Description string `json:"description"`
	Date        int `json:"date"`
	Comments    int `json:"comments"`
	Views       int `json:"views"`
	Width       int `json:"width"`
	Height      int `json:"height"`
	Photo130    string `json:"photo_130"`
	Photo320    string `json:"photo_320"`
	Photo800    string `json:"photo_800"`
	AddingDate  int `json:"adding_date"`
	Files struct {
		Mp4240 string `json:"mp4_240"`
		Mp4360 string `json:"mp4_360"`
		Mp4480 string `json:"mp4_480"`
		Mp4720 string `json:"mp4_720"`
	} `json:"files"`
	Player     string `json:"player"`
	CanAdd     int `json:"can_add"`
	CanComment int `json:"can_comment"`
	CanRepost  int `json:"can_repost"`
	Likes struct {
		UserLikes int `json:"user_likes"`
		Count     int `json:"count"`
	} `json:"likes"`
	Reposts struct {
		Count        int `json:"count"`
		UserReposted int `json:"user_reposted"`
	} `json:"reposts"`
	Repeat int `json:"repeat"`
}
<|endoftext|>


<commit_before>package edgeworth

import "testing"

func Test_GetGroup_1(t *testing.T) {
	var control GroupBits
	var bits ControlBits
	var input Instruction
	control = 0x3
	input = 0x01234123
	bits = input.GetControlBits()

	if bits.GetGroup() != control {
		t.Errorf("Expected %d, got %d.", control, bits.GetGroup())
	}
}

func Test_GetOperation_1(t *testing.T) {
	var control OpBits
	var bits ControlBits
	var input Instruction
	control = 0x15 // 0xA8 >> 3
	input = 0x012341A8
	bits = input.GetControlBits()
	if bits.GetOperation() != control {
		t.Errorf("Expected %d, got %d.", control, bits.GetOperation())
	}
}
<commit_msg>Updated tests further<commit_after>package edgeworth

import "testing"

var DefaultInstructionEncoding Instruction = 0x01234123

func Test_GetGroup_1(t *testing.T) {
	control := GroupBits(0x3)
	bits := DefaultInstructionEncoding.GetControlBits()
	if bits.GetGroup() != control {
		t.Errorf("Expected %d, got %d.", control, bits.GetGroup())
	}
}

func Test_GetOperation_1(t *testing.T) {
	control := OpBits(0x4)
	bits := DefaultInstructionEncoding.GetControlBits()
	if bits.GetOperation() != control {
		t.Errorf("Expected %d, got %d.", control, bits.GetOperation())
	}
}

func Test_GetControlBits_1(t *testing.T) {
	control := ControlBits(0x23)
	bits := DefaultInstructionEncoding.GetControlBits()
	if bits != control {
		t.Errorf("Expected %d, got %d.", control, bits)
	}
}
<|endoftext|>


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

// The math package provides basic constants and mathematical functions.
package math

// Mathematical constants.
// Reference: http://www.research.att.com/~njas/sequences/Axxxxxx
const (
	E	= 2.71828182845904523536028747135266249775724709369995957496696763;  // A001113
	Pi	= 3.14159265358979323846264338327950288419716939937510582097494459;  // A000796
	Phi	= 1.61803398874989484820458683436563811772030917980576286213544862;  // A001622

	Sqrt2	= 1.41421356237309504880168872420969807856967187537694807317667974;  // A002193
	SqrtE	= 1.64872127070012814684865078781416357165377610071014801157507931;  // A019774
	SqrtPi	= 1.77245385090551602729816748334114518279754945612238712821380779;  // A002161
	SqrtPhi	= 1.27201964951406896425242246173749149171560804184009624861664038;  // A139339

	Ln2	= 0.693147180559945309417232121458176568075500134360255254120680009; // A002162
	Log2E	= 1/Ln2;
	Ln10	= 2.30258509299404568401799145468436420760110148862877297603332790;  // A002392
	Log10E	= 1/Ln10;
)

// BUG(rsc): The manual should define the special cases for all of these functions.
<commit_msg>constants for floating point limits<commit_after>// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The math package provides basic constants and mathematical functions.
package math

// Mathematical constants.
// Reference: http://www.research.att.com/~njas/sequences/Axxxxxx
const (
	E	= 2.71828182845904523536028747135266249775724709369995957496696763;  // A001113
	Pi	= 3.14159265358979323846264338327950288419716939937510582097494459;  // A000796
	Phi	= 1.61803398874989484820458683436563811772030917980576286213544862;  // A001622

	Sqrt2	= 1.41421356237309504880168872420969807856967187537694807317667974;  // A002193
	SqrtE	= 1.64872127070012814684865078781416357165377610071014801157507931;  // A019774
	SqrtPi	= 1.77245385090551602729816748334114518279754945612238712821380779;  // A002161
	SqrtPhi	= 1.27201964951406896425242246173749149171560804184009624861664038;  // A139339

	Ln2	= 0.693147180559945309417232121458176568075500134360255254120680009; // A002162
	Log2E	= 1/Ln2;
	Ln10	= 2.30258509299404568401799145468436420760110148862877297603332790;  // A002392
	Log10E	= 1/Ln10;

	MaxFloat32	= 3.40282346638528860e+38;
	MinFloat32	= 1.40129846432481707e-45;
	MaxFloat64	= 1.7976931348623157e+308;
	MinFloat64	= 5.0e-324;
)

// BUG(rsc): The manual should define the special cases for all of these functions.
<|endoftext|>


<commit_before>package btcwallet

import (
	"path/filepath"

	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"

	"github.com/roasbeef/btcwallet/chain"

	// This is required to register bdb as a valid walletdb driver. In the
	// init function of the package, it registers itself. The import is used
	// to activate the side effects w/o actually binding the package name to
	// a file-level variable.
	_ "github.com/roasbeef/btcwallet/walletdb/bdb"
)

var (
	lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
	defaultDataDir  = lnwalletHomeDir

	defaultLogFilename = "lnwallet.log"
	defaultLogDirname  = "logs"
	defaultLogDir      = filepath.Join(lnwalletHomeDir, defaultLogDirname)

	btcdHomeDir        = btcutil.AppDataDir("btcd", false)
	btcdHomedirCAFile  = filepath.Join(btcdHomeDir, "rpc.cert")
	defaultRPCKeyFile  = filepath.Join(lnwalletHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")

	// defaultPubPassphrase is the default public wallet passphrase which is
	// used when the user indicates they do not want additional protection
	// provided by having all public data in the wallet encrypted by a
	// passphrase only known to them.
	defaultPubPassphrase = []byte("public")

	walletDbName = "lnwallet.db"
)

// Config is a struct which houses configuration parameters which modify the
// instance of BtcWallet generated by the New() function.
type Config struct {
	// DataDir is the name of the directory where the wallet's persistent
	// state should be stored.
	DataDir string

	// LogDir is the name of the directory which should be used to store
	// generated log files.
	LogDir string

	// PrivatePass is the private password to the underlying btcwallet
	// instance. Without this, the wallet cannot be decrypted and operated.
	PrivatePass []byte

	// PublicPass is the optional public password to btcwallet. This is
	// optionally used to encrypt public material such as public keys and
	// scripts.
	PublicPass []byte

	// HdSeed is an optional seed to feed into the wallet. If this is
	// unspecified, a new seed will be generated.
	HdSeed []byte

	// ChainSource is the primary chain interface. This is used to operate
	// the wallet and do things such as rescanning, sending transactions,
	// notifications for received funds, etc.
	ChainSource chain.Interface

	// FeeEstimator is an instance of the fee estimator interface which
	// will be used by the wallet to dynamically set transaction fees when
	// crafting transactions.
	FeeEstimator lnwallet.FeeEstimator

	// NetParams is the net parameters for the target chain.
	NetParams *chaincfg.Params
}

// NetworkDir returns the directory name of a network directory to hold wallet
// files.
func NetworkDir(dataDir string, chainParams *chaincfg.Params) string {
	netname := chainParams.Name

	// For now, we must always name the testnet data directory as "testnet"
	// and not "testnet3" or any other version, as the chaincfg testnet3
	// parameters will likely be switched to being named "testnet3" in the
	// future.  This is done to future proof that change, and an upgrade
	// plan to move the testnet3 data directory can be worked out later.
	if chainParams.Net == wire.TestNet3 {
		netname = "testnet"
	}

	return filepath.Join(dataDir, netname)
}
<commit_msg>lnwallet/btcwallet/config: add CoinType to configuration<commit_after>package btcwallet

import (
	"path/filepath"

	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/roasbeef/btcd/chaincfg"
	"github.com/roasbeef/btcd/wire"
	"github.com/roasbeef/btcutil"

	"github.com/roasbeef/btcwallet/chain"

	// This is required to register bdb as a valid walletdb driver. In the
	// init function of the package, it registers itself. The import is used
	// to activate the side effects w/o actually binding the package name to
	// a file-level variable.
	_ "github.com/roasbeef/btcwallet/walletdb/bdb"
)

var (
	lnwalletHomeDir = btcutil.AppDataDir("lnwallet", false)
	defaultDataDir  = lnwalletHomeDir

	defaultLogFilename = "lnwallet.log"
	defaultLogDirname  = "logs"
	defaultLogDir      = filepath.Join(lnwalletHomeDir, defaultLogDirname)

	btcdHomeDir        = btcutil.AppDataDir("btcd", false)
	btcdHomedirCAFile  = filepath.Join(btcdHomeDir, "rpc.cert")
	defaultRPCKeyFile  = filepath.Join(lnwalletHomeDir, "rpc.key")
	defaultRPCCertFile = filepath.Join(lnwalletHomeDir, "rpc.cert")

	// defaultPubPassphrase is the default public wallet passphrase which is
	// used when the user indicates they do not want additional protection
	// provided by having all public data in the wallet encrypted by a
	// passphrase only known to them.
	defaultPubPassphrase = []byte("public")

	walletDbName = "lnwallet.db"
)

// Config is a struct which houses configuration parameters which modify the
// instance of BtcWallet generated by the New() function.
type Config struct {
	// DataDir is the name of the directory where the wallet's persistent
	// state should be stored.
	DataDir string

	// LogDir is the name of the directory which should be used to store
	// generated log files.
	LogDir string

	// PrivatePass is the private password to the underlying btcwallet
	// instance. Without this, the wallet cannot be decrypted and operated.
	PrivatePass []byte

	// PublicPass is the optional public password to btcwallet. This is
	// optionally used to encrypt public material such as public keys and
	// scripts.
	PublicPass []byte

	// HdSeed is an optional seed to feed into the wallet. If this is
	// unspecified, a new seed will be generated.
	HdSeed []byte

	// ChainSource is the primary chain interface. This is used to operate
	// the wallet and do things such as rescanning, sending transactions,
	// notifications for received funds, etc.
	ChainSource chain.Interface

	// FeeEstimator is an instance of the fee estimator interface which
	// will be used by the wallet to dynamically set transaction fees when
	// crafting transactions.
	FeeEstimator lnwallet.FeeEstimator

	// NetParams is the net parameters for the target chain.
	NetParams *chaincfg.Params

	// CoinType specifies the BIP 44 coin type to be used for derivation.
	CoinType uint32
}

// NetworkDir returns the directory name of a network directory to hold wallet
// files.
func NetworkDir(dataDir string, chainParams *chaincfg.Params) string {
	netname := chainParams.Name

	// For now, we must always name the testnet data directory as "testnet"
	// and not "testnet3" or any other version, as the chaincfg testnet3
	// parameters will likely be switched to being named "testnet3" in the
	// future.  This is done to future proof that change, and an upgrade
	// plan to move the testnet3 data directory can be worked out later.
	if chainParams.Net == wire.TestNet3 {
		netname = "testnet"
	}

	return filepath.Join(dataDir, netname)
}
<|endoftext|>


<commit_before>package compilers

import (
	"github.com/albrow/scribble/config"
	"github.com/albrow/scribble/log"
	"github.com/albrow/scribble/util"
	"github.com/howeyc/fsnotify"
	"os"
	"path/filepath"
	"strings"
)

// Compilers is a slice of all known Compilers.
// NOTE: it is important that PostsCompiler is the first
// item in the slice, because some other compilers rely on
// the existence of a list of parsed Post objects. For example,
// AceCompiler relies on the Posts function returning the correct
// results inside of ace templates.
var Compilers = []Compiler{&PostsCompiler, &SassCompiler, &HtmlTemplatesCompiler}

// CompilerPaths is a map of Compiler to the matched paths for that Compiler
var CompilerPaths = map[Compiler][]string{}

// UnmatchedPaths is a slice of paths which do not match any compiler
var UnmatchedPaths = []string{}

// noHiddenNoIgnore is a MatchFunc which returns true for any path that is
// does not begin with a "." or "_" and is not inside any directory which begins
// with a "." or "_".
var noHiddenNoIgnore = filenameMatchFunc("*", true, true)

// MatchFunc represents a function which should return true iff
// path matches some pattern. Compilers and Watchers return a MatchFunc
// to specify which paths they are concerned with.
type MatchFunc func(path string) (bool, error)

// Initer is an interface satisfied by any Compiler which needs
// to do something before Compile or CompileAll are called.
type Initer interface {
	// Init allows a Compiler or Watcher to do any necessary
	// setup before other methods are called. (e.g. set the
	// result of PathMatch based on some config variable). The
	// Init method is not required, but it will be called if
	// it exists.
	Init()
}

// Compiler is capable of compiling a certain type of file. It
// also is responsible for watching for changes to certain types
// of files.
type Compiler interface {
	// CompileMatchFunc returns a MatchFunc which will be applied
	// to every path in config.SourceDir to determine which paths
	// a Compiler is responsible for compiling.
	CompileMatchFunc() MatchFunc
	// Compile compiles a source file identified by srcPath.
	// srcPath will be some path that matches according to the
	// MatchFunc for the Compiler.
	Compile(srcPath string) error
	// CompileAll compiles all the files found in each path.
	// srcPaths will be all paths that match according to
	// the MatchFunc for the Compiler.
	CompileAll(srcPaths []string) error
	// RemoveAllOld removes all files which this compiler has created
	// in config.DestDir. A Compiler is responsible for keeping track
	// of the files it has created and removing them when this method
	// is called.
	RemoveOld() error
	// WatchMatchFunc returns a MatchFunc which will be applied
	// to every path in config.SourceDir to determine which paths
	// a Compiler is responsible for watching. Note that the files
	// that are watched may not be the same as those that are compiled.
	// E.g, files that start with an underscore are typically not compiled,
	// but may be imported or used by other files that are compiled, and
	// therefore should be watched.
	WatchMatchFunc() MatchFunc
	// FileChanged is triggered whenever a relevant file is changed.
	// Typically, the Compiler should recompile certain files.
	// srcPath will be some path that matches according to WatchMatchFunc,
	// and ev is the FileEvent associated with the change.
	FileChanged(srcPath string, ev fsnotify.FileEvent) error
}

// FindPaths iterates recursively through config.SourceDir and
// returns all the matched paths using mf as a MatchFunc.
func FindPaths(mf MatchFunc) ([]string, error) {
	paths := []string{}
	walkFunc := matchWalkFunc(&paths, mf)
	if err := filepath.Walk(config.SourceDir, walkFunc); err != nil {
		return nil, err
	}
	return paths, nil
}

// CompileAll compiles all files in config.SourceDir by delegating each path to
// it's corresponding Compiler. If a path in config.SourceDir does not match any Compiler,
// it will be copied to config.DestDir directly.
func CompileAll() error {
	initCompilers()
	if err := delegateCompilePaths(); err != nil {
		return err
	}
	for _, c := range Compilers {
		if err := compileAllForCompiler(c); err != nil {
			return err
		}
	}
	if err := copyUnmatchedPaths(UnmatchedPaths); err != nil {
		return err
	}
	return nil
}

// compileAllForCompiler recompiles all paths that are matched according to the given compiler's
// MatchFunc
func compileAllForCompiler(c Compiler) error {
	paths, found := CompilerPaths[c]
	if found && len(paths) > 0 {
		if err := c.CompileAll(paths); err != nil {
			return err
		}
	}
	return nil
}

// FileChanged delegates file changes to the appropriate compiler. If srcPath does not match any
// Compiler, it will be copied to config.DestDir directly.
func FileChanged(srcPath string, ev fsnotify.FileEvent) error {
	hasMatch := false
	for _, c := range Compilers {
		if match, err := c.WatchMatchFunc()(srcPath); err != nil {
			return err
		} else if match {
			hasMatch = true
			log.Info.Printf("CHANGED: %s", ev.Name)
			if err := c.FileChanged(srcPath, ev); err != nil {
				return err
			}
		}
	}
	if !hasMatch {
		// srcPath did not match any Compiler
		if match, err := noHiddenNoIgnore(srcPath); err != nil {
			return err
		} else if match {
			log.Info.Printf("CHANGED: %s", ev.Name)
			// TODO: move the file into config.DestDir verbatim
			log.Default.Printf("Unmatched path: %s", srcPath)
		}
	}
	return nil
}

// initCompilers calls the Init method for each compiler that has it
func initCompilers() {
	for _, c := range Compilers {
		if initer, ok := c.(Initer); ok {
			// If the Compiler has an Init function, run it
			initer.Init()
		}
		CompilerPaths[c] = []string{}
	}
}

// recompileAllForCompiler calls RemoveOld to remove any old files the compiiler may have
// created in config.DestDir. Then it finds all the paths that match the given compiler
// (in case something changed since the last time we found the paths). Next, it compiles
// all of the matching files with a call to CompileAll. Finally, it removes any empty
// directories that may still be in config.DestDir.
func recompileAllForCompiler(c Compiler) error {
	// Have the compiler remove any files it may have created
	if err := c.RemoveOld(); err != nil {
		return err
	}
	// Find all the paths again for the given compiler (in case something changed)
	paths, err := FindPaths(c.CompileMatchFunc())
	if err != nil {
		return err
	}
	// Compile all the paths
	if err := c.CompileAll(paths); err != nil {
		return err
	}
	// Cleanup by removing any empty dirs from config.DestDir
	if err := util.RemoveEmptyDirs(config.DestDir); err != nil {
		return err
	}
	return nil
}

// delegateCompilePaths walks through the source directory, checks if a path matches according
// to the MatchFunc for each compiler, and adds the path to CompilerPaths if it does
// match.
func delegateCompilePaths() error {
	return filepath.Walk(config.SourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		matched := false
		for _, c := range Compilers {
			if match, err := c.CompileMatchFunc()(path); err != nil {
				return err
			} else if match {
				matched = true
				CompilerPaths[c] = append(CompilerPaths[c], path)
			}
		}
		if !matched && !info.IsDir() {
			// If the path didn't match any compilers according to their MatchFuncs,
			// it isn't a dir, and it is not a hidden or ignored file, add it to the
			// list of unmatched paths. These will be copied from config.SourceDir
			// to config.DestDir without being changed.
			if match, err := noHiddenNoIgnore(path); err != nil {
				return err
			} else if match {
				UnmatchedPaths = append(UnmatchedPaths, path)
			}
		}
		return nil
	})
}

// copyUnmatchedPaths copies paths from config.SourceDir to config.DestDir without changing them. It perserves
// directory structures, so e.g., source/archive/index.html becomes public/archive/index.html.
func copyUnmatchedPaths(paths []string) error {
	for _, path := range paths {
		destPath := strings.Replace(path, config.SourceDir, config.DestDir, 1)
		if err := util.CopyFile(path, destPath); err != nil {
			return err
		}
	}
	return nil
}

// filenameMatchFunc creates and returns a MatchFunc which
// will check for exact matches between the filename for some path (i.e. filepath.Base(path))
// and pattern, according to the filepath.Match semantics. You can add options to ignore hidden
// files and directories (which start with a '.') or files which should typically be ignored by
// scribble (which start with a '_').
// BUG: Filename matching is not expected to work on windows.
func filenameMatchFunc(pattern string, ignoreHidden bool, ignoreUnderscore bool) MatchFunc {
	return func(path string) (bool, error) {
		if ignoreHidden {
			// Check for hidden files and directories, i.e. those
			// that begin with a '.'. If we find the substring "/."
			// it must mean that some file or directory in the path
			// is hidden.
			if strings.Contains(path, string(os.PathSeparator)+".") {
				return false, nil
			}
		}
		if ignoreUnderscore {
			// Check for files and directories that begin with a '_',
			// which have special meaning in scribble and should typically
			// be ignored. If we find the substring "/_" it must mean that some
			// file or directory in the path starts with an underscore.
			if strings.Contains(path, string(os.PathSeparator)+"_") {
				return false, nil
			}
		}
		return filepath.Match(pattern, filepath.Base(path))
	}
}

// pathMatchFunc creates and returns a MatchFunc which
// will check for exact matches between some full path and pattern, according to the
// filepath.Match semantics. You can add options to ignore hidden files and directories
// (which start with a '.') or files which should typically be ignored by scribble (which
// start with a '_').
// BUG: Path matching is not expected to work on windows.
func pathMatchFunc(pattern string, ignoreHidden bool, ignoreUnderscore bool) MatchFunc {
	return func(path string) (bool, error) {
		if ignoreHidden {
			// Check for hidden files and directories, i.e. those
			// that begin with a '.'. If we find the substring "/."
			// it must mean that some file or directory in the path
			// is hidden.
			// TODO: Make this compatible with windows.
			if strings.Contains(path, string(os.PathSeparator)+".") {
				return false, nil
			}
		}
		if ignoreUnderscore {
			// Check for files and directories that begin with a '_',
			// which have special meaning in scribble and should typically
			// be ignored. If we find the substring "/_" it must mean that some
			// file or directory in the path starts with an underscore.
			// TODO: Make this compatible with windows.
			if strings.Contains(path, string(os.PathSeparator)+"_") {
				return false, nil
			}
		}
		return filepath.Match(pattern, path)
	}
}

// matchWalkFunc creates and returns a filepath.WalkFunc which
// will check if a file path matches using matchFunc (i.e. when matchFunc returns true),
// and append all the paths that match to paths. Typically, this should only be used when
// you want to get the paths for a specific Compiler/Watcher and not for any of the others,
// e.g. for testing.
func matchWalkFunc(paths *[]string, matchFunc func(path string) (bool, error)) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if matched, err := matchFunc(path); err != nil {
			return err
		} else if matched {
			if paths != nil {
				(*paths) = append(*paths, path)
			}
		}
		return nil
	}
}

// intersectMatchFuncs returns a MatchFunc which is functionally equivalent to the
// intersection of each MatchFunc in funcs. That is, it returns true iff each and
// every MatchFunc in funcs returns true.
func intersectMatchFuncs(funcs ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		for _, f := range funcs {
			if match, err := f(path); err != nil {
				return false, err
			} else if !match {
				return false, nil
			}
		}
		return true, nil
	}
}

// unionMatchFuncs returns a MatchFunc which is functionally equivalent to the
// union of each MatchFunc in funcs. That is, it returns true iff at least one MatchFunc
// in funcs returns true.
func unionMatchFuncs(funcs ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		for _, f := range funcs {
			if match, err := f(path); err != nil {
				return false, err
			} else if match {
				return true, nil
			}
		}
		return false, nil
	}
}

// excludeMatchFuncs returns a MatchFunc which returns true iff f returns true
// and no function in excludes returns true. It allows you to match with a simple
// function f but exclude the path if it matches some other pattern.
func excludeMatchFuncs(f MatchFunc, excludes ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		if firstMatch, err := f(path); err != nil {
			return false, err
		} else if !firstMatch {
			// If it doesn't match f, always return false
			return false, nil
		}
		// If it does match f, check each MatchFunc in excludes
		for _, exclude := range excludes {
			if excludeMatch, err := exclude(path); err != nil {
				return false, err
			} else if excludeMatch {
				// if path matches any MatchFunc in excludes, we should
				// exclude it. i.e., return false
				return false, nil
			}
		}
		// For all other cases, return true
		return true, nil
	}
}
<commit_msg>Respond to changes in unmatched paths when watching/serving.<commit_after>package compilers

import (
	"github.com/albrow/scribble/config"
	"github.com/albrow/scribble/log"
	"github.com/albrow/scribble/util"
	"github.com/howeyc/fsnotify"
	"os"
	"path/filepath"
	"strings"
)

// Compilers is a slice of all known Compilers.
// NOTE: it is important that PostsCompiler is the first
// item in the slice, because some other compilers rely on
// the existence of a list of parsed Post objects. For example,
// AceCompiler relies on the Posts function returning the correct
// results inside of ace templates.
var Compilers = []Compiler{&PostsCompiler, &SassCompiler, &HtmlTemplatesCompiler}

// CompilerPaths is a map of Compiler to the matched paths for that Compiler
var CompilerPaths = map[Compiler][]string{}

// UnmatchedPaths is a slice of paths which do not match any compiler
var UnmatchedPaths = []string{}

// noHiddenNoIgnore is a MatchFunc which returns true for any path that is
// does not begin with a "." or "_" and is not inside any directory which begins
// with a "." or "_".
var noHiddenNoIgnore = filenameMatchFunc("*", true, true)

// MatchFunc represents a function which should return true iff
// path matches some pattern. Compilers and Watchers return a MatchFunc
// to specify which paths they are concerned with.
type MatchFunc func(path string) (bool, error)

// Initer is an interface satisfied by any Compiler which needs
// to do something before Compile or CompileAll are called.
type Initer interface {
	// Init allows a Compiler or Watcher to do any necessary
	// setup before other methods are called. (e.g. set the
	// result of PathMatch based on some config variable). The
	// Init method is not required, but it will be called if
	// it exists.
	Init()
}

// Compiler is capable of compiling a certain type of file. It
// also is responsible for watching for changes to certain types
// of files.
type Compiler interface {
	// CompileMatchFunc returns a MatchFunc which will be applied
	// to every path in config.SourceDir to determine which paths
	// a Compiler is responsible for compiling.
	CompileMatchFunc() MatchFunc
	// Compile compiles a source file identified by srcPath.
	// srcPath will be some path that matches according to the
	// MatchFunc for the Compiler.
	Compile(srcPath string) error
	// CompileAll compiles all the files found in each path.
	// srcPaths will be all paths that match according to
	// the MatchFunc for the Compiler.
	CompileAll(srcPaths []string) error
	// RemoveAllOld removes all files which this compiler has created
	// in config.DestDir. A Compiler is responsible for keeping track
	// of the files it has created and removing them when this method
	// is called.
	RemoveOld() error
	// WatchMatchFunc returns a MatchFunc which will be applied
	// to every path in config.SourceDir to determine which paths
	// a Compiler is responsible for watching. Note that the files
	// that are watched may not be the same as those that are compiled.
	// E.g, files that start with an underscore are typically not compiled,
	// but may be imported or used by other files that are compiled, and
	// therefore should be watched.
	WatchMatchFunc() MatchFunc
	// FileChanged is triggered whenever a relevant file is changed.
	// Typically, the Compiler should recompile certain files.
	// srcPath will be some path that matches according to WatchMatchFunc,
	// and ev is the FileEvent associated with the change.
	FileChanged(srcPath string, ev fsnotify.FileEvent) error
}

// FindPaths iterates recursively through config.SourceDir and
// returns all the matched paths using mf as a MatchFunc.
func FindPaths(mf MatchFunc) ([]string, error) {
	paths := []string{}
	walkFunc := matchWalkFunc(&paths, mf)
	if err := filepath.Walk(config.SourceDir, walkFunc); err != nil {
		return nil, err
	}
	return paths, nil
}

// CompileAll compiles all files in config.SourceDir by delegating each path to
// it's corresponding Compiler. If a path in config.SourceDir does not match any Compiler,
// it will be copied to config.DestDir directly.
func CompileAll() error {
	initCompilers()
	if err := delegateCompilePaths(); err != nil {
		return err
	}
	for _, c := range Compilers {
		if err := compileAllForCompiler(c); err != nil {
			return err
		}
	}
	if err := copyUnmatchedPaths(UnmatchedPaths); err != nil {
		return err
	}
	return nil
}

// compileAllForCompiler recompiles all paths that are matched according to the given compiler's
// MatchFunc
func compileAllForCompiler(c Compiler) error {
	paths, found := CompilerPaths[c]
	if found && len(paths) > 0 {
		if err := c.CompileAll(paths); err != nil {
			return err
		}
	}
	return nil
}

// FileChanged delegates file changes to the appropriate compiler. If srcPath does not match any
// Compiler, it will be copied to config.DestDir directly.
func FileChanged(srcPath string, ev fsnotify.FileEvent) error {
	hasMatch := false
	for _, c := range Compilers {
		if match, err := c.WatchMatchFunc()(srcPath); err != nil {
			return err
		} else if match {
			hasMatch = true
			log.Info.Printf("CHANGED: %s", ev.Name)
			if err := c.FileChanged(srcPath, ev); err != nil {
				return err
			}
		}
	}
	if !hasMatch {
		// srcPath did not match any Compiler
		if match, err := noHiddenNoIgnore(srcPath); err != nil {
			return err
		} else if match {
			log.Info.Printf("CHANGED: %s", ev.Name)
			// Move the file into config.DestDir as is
			if err := copyUnmatchedPaths([]string{ev.Name}); err != nil {
				return err
			}
		}
	}
	return nil
}

// initCompilers calls the Init method for each compiler that has it
func initCompilers() {
	for _, c := range Compilers {
		if initer, ok := c.(Initer); ok {
			// If the Compiler has an Init function, run it
			initer.Init()
		}
		CompilerPaths[c] = []string{}
	}
}

// recompileAllForCompiler calls RemoveOld to remove any old files the compiiler may have
// created in config.DestDir. Then it finds all the paths that match the given compiler
// (in case something changed since the last time we found the paths). Next, it compiles
// all of the matching files with a call to CompileAll. Finally, it removes any empty
// directories that may still be in config.DestDir.
func recompileAllForCompiler(c Compiler) error {
	// Have the compiler remove any files it may have created
	if err := c.RemoveOld(); err != nil {
		return err
	}
	// Find all the paths again for the given compiler (in case something changed)
	paths, err := FindPaths(c.CompileMatchFunc())
	if err != nil {
		return err
	}
	// Compile all the paths
	if err := c.CompileAll(paths); err != nil {
		return err
	}
	// Cleanup by removing any empty dirs from config.DestDir
	if err := util.RemoveEmptyDirs(config.DestDir); err != nil {
		return err
	}
	return nil
}

// delegateCompilePaths walks through the source directory, checks if a path matches according
// to the MatchFunc for each compiler, and adds the path to CompilerPaths if it does
// match.
func delegateCompilePaths() error {
	return filepath.Walk(config.SourceDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		matched := false
		for _, c := range Compilers {
			if match, err := c.CompileMatchFunc()(path); err != nil {
				return err
			} else if match {
				matched = true
				CompilerPaths[c] = append(CompilerPaths[c], path)
			}
		}
		if !matched && !info.IsDir() {
			// If the path didn't match any compilers according to their MatchFuncs,
			// it isn't a dir, and it is not a hidden or ignored file, add it to the
			// list of unmatched paths. These will be copied from config.SourceDir
			// to config.DestDir without being changed.
			if match, err := noHiddenNoIgnore(path); err != nil {
				return err
			} else if match {
				UnmatchedPaths = append(UnmatchedPaths, path)
			}
		}
		return nil
	})
}

// copyUnmatchedPaths copies paths from config.SourceDir to config.DestDir without changing them. It perserves
// directory structures, so e.g., source/archive/index.html becomes public/archive/index.html.
func copyUnmatchedPaths(paths []string) error {
	for _, path := range paths {
		destPath := strings.Replace(path, config.SourceDir, config.DestDir, 1)
		log.Success.Printf("CREATE: %s -> %s", path, destPath)
		if err := util.CopyFile(path, destPath); err != nil {
			return err
		}
	}
	return nil
}

// filenameMatchFunc creates and returns a MatchFunc which
// will check for exact matches between the filename for some path (i.e. filepath.Base(path))
// and pattern, according to the filepath.Match semantics. You can add options to ignore hidden
// files and directories (which start with a '.') or files which should typically be ignored by
// scribble (which start with a '_').
// BUG: Filename matching is not expected to work on windows.
func filenameMatchFunc(pattern string, ignoreHidden bool, ignoreUnderscore bool) MatchFunc {
	return func(path string) (bool, error) {
		if ignoreHidden {
			// Check for hidden files and directories, i.e. those
			// that begin with a '.'. If we find the substring "/."
			// it must mean that some file or directory in the path
			// is hidden.
			if strings.Contains(path, string(os.PathSeparator)+".") {
				return false, nil
			}
		}
		if ignoreUnderscore {
			// Check for files and directories that begin with a '_',
			// which have special meaning in scribble and should typically
			// be ignored. If we find the substring "/_" it must mean that some
			// file or directory in the path starts with an underscore.
			if strings.Contains(path, string(os.PathSeparator)+"_") {
				return false, nil
			}
		}
		return filepath.Match(pattern, filepath.Base(path))
	}
}

// pathMatchFunc creates and returns a MatchFunc which
// will check for exact matches between some full path and pattern, according to the
// filepath.Match semantics. You can add options to ignore hidden files and directories
// (which start with a '.') or files which should typically be ignored by scribble (which
// start with a '_').
// BUG: Path matching is not expected to work on windows.
func pathMatchFunc(pattern string, ignoreHidden bool, ignoreUnderscore bool) MatchFunc {
	return func(path string) (bool, error) {
		if ignoreHidden {
			// Check for hidden files and directories, i.e. those
			// that begin with a '.'. If we find the substring "/."
			// it must mean that some file or directory in the path
			// is hidden.
			// TODO: Make this compatible with windows.
			if strings.Contains(path, string(os.PathSeparator)+".") {
				return false, nil
			}
		}
		if ignoreUnderscore {
			// Check for files and directories that begin with a '_',
			// which have special meaning in scribble and should typically
			// be ignored. If we find the substring "/_" it must mean that some
			// file or directory in the path starts with an underscore.
			// TODO: Make this compatible with windows.
			if strings.Contains(path, string(os.PathSeparator)+"_") {
				return false, nil
			}
		}
		return filepath.Match(pattern, path)
	}
}

// matchWalkFunc creates and returns a filepath.WalkFunc which
// will check if a file path matches using matchFunc (i.e. when matchFunc returns true),
// and append all the paths that match to paths. Typically, this should only be used when
// you want to get the paths for a specific Compiler/Watcher and not for any of the others,
// e.g. for testing.
func matchWalkFunc(paths *[]string, matchFunc func(path string) (bool, error)) filepath.WalkFunc {
	return func(path string, info os.FileInfo, err error) error {
		if matched, err := matchFunc(path); err != nil {
			return err
		} else if matched {
			if paths != nil {
				(*paths) = append(*paths, path)
			}
		}
		return nil
	}
}

// intersectMatchFuncs returns a MatchFunc which is functionally equivalent to the
// intersection of each MatchFunc in funcs. That is, it returns true iff each and
// every MatchFunc in funcs returns true.
func intersectMatchFuncs(funcs ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		for _, f := range funcs {
			if match, err := f(path); err != nil {
				return false, err
			} else if !match {
				return false, nil
			}
		}
		return true, nil
	}
}

// unionMatchFuncs returns a MatchFunc which is functionally equivalent to the
// union of each MatchFunc in funcs. That is, it returns true iff at least one MatchFunc
// in funcs returns true.
func unionMatchFuncs(funcs ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		for _, f := range funcs {
			if match, err := f(path); err != nil {
				return false, err
			} else if match {
				return true, nil
			}
		}
		return false, nil
	}
}

// excludeMatchFuncs returns a MatchFunc which returns true iff f returns true
// and no function in excludes returns true. It allows you to match with a simple
// function f but exclude the path if it matches some other pattern.
func excludeMatchFuncs(f MatchFunc, excludes ...MatchFunc) MatchFunc {
	return func(path string) (bool, error) {
		if firstMatch, err := f(path); err != nil {
			return false, err
		} else if !firstMatch {
			// If it doesn't match f, always return false
			return false, nil
		}
		// If it does match f, check each MatchFunc in excludes
		for _, exclude := range excludes {
			if excludeMatch, err := exclude(path); err != nil {
				return false, err
			} else if excludeMatch {
				// if path matches any MatchFunc in excludes, we should
				// exclude it. i.e., return false
				return false, nil
			}
		}
		// For all other cases, return true
		return true, nil
	}
}
<|endoftext|>


<commit_before>package complaintdb

import (
	"time"

	"github.com/skypies/geo"
	"github.com/skypies/util/date"
	"github.com/skypies/util/dsprovider"

	"github.com/skypies/complaints/complaintdb/types"
)

// {{{ cdb.GetComplaintPositionsInSpanByIcao

// uniqueUsers: if true, only one result per unique user; else one result per complaint.
// icaoid: use empty string to get all complaints; else limits to that aircraft

func (cdb ComplaintDB)GetComplaintPositionsInSpanByIcao(start,end time.Time, uniqueUsers bool, icaoid string) ([]geo.Latlong, error) {
	ret := []geo.Latlong{}

	q := cdb.NewComplaintQuery().
		ByTimespan(start,end).
		Project("Profile.Lat","Profile.Long")

	if uniqueUsers {
		// This is really horribly inefficient :(

		// Can't simply add Distinct() to this, as it forces us to add Timestamp into the projection,
		// which renders Distinct() kinda useless, as the timestamps are always distinct :/
		// So we have to filter afterwards, which is horrible.
		q = cdb.NewComplaintQuery().
			ByTimespan(start,end).
			Project("Profile.Lat","Profile.Long","Profile.EmailAddress")
	}

	if icaoid != "" {
		q = q.ByIcaoId(icaoid)
	}

	q = q.Limit(-1)

	// Could do this here ... but maybe semantics clearer higher up the stack.
	// _,data,err := cdb.getMaybeCachedComplaintsByQuery(q, "my_keyname")

	results,err := cdb.LookupAll(q)
	if err != nil {
		return ret,err
	}

	seen := map[string]int{}
	for _,c := range results {
		if uniqueUsers {
			if _,exists := seen[c.Profile.EmailAddress]; exists {
				continue
			} else {
				seen[c.Profile.EmailAddress]++
			}
		}

		// Round off the position data to avoid exposing address
		pos := ApproximatePosition(geo.Latlong{Lat:c.Profile.Lat, Long:c.Profile.Long})
		if ! pos.IsNil() {
			ret = append(ret, pos)
		}
	}

	return ret,nil
}

// }}}
// {{{ cdb.GetProfileLocations

// uniqueUsers: if true, only one result per unique user; else one result per complaint.
// icaoid: use empty string to get all complaints; else limits to that aircraft

func (cdb ComplaintDB)GetProfileLocations() ([]geo.Latlong, error) {
	ret := []geo.Latlong{}

	q := cdb.NewProfileQuery().
		//Project("Lat","Long"). // WTF; this limits the resultset to 280 results, not 5300 ??
		Limit(-1)

	profiles,err := cdb.LookupAllProfiles(q)
	if err != nil {
		return ret,err
	}

	cdb.Infof("We saw %d locations", len(profiles))

	for _,cp := range profiles {
		// Round off the position data to avoid exposing address
		pos := ApproximatePosition(geo.Latlong{Lat:cp.Lat, Long:cp.Long})
		if ! pos.IsNil() {
			ret = append(ret, pos)
		}
	}

	return ret,nil
}

// }}}

// {{{ cdb.getDailyCountsByEmailAdress

func (cdb ComplaintDB) getDailyCountsByEmailAdress(ea string) ([]types.CountItem, error) {
	cdb.Debugf("gDCBEA_001", "starting")
	gs,_ := cdb.LoadGlobalStats()
	cdb.Debugf("gDCBEA_002", "global stats loaded")
	stats := map[string]*DailyCount{}
	maxDays := 60 // As many rows as we care about

	if gs != nil {
		for i,dc := range gs.Counts {
			if i >= maxDays { break }
			stats[date.Datestring2MidnightPdt(dc.Datestring).Format("Jan 02")] = &gs.Counts[i]
		}
	}
	cdb.Debugf("gDCBEA_003", "global stats munged; loading daily")

	dailys,err := cdb.GetDailyCounts(ea)
	if err != nil {
		return []types.CountItem{}, err
	}

	counts := []types.CountItem{}

	cdb.Debugf("gDCBEA_004", "daily stats loaded (%d dailys, %d stats)", len(dailys), len(stats))
	for i,daily := range dailys {
		if i >= maxDays { break }
		item := types.CountItem{
			Key: daily.Timestamp().Format("Jan 02"),
			Count: daily.NumComplaints,
		}
		if dc,exists := stats[item.Key]; exists {
			item.TotalComplainers = dc.NumComplainers
			item.TotalComplaints = dc.NumComplaints
			item.IsMaxComplainers = dc.IsMaxComplainers
			item.IsMaxComplaints = dc.IsMaxComplaints
		}
		counts = append(counts, item)
	}
	cdb.Debugf("gDCBEA_005", "daily stats munged (%d counts)", len(counts))

	return counts, nil
}

// }}}
// {{{ cdb.GetAllByEmailAddress

func (cdb ComplaintDB) GetAllByEmailAddress(ea string, everything bool) (*types.ComplaintsAndProfile, error) {
	var cap types.ComplaintsAndProfile

	cdb.Debugf("GABEA_001", "cdb.GetAllByEmailAddress starting (everything=%v)", everything)

	if cp,err := cdb.MustLookupProfile(ea); err == dsprovider.ErrNoSuchEntity {
		return nil,nil  // No such profile exists
	} else if err != nil {
		return nil,err  // A real problem occurred
	} else {
		cdb.Debugf("GABEA_002", "profile retrieved")
		cap.Profile = *cp
	}

	if everything {
		if c,err := cdb.LookupAll(cdb.CQByEmail(ea)); err != nil {
			return nil,err
		} else {
			cdb.Debugf("GABEA_003", "EVERYTHING retrieved")
			cap.Complaints = c
		}

	} else {
		// Just today
		s,e := date.WindowForToday()
		if c,err := cdb.LookupAll(cdb.CQByEmail(ea).ByTimespan(s,e)); err != nil {
			return nil,err
		} else {
			cdb.Debugf("GABEA_004", "WindowForToday retrieved; now getting counts")
			cap.Complaints = c
		}
	}

	if counts,err := cdb.getDailyCountsByEmailAdress(ea); err != nil {
		return nil,err
	} else {
		cdb.Debugf("GABEA_005", "counts retrieved")
		cap.Counts = counts
	}

	return &cap, nil
}

// }}}

func (cdb ComplaintDB)CountComplaintsAndUniqueUsersIn(s,e time.Time) (int, int, error) {
	// What we'd like to do, is to do Project("Profile.EmailAddress").Distinct().ByTimespan().
	// But we can't, because ...
	//  1. ByTimespan does Filter("Timestamp") ...
	//  2. so we need to include "Timestamp" in the Project() args ...
	//  3. but Distinct() acts on all projected fields, and the timestamps defeat grouping
	// So we need to count users manually.
	q := cdb.NewComplaintQuery().Project("Profile.EmailAddress").ByTimespan(s,e)

	complaints,err := cdb.LookupAll(q)
	if err != nil {
		return 0, 0, err
	}

	users := map[string]int{}
	for _,c := range complaints {
		users[c.Profile.EmailAddress]++
	}

	return len(complaints), len(users), nil
}

// {{{ -------------------------={ E N D }=----------------------------------

// Local variables:
// folded-file: t
// end:

// }}}
<commit_msg>Refactor the daily counter, so we can get list of users<commit_after>package complaintdb

import (
	"time"

	"github.com/skypies/geo"
	"github.com/skypies/util/date"
	"github.com/skypies/util/dsprovider"

	"github.com/skypies/complaints/complaintdb/types"
)

// {{{ cdb.GetComplaintPositionsInSpanByIcao

// uniqueUsers: if true, only one result per unique user; else one result per complaint.
// icaoid: use empty string to get all complaints; else limits to that aircraft

func (cdb ComplaintDB)GetComplaintPositionsInSpanByIcao(start,end time.Time, uniqueUsers bool, icaoid string) ([]geo.Latlong, error) {
	ret := []geo.Latlong{}

	q := cdb.NewComplaintQuery().
		ByTimespan(start,end).
		Project("Profile.Lat","Profile.Long")

	if uniqueUsers {
		// This is really horribly inefficient :(

		// Can't simply add Distinct() to this, as it forces us to add Timestamp into the projection,
		// which renders Distinct() kinda useless, as the timestamps are always distinct :/
		// So we have to filter afterwards, which is horrible.
		q = cdb.NewComplaintQuery().
			ByTimespan(start,end).
			Project("Profile.Lat","Profile.Long","Profile.EmailAddress")
	}

	if icaoid != "" {
		q = q.ByIcaoId(icaoid)
	}

	q = q.Limit(-1)

	// Could do this here ... but maybe semantics clearer higher up the stack.
	// _,data,err := cdb.getMaybeCachedComplaintsByQuery(q, "my_keyname")

	results,err := cdb.LookupAll(q)
	if err != nil {
		return ret,err
	}

	seen := map[string]int{}
	for _,c := range results {
		if uniqueUsers {
			if _,exists := seen[c.Profile.EmailAddress]; exists {
				continue
			} else {
				seen[c.Profile.EmailAddress]++
			}
		}

		// Round off the position data to avoid exposing address
		pos := ApproximatePosition(geo.Latlong{Lat:c.Profile.Lat, Long:c.Profile.Long})
		if ! pos.IsNil() {
			ret = append(ret, pos)
		}
	}

	return ret,nil
}

// }}}
// {{{ cdb.GetProfileLocations

// uniqueUsers: if true, only one result per unique user; else one result per complaint.
// icaoid: use empty string to get all complaints; else limits to that aircraft

func (cdb ComplaintDB)GetProfileLocations() ([]geo.Latlong, error) {
	ret := []geo.Latlong{}

	q := cdb.NewProfileQuery().
		//Project("Lat","Long"). // WTF; this limits the resultset to 280 results, not 5300 ??
		Limit(-1)

	profiles,err := cdb.LookupAllProfiles(q)
	if err != nil {
		return ret,err
	}

	cdb.Infof("We saw %d locations", len(profiles))

	for _,cp := range profiles {
		// Round off the position data to avoid exposing address
		pos := ApproximatePosition(geo.Latlong{Lat:cp.Lat, Long:cp.Long})
		if ! pos.IsNil() {
			ret = append(ret, pos)
		}
	}

	return ret,nil
}

// }}}

// {{{ cdb.getDailyCountsByEmailAdress

func (cdb ComplaintDB) getDailyCountsByEmailAdress(ea string) ([]types.CountItem, error) {
	cdb.Debugf("gDCBEA_001", "starting")
	gs,_ := cdb.LoadGlobalStats()
	cdb.Debugf("gDCBEA_002", "global stats loaded")
	stats := map[string]*DailyCount{}
	maxDays := 60 // As many rows as we care about

	if gs != nil {
		for i,dc := range gs.Counts {
			if i >= maxDays { break }
			stats[date.Datestring2MidnightPdt(dc.Datestring).Format("Jan 02")] = &gs.Counts[i]
		}
	}
	cdb.Debugf("gDCBEA_003", "global stats munged; loading daily")

	dailys,err := cdb.GetDailyCounts(ea)
	if err != nil {
		return []types.CountItem{}, err
	}

	counts := []types.CountItem{}

	cdb.Debugf("gDCBEA_004", "daily stats loaded (%d dailys, %d stats)", len(dailys), len(stats))
	for i,daily := range dailys {
		if i >= maxDays { break }
		item := types.CountItem{
			Key: daily.Timestamp().Format("Jan 02"),
			Count: daily.NumComplaints,
		}
		if dc,exists := stats[item.Key]; exists {
			item.TotalComplainers = dc.NumComplainers
			item.TotalComplaints = dc.NumComplaints
			item.IsMaxComplainers = dc.IsMaxComplainers
			item.IsMaxComplaints = dc.IsMaxComplaints
		}
		counts = append(counts, item)
	}
	cdb.Debugf("gDCBEA_005", "daily stats munged (%d counts)", len(counts))

	return counts, nil
}

// }}}
// {{{ cdb.GetAllByEmailAddress

func (cdb ComplaintDB) GetAllByEmailAddress(ea string, everything bool) (*types.ComplaintsAndProfile, error) {
	var cap types.ComplaintsAndProfile

	cdb.Debugf("GABEA_001", "cdb.GetAllByEmailAddress starting (everything=%v)", everything)

	if cp,err := cdb.MustLookupProfile(ea); err == dsprovider.ErrNoSuchEntity {
		return nil,nil  // No such profile exists
	} else if err != nil {
		return nil,err  // A real problem occurred
	} else {
		cdb.Debugf("GABEA_002", "profile retrieved")
		cap.Profile = *cp
	}

	if everything {
		if c,err := cdb.LookupAll(cdb.CQByEmail(ea)); err != nil {
			return nil,err
		} else {
			cdb.Debugf("GABEA_003", "EVERYTHING retrieved")
			cap.Complaints = c
		}

	} else {
		// Just today
		s,e := date.WindowForToday()
		if c,err := cdb.LookupAll(cdb.CQByEmail(ea).ByTimespan(s,e)); err != nil {
			return nil,err
		} else {
			cdb.Debugf("GABEA_004", "WindowForToday retrieved; now getting counts")
			cap.Complaints = c
		}
	}

	if counts,err := cdb.getDailyCountsByEmailAdress(ea); err != nil {
		return nil,err
	} else {
		cdb.Debugf("GABEA_005", "counts retrieved")
		cap.Counts = counts
	}

	return &cap, nil
}

// }}}

// {{{ cdb.GetUniqueUsersAndCountsIn

func (cdb ComplaintDB)GetUniqueUsersAndCountsIn(s,e time.Time) ([]string, int, error) {
	// What we'd like to do, is to do Project("Profile.EmailAddress").Distinct().ByTimespan().
	// But we can't, because ...
	//  1. ByTimespan does Filter("Timestamp") ...
	//  2. so we need to include "Timestamp" in the Project() args ...
	//  3. but Distinct() acts on all projected fields, and the timestamps defeat grouping
	// So we need to count users manually.
	// As a bonus, that means we can count complaints while we're at it.
	q := cdb.NewComplaintQuery().Project("Profile.EmailAddress").ByTimespan(s,e)

	complaints,err := cdb.LookupAll(q)
	if err != nil {
		return nil, 0, err
	}

	user_map := map[string]int{}
	for _,c := range complaints {
		user_map[c.Profile.EmailAddress]++
	}

	users := make([]string, len(user_map))
	n_complaints := 0
	i := 0
	for user,n := range user_map {
		n_complaints += n
		users[i] = user
		i++
	}

	return users, n_complaints, nil
}

// }}}
// {{{ cdb.CountComplaintsAndUniqueUsersIn

func (cdb ComplaintDB)CountComplaintsAndUniqueUsersIn(s,e time.Time) (int, int, error) {
	users,n_complaints, err := cdb.GetUniqueUsersAndCountsIn(s,e)
	if err != nil {
		return 0, 0, err
	}

	return n_complaints, len(users), nil
}

// }}}

// {{{ -------------------------={ E N D }=----------------------------------

// Local variables:
// folded-file: t
// end:

// }}}
<|endoftext|>


<commit_before>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

package mango

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

// CardRegistration is used to register a credit card.
//
// http://docs.mangopay.com/api-references/card-registration/
type CardRegistration struct {
	ProcessReply
	// Id of the author.
	UserId string
	// Currency of the registered card.
	Currency string
	// Key sent with the card details and the PreregistrationData.
	AccessKey string
	// This passphrase is sent with the card details and the AccessKey.
	PreregistrationData string
	// The actual URL to POST the card details, the access key and the
	// PreregistrationData.
	CardRegistrationUrl string
	// Part of the reply, once the card details, the AccessKey and the
	// PreregistrationData has been sent.
	CardRegistrationData string
	CardType             string
	// CardId if part of the reply, once the CardRegistration has been
	// edited with the CardRegistrationData.
	CardId string

	service *MangoPay
	// true after Init() is successful.
	isInitialized bool
}

func (c *CardRegistration) String() string {
	return fmt.Sprintf(`
Id                      : %s
Tag                     : %s
CreationDate            : %s
ResultCode              : %s
ResultMessage           : %s
Status                  : %s
UserId                  : %s
Currency                : %s
AccessKey               : %s
PreregistrationData     : %s
CardRegistrationUrl     : %s
CardRegistrationData    : %s
CardType                : %s
CardId                  : %s
`, c.Id, c.Tag, unixTimeToString(c.CreationDate), c.ResultCode, c.ResultMessage, c.Status, c.UserId, c.Currency, c.AccessKey, c.PreregistrationData, c.CardRegistrationUrl, c.CardRegistrationData, c.CardType, c.CardId)
}

// NewCardRegistration creates a new credit card registration process.
func (m *MangoPay) NewCardRegistration(user Consumer, currency string) (*CardRegistration, error) {
	id := ""
	switch user.(type) {
	case *LegalUser:
		id = user.(*LegalUser).Id
	case *NaturalUser:
		id = user.(*NaturalUser).Id
	}
	if id == "" {
		return nil, errors.New("empty user ID. Unable to create card registration.")
	}
	cr := &CardRegistration{
		UserId:        id,
		Currency:      currency,
		ProcessReply:  ProcessReply{},
		isInitialized: false,
	}
	cr.service = m
	return cr, nil
}

// Init initiates the process of getting pre-registration data and access
// key from MangoPay to allow a user to post his credit card info to the
// c.CardRegistrationUrl (which is an external banking service).
//
// User's card details must be sent directly through an HTML form to the
// c.CardRegistrationUrl.
//
// The HTML form must have the following input fields:
//  - "data" (hidden) equals to c.PreregistrationData
//  - "accessKeyRef" (hidden) equals to c.AccessKey
//  - "cardNumber" equals to the user's credit card number
//  - "cardExpirationDate" equals to the user's card expiration date (format: MMYY)
//  - "cardCvx" equals to user's 3-digits cvx code
//  - "returnURL" so we can retrieve the final registration data token
//
// A successful call to Init() will fill in the PreregistrationData and
// AccessKey fields of the current CardRegistration object automatically.
func (c *CardRegistration) Init() error {
	data := JsonObject{}
	j, err := json.Marshal(c)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(j, &data); err != nil {
		return err
	}

	// Force float64 to int conversion after unmarshalling.
	for _, field := range []string{"CreationDate"} {
		data[field] = int(data[field].(float64))
	}

	// Fields not allowed when initiating a card registration.
	for _, field := range []string{"Id", "CreationDate", "ExecutionDate",
		"ResultCode", "ResultMessage", "Status", "AccessKey", "CardId",
		"CardRegistrationData", "CardRegistrationUrl", "CardType",
		"PreregistrationData", "Tag"} {
		delete(data, field)
	}

	cr, err := c.service.cardRegistartionRequest(actionCreateCardRegistration, data)
	if err != nil {
		return err
	}
	// Backup private service
	service := c.service
	*c = *cr
	c.service = service

	// Okay for step 2.
	c.isInitialized = true
	return nil
}

// Register effectively registers the credit card against the MangoPay service. The
// registrationData value is returned by the external banking service that deals with
// the credit card information, and is obtained by submitting an HTML form to
// the external banking service.
func (c *CardRegistration) Register(registrationData string) error {
	if !strings.HasPrefix(registrationData, "data=") {
		return errors.New("invalid registration data. Must start with data=")
	}
	if !c.isInitialized {
		return errors.New("card registration process not initialized. Did you call Init() first?")
	}
	cr, err := c.service.cardRegistartionRequest(actionSendCardRegistrationData,
		JsonObject{"Id": c.Id, "RegistrationData": registrationData})
	if err != nil {
		return err
	}
	// Backup private members
	serv := c.service
	isr := c.isInitialized
	*c = *cr
	c.CardRegistrationData = registrationData
	c.service = serv
	c.isInitialized = isr
	return nil
}

func (m *MangoPay) cardRegistartionRequest(action mangoAction, data JsonObject) (*CardRegistration, error) {
	resp, err := m.request(action, data)
	if err != nil {
		return nil, err
	}
	u := new(CardRegistration)
	if err := m.unMarshalJSONResponse(resp, u); err != nil {
		return nil, err
	}
	return u, nil
}
<commit_msg>Add detailled documentation for card registration<commit_after>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

package mango

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

// CardRegistration is used to register a credit card.
//
// http://docs.mangopay.com/api-references/card-registration/
type CardRegistration struct {
	ProcessReply
	// Id of the author.
	UserId string
	// Currency of the registered card.
	Currency string
	// Key sent with the card details and the PreregistrationData.
	AccessKey string
	// This passphrase is sent with the card details and the AccessKey.
	PreregistrationData string
	// The actual URL to POST the card details, the access key and the
	// PreregistrationData.
	CardRegistrationUrl string
	// Part of the reply, once the card details, the AccessKey and the
	// PreregistrationData has been sent.
	CardRegistrationData string
	CardType             string
	// CardId if part of the reply, once the CardRegistration has been
	// edited with the CardRegistrationData.
	CardId string

	service *MangoPay
	// true after Init() is successful.
	isInitialized bool
}

func (c *CardRegistration) String() string {
	return fmt.Sprintf(`
Id                      : %s
Tag                     : %s
CreationDate            : %s
ResultCode              : %s
ResultMessage           : %s
Status                  : %s
UserId                  : %s
Currency                : %s
AccessKey               : %s
PreregistrationData     : %s
CardRegistrationUrl     : %s
CardRegistrationData    : %s
CardType                : %s
CardId                  : %s
`, c.Id, c.Tag, unixTimeToString(c.CreationDate), c.ResultCode, c.ResultMessage,
		c.Status, c.UserId, c.Currency, c.AccessKey, c.PreregistrationData,
		c.CardRegistrationUrl, c.CardRegistrationData, c.CardType, c.CardId)
}

// NewCardRegistration creates a new credit card registration object that can
// be used to register a new credit card for a given user.
//
// Registering a new credit card involves the following workflow:
//
//  1. Create a new CardRegistration object
//  2. Call .Init() to pre-register the card against MangoPay services and get
//     access tokens required to register the credit card againts an external
//     banking service
//  3. Insert those tokens in an HTML form submitted by the user directly to
//     the external banking service
//  4. Get the final token from the external banking service
//  5. Call .Register() with this token to commit credit card registration at
//     MangoPay
//
// See http://docs.mangopay.com/api-references/card-registration/
//
// Example:
//
//  user := NewNaturalUser(...)
//  cr, err := NewCardRegistration(user, "EUR")
//  if err != nil {
//      log.Fatal(err)
//  }
//  if err := cr.Init(); err != nil {
//      log.Fatal(err)
//  }}
//
// Now render an HTML form for user card details (see Init()). Once submitted,
// you get the final token as a string starting with "data=". Use this token to
// finally register the card:
//
//  if err := cr.Register(token); err != nil {
//      log.Fatal(err)
//  }}
func (m *MangoPay) NewCardRegistration(user Consumer, currency string) (*CardRegistration, error) {
	id := ""
	switch user.(type) {
	case *LegalUser:
		id = user.(*LegalUser).Id
	case *NaturalUser:
		id = user.(*NaturalUser).Id
	}
	if id == "" {
		return nil, errors.New("empty user ID. Unable to create card registration.")
	}
	cr := &CardRegistration{
		UserId:        id,
		Currency:      currency,
		ProcessReply:  ProcessReply{},
		isInitialized: false,
	}
	cr.service = m
	return cr, nil
}

// Init initiates the process of getting pre-registration data and access
// key from MangoPay to allow a user to post his credit card info to the
// c.CardRegistrationUrl (which is an external banking service).
//
// User's card details must be sent directly through an HTML form to the
// c.CardRegistrationUrl.
//
// The HTML form must have the following input fields:
//  - "data" (hidden) equals to c.PreregistrationData
//  - "accessKeyRef" (hidden) equals to c.AccessKey
//  - "cardNumber" equals to the user's credit card number
//  - "cardExpirationDate" equals to the user's card expiration date (format: MMYY)
//  - "cardCvx" equals to user's 3-digits cvx code
//  - "returnURL" so we can retrieve the final registration data token
//
// A successful call to Init() will fill in the PreregistrationData and
// AccessKey fields of the current CardRegistration object automatically.
func (c *CardRegistration) Init() error {
	data := JsonObject{}
	j, err := json.Marshal(c)
	if err != nil {
		return err
	}
	if err := json.Unmarshal(j, &data); err != nil {
		return err
	}

	// Force float64 to int conversion after unmarshalling.
	for _, field := range []string{"CreationDate"} {
		data[field] = int(data[field].(float64))
	}

	// Fields not allowed when initiating a card registration.
	for _, field := range []string{"Id", "CreationDate", "ExecutionDate",
		"ResultCode", "ResultMessage", "Status", "AccessKey", "CardId",
		"CardRegistrationData", "CardRegistrationUrl", "CardType",
		"PreregistrationData", "Tag"} {
		delete(data, field)
	}

	cr, err := c.service.cardRegistartionRequest(actionCreateCardRegistration, data)
	if err != nil {
		return err
	}
	// Backup private service
	service := c.service
	*c = *cr
	c.service = service

	// Okay for step 2.
	c.isInitialized = true
	return nil
}

// Register effectively registers the credit card against the MangoPay service. The
// registrationData value is returned by the external banking service that deals with
// the credit card information, and is obtained by submitting an HTML form to
// the external banking service.
func (c *CardRegistration) Register(registrationData string) error {
	if !strings.HasPrefix(registrationData, "data=") {
		return errors.New("invalid registration data. Must start with data=")
	}
	if !c.isInitialized {
		return errors.New("card registration process not initialized. Did you call Init() first?")
	}
	cr, err := c.service.cardRegistartionRequest(actionSendCardRegistrationData,
		JsonObject{"Id": c.Id, "RegistrationData": registrationData})
	if err != nil {
		return err
	}
	// Backup private members
	serv := c.service
	isr := c.isInitialized
	*c = *cr
	c.CardRegistrationData = registrationData
	c.service = serv
	c.isInitialized = isr
	return nil
}

func (m *MangoPay) cardRegistartionRequest(action mangoAction, data JsonObject) (*CardRegistration, error) {
	resp, err := m.request(action, data)
	if err != nil {
		return nil, err
	}
	u := new(CardRegistration)
	if err := m.unMarshalJSONResponse(resp, u); err != nil {
		return nil, err
	}
	return u, nil
}
<|endoftext|>