<commit_before>package lazyexp

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

// A Node represents a blocking calculation. Create one using NewNode().
//
// You'll probably want to embed a Node in your struct that will contain the result.
type Node interface {
	Fetch(context.Context) error
	// FetchStrict never cancels siblings on error and can be useful for debugging, but should generally be avoided.
	FetchStrict(context.Context) error
	fetched() bool
	noUserImplementations()
}

// A Dependency is a Node whose result another Node depends on.
//
// Created using ContinueOnError(), CancelOnError(), CancelOnCompletion() and AbortOnError().
type Dependency struct {
	node         Node
	onCompletion completionStrategy
}

type dependencyIndex struct {
	Dependency
	index int
}

// ContinueOnError returns a Dependency where any Node Fetch() errors are simply passed on without affecting any other Nodes.
func ContinueOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: 0,
	}
}

// CancelOnError returns a Dependency where any Node Fetch() error causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csCancel,
	}
}

// CancelOnCompletion returns a Dependency that upon Node Fetch() completion causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnCompletion(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csSuccess | csCancel,
	}
}

// CancelOnSuccess returns a Dependency that upon Node Fetch() success causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnSuccess(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csSuccess | csCancel,
	}
}

// AbortOnError returns a Dependency where any Node Fetch() error causes sibling Dependencies' Fetches to be canceled and propagates the error.
//
// TODO: enrich the error with context before passing it on once Nodes have a description
func AbortOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csAbort,
	}
}

// Dependencies are Nodes that must be Fetched before the given one can be.
type Dependencies []Dependency

// NewNode returns a Node backed by the given function.
//
// The fetch function must not be nil, unless you never intend to Fetch() this Node. It will be called with the errors from the optional dependencies, unless they are fatal, or context.Canceled if they returned CancelFetchSuccess().
//
// The dependencies will be fetched in parallel before fetch() is called and must not contain zero values. Don't introduce circular dependencies or the Fetch will deadlock waiting for itself to finish.
func NewNode(dependencies Dependencies, fetch func(context.Context, []error) error) Node {
	return newNode(dependencies, fetch)
}

func newNode(dependencies Dependencies, fetch func(context.Context, []error) error) *node {
	return &node{
		fetcher:      fetch,
		dependencies: dependencies,
		fetchedChan:  make(chan struct{}),
	}
}

type node struct {
	fetcher      func(context.Context, []error) error
	dependencies Dependencies
	once         sync.Once
	err          error
	iFetched     int32
	fetchedChan  chan struct{}
	start, end   time.Time
}

func (n *node) Fetch(ctx context.Context) error { return n.fetch(ctx, false) }

func (n *node) FetchStrict(ctx context.Context) error { return n.fetch(ctx, true) }

func (n *node) complete(err error) {
	n.err = err
	atomic.StoreInt32(&n.iFetched, 1)
	close(n.fetchedChan)
}

func (n *node) fetch(ctx context.Context, strict bool) error {
	n.once.Do(func() {

		data := precheckDependencies(ctx, n.dependencies)
		if data.abort && !strict {
			n.complete(data.abortErr)
			return
		}
		fetch := func(dep dependencyIndex) error { return dep.node.Fetch(ctx) }
		if strict {
			fetch = func(dep dependencyIndex) error { return dep.node.FetchStrict(ctx) }
			// in strict mode, nothing cancels
			data.nonCancelingDependencies = append(data.nonCancelingDependencies, data.cancelingDependencies...)
			data.cancelingDependencies = nil
		}
		if !data.cancel || strict {
			// fetch dependencies
			switch len(data.cancelingDependencies) {
			case 0: // no cancelling dependencies, just fetch everything
				fetchUncanceled(data.nonCancelingDependencies, fetch, data.errs)
			case 1: // exactly one cancelling dependency - we can do that on this thread
				if abort, abortErr := fetchSingleCancel(data.cancelingDependencies[0], data.nonCancelingDependencies, fetch, data.errs); !data.abort {
					data.abort = abort
					data.abortErr = abortErr
				}
			default: // multiple cancelling dependencies
				if abort, abortErr := fetchMultiCancel(data.cancelingDependencies, data.nonCancelingDependencies, strict, fetch, data.errs); !data.abort {
					data.abort = abort
					data.abortErr = abortErr
				}
			}
		}
		if data.abort {
			n.complete(data.abortErr)
		} else {
			// fetch this node
			n.start = time.Now()
			err := n.fetcher(ctx, data.errs)
			n.end = time.Now()
			n.complete(err)
		}
	})
	return n.err
}

func precheckDependencies(ctx context.Context, dependencies Dependencies) (result struct {
	cancelingDependencies    []dependencyIndex
	nonCancelingDependencies []dependencyIndex
	errs                     []error // result of fetching the dependencies
	abort                    bool
	abortErr                 error // if abort == true, this is the reason for abortion
	cancel                   bool
}) {
	result.cancelingDependencies = make([]dependencyIndex, 0, len(dependencies))
	result.nonCancelingDependencies = make([]dependencyIndex, 0, len(dependencies))
	result.errs = make([]error, len(dependencies))
	// find dependencies that are not yet fetched
	for i, dep := range dependencies {
		if dep.node.fetched() {
			// but dependencies that are already fetched may cause cancellation/abortion
			result.errs[i] = dep.node.Fetch(ctx)
			if dep.onCompletion.Cancel() && dep.onCompletion.Match(result.errs[i]) {
				result.cancel = true
			} else if dep.onCompletion.Abort() && dep.onCompletion.Match(result.errs[i]) {
				result.abortErr = result.errs[i]
				result.abort = true
			}
		} else {
			if dep.onCompletion.Cancel() || dep.onCompletion.Abort() {
				result.cancelingDependencies = append(result.cancelingDependencies, dependencyIndex{Dependency: dep, index: i})
			} else {
				result.nonCancelingDependencies = append(result.nonCancelingDependencies, dependencyIndex{Dependency: dep, index: i})
			}
		}
	}
	if result.cancel {
		for _, dep := range result.cancelingDependencies {
			result.errs[dep.index] = context.Canceled
		}
		for _, dep := range result.nonCancelingDependencies {
			result.errs[dep.index] = context.Canceled
		}
	}
	return
}

func fetchUncanceled(deps []dependencyIndex, fetch func(dependencyIndex) error, errs []error) {
	switch len(deps) {
	case 0: // nothing to do
	case 1:
		errs[deps[0].index] = fetch(deps[0])
	default:
		var wg sync.WaitGroup
		// one calculation can be done on this thread
		wg.Add(len(deps) - 1)
		for i := 1; i < len(deps); i++ {
			go func(i int) {
				errs[deps[i].index] = fetch(deps[i])
				wg.Done()
			}(i)
		}
		errs[deps[0].index] = fetch(deps[0])
		wg.Wait()
	}
}

func fetchSingleCancel(cancelingDep dependencyIndex, nonCancelingDeps []dependencyIndex, fetch func(dependencyIndex) error, errs []error) (abort bool, abortErr error) {
	if len(nonCancelingDeps) == 0 {
		// nothing being canceled
		errs[cancelingDep.index] = fetch(cancelingDep)
		if cancelingDep.onCompletion.Abort() && cancelingDep.onCompletion.Match(errs[cancelingDep.index]) {
			abort = true
			abortErr = errs[cancelingDep.index]
		}
	} else {
		// need to be careful to only set each err once
		once := make([]sync.Once, len(nonCancelingDeps))
		var wg sync.WaitGroup
		wg.Add(len(nonCancelingDeps))
		for i := range nonCancelingDeps {
			go func(i int) {
				err := fetch(nonCancelingDeps[i])
				once[i].Do(func() { errs[nonCancelingDeps[i].index] = err })
				wg.Done()
			}(i)
		}
		errs[cancelingDep.index] = fetch(cancelingDep)
		if cancelingDep.onCompletion.Match(errs[cancelingDep.index]) {
			if cancelingDep.onCompletion.Abort() {
				abort = true
				abortErr = errs[cancelingDep.index]
			} else {
				// canceled, set unfinished dependencies' error
				for i := range nonCancelingDeps {
					once[i].Do(func() { errs[nonCancelingDeps[i].index] = context.Canceled })
				}
			}
		} else {
			// did not cause cancellation, await other dependencies
			wg.Wait()
		}
	}
	return
}

func fetchMultiCancel(cancelingDeps []dependencyIndex, nonCancelingDeps []dependencyIndex, strict bool, fetch func(dependencyIndex) error, errs []error) (abort bool, abortErr error) {
	type doneEvent struct {
		index int
		err   error
	}
	// these channels are chosen large enough to hold all events in case of cancellation/abortion
	doneChan := make(chan doneEvent, len(cancelingDeps)+len(nonCancelingDeps))
	cancelChan := make(chan doneEvent, len(cancelingDeps))
	abortChan := make(chan doneEvent, len(cancelingDeps))
	remainingIndices := map[int]struct{}{}
	var wg sync.WaitGroup
	wg.Add(len(cancelingDeps) + len(nonCancelingDeps))
	for _, dep := range nonCancelingDeps {
		remainingIndices[dep.index] = struct{}{}
		go func(dep dependencyIndex) {
			doneChan <- doneEvent{dep.index, fetch(dep)}
			wg.Done()
		}(dep)
	}
	for _, dep := range cancelingDeps {
		remainingIndices[dep.index] = struct{}{}
		go func(dep dependencyIndex) {
			ev := doneEvent{dep.index, fetch(dep)}
			if dep.onCompletion.Abort() && dep.onCompletion.Match(ev.err) {
				abortChan <- ev
			} else if dep.onCompletion.Cancel() && dep.onCompletion.Match(ev.err) {
				cancelChan <- ev
			} else {
				doneChan <- ev
			}
			wg.Done()
		}(dep)
	}
	for len(remainingIndices) > 0 {
		select {
		case ev := <-doneChan:
			errs[ev.index] = ev.err
			delete(remainingIndices, ev.index)
		case ev := <-cancelChan:
			errs[ev.index] = ev.err
			delete(remainingIndices, ev.index)
			for i := range remainingIndices {
				errs[i] = context.Canceled
			}
			remainingIndices = nil
		case ev := <-abortChan:
			abort = true
			abortErr = ev.err
			remainingIndices = nil
		}
	}
	if strict {
		wg.Wait()
	}
	return
}

func (n *node) fetched() bool {
	fetched := atomic.LoadInt32(&n.iFetched) != 0
	if fetched {
		// atomic reads are no write barriers, so we still need to synchronize before we can access the result
		<-n.fetchedChan
	}
	return fetched
}

func (n *node) noUserImplementations() {}

type completionStrategy int

func (cs completionStrategy) Match(err error) bool {
	return cs&csSuccess != 0 && err == nil ||
		cs&csError != 0 && err != nil
}

func (cs completionStrategy) Cancel() bool {
	return cs&csCancel != 0
}

func (cs completionStrategy) Abort() bool {
	return cs&csAbort != 0
}

const (
	csError completionStrategy = 1 << iota
	csSuccess
	csCancel
	csAbort
)
<commit_msg>remove strict special case in fetchMultiCancel (fix #6)<commit_after>package lazyexp

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

// A Node represents a blocking calculation. Create one using NewNode().
//
// You'll probably want to embed a Node in your struct that will contain the result.
type Node interface {
	Fetch(context.Context) error
	// FetchStrict never cancels siblings on error and can be useful for debugging, but should generally be avoided.
	FetchStrict(context.Context) error
	fetched() bool
	noUserImplementations()
}

// A Dependency is a Node whose result another Node depends on.
//
// Created using ContinueOnError(), CancelOnError(), CancelOnCompletion() and AbortOnError().
type Dependency struct {
	node         Node
	onCompletion completionStrategy
}

type dependencyIndex struct {
	Dependency
	index int
}

// ContinueOnError returns a Dependency where any Node Fetch() errors are simply passed on without affecting any other Nodes.
func ContinueOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: 0,
	}
}

// CancelOnError returns a Dependency where any Node Fetch() error causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csCancel,
	}
}

// CancelOnCompletion returns a Dependency that upon Node Fetch() completion causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnCompletion(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csSuccess | csCancel,
	}
}

// CancelOnSuccess returns a Dependency that upon Node Fetch() success causes sibling Dependencies' Fetches to be canceled, but still continues with the fetch function.
func CancelOnSuccess(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csSuccess | csCancel,
	}
}

// AbortOnError returns a Dependency where any Node Fetch() error causes sibling Dependencies' Fetches to be canceled and propagates the error.
//
// TODO: enrich the error with context before passing it on once Nodes have a description
func AbortOnError(node Node) Dependency {
	return Dependency{
		node:         node,
		onCompletion: csError | csAbort,
	}
}

// Dependencies are Nodes that must be Fetched before the given one can be.
type Dependencies []Dependency

// NewNode returns a Node backed by the given function.
//
// The fetch function must not be nil, unless you never intend to Fetch() this Node. It will be called with the errors from the optional dependencies, unless they are fatal, or context.Canceled if they returned CancelFetchSuccess().
//
// The dependencies will be fetched in parallel before fetch() is called and must not contain zero values. Don't introduce circular dependencies or the Fetch will deadlock waiting for itself to finish.
func NewNode(dependencies Dependencies, fetch func(context.Context, []error) error) Node {
	return newNode(dependencies, fetch)
}

func newNode(dependencies Dependencies, fetch func(context.Context, []error) error) *node {
	return &node{
		fetcher:      fetch,
		dependencies: dependencies,
		fetchedChan:  make(chan struct{}),
	}
}

type node struct {
	fetcher      func(context.Context, []error) error
	dependencies Dependencies
	once         sync.Once
	err          error
	iFetched     int32
	fetchedChan  chan struct{}
	start, end   time.Time
}

func (n *node) Fetch(ctx context.Context) error { return n.fetch(ctx, false) }

func (n *node) FetchStrict(ctx context.Context) error { return n.fetch(ctx, true) }

func (n *node) complete(err error) {
	n.err = err
	atomic.StoreInt32(&n.iFetched, 1)
	close(n.fetchedChan)
}

func (n *node) fetch(ctx context.Context, strict bool) error {
	n.once.Do(func() {

		data := precheckDependencies(ctx, n.dependencies)
		if data.abort && !strict {
			n.complete(data.abortErr)
			return
		}
		fetch := func(dep dependencyIndex) error { return dep.node.Fetch(ctx) }
		if strict {
			fetch = func(dep dependencyIndex) error { return dep.node.FetchStrict(ctx) }
			// in strict mode, nothing cancels
			data.nonCancelingDependencies = append(data.nonCancelingDependencies, data.cancelingDependencies...)
			data.cancelingDependencies = nil
		}
		if !data.cancel || strict {
			// fetch dependencies
			switch len(data.cancelingDependencies) {
			case 0: // no cancelling dependencies, just fetch everything
				fetchUncanceled(data.nonCancelingDependencies, fetch, data.errs)
			case 1: // exactly one cancelling dependency - we can do that on this thread
				if abort, abortErr := fetchSingleCancel(data.cancelingDependencies[0], data.nonCancelingDependencies, fetch, data.errs); !data.abort {
					data.abort = abort
					data.abortErr = abortErr
				}
			default: // multiple cancelling dependencies
				if abort, abortErr := fetchMultiCancel(data.cancelingDependencies, data.nonCancelingDependencies, fetch, data.errs); !data.abort {
					data.abort = abort
					data.abortErr = abortErr
				}
			}
		}
		if data.abort {
			n.complete(data.abortErr)
		} else {
			// fetch this node
			n.start = time.Now()
			err := n.fetcher(ctx, data.errs)
			n.end = time.Now()
			n.complete(err)
		}
	})
	return n.err
}

func precheckDependencies(ctx context.Context, dependencies Dependencies) (result struct {
	cancelingDependencies    []dependencyIndex
	nonCancelingDependencies []dependencyIndex
	errs                     []error // result of fetching the dependencies
	abort                    bool
	abortErr                 error // if abort == true, this is the reason for abortion
	cancel                   bool
}) {
	result.cancelingDependencies = make([]dependencyIndex, 0, len(dependencies))
	result.nonCancelingDependencies = make([]dependencyIndex, 0, len(dependencies))
	result.errs = make([]error, len(dependencies))
	// find dependencies that are not yet fetched
	for i, dep := range dependencies {
		if dep.node.fetched() {
			// but dependencies that are already fetched may cause cancellation/abortion
			result.errs[i] = dep.node.Fetch(ctx)
			if dep.onCompletion.Cancel() && dep.onCompletion.Match(result.errs[i]) {
				result.cancel = true
			} else if dep.onCompletion.Abort() && dep.onCompletion.Match(result.errs[i]) {
				result.abortErr = result.errs[i]
				result.abort = true
			}
		} else {
			if dep.onCompletion.Cancel() || dep.onCompletion.Abort() {
				result.cancelingDependencies = append(result.cancelingDependencies, dependencyIndex{Dependency: dep, index: i})
			} else {
				result.nonCancelingDependencies = append(result.nonCancelingDependencies, dependencyIndex{Dependency: dep, index: i})
			}
		}
	}
	if result.cancel {
		for _, dep := range result.cancelingDependencies {
			result.errs[dep.index] = context.Canceled
		}
		for _, dep := range result.nonCancelingDependencies {
			result.errs[dep.index] = context.Canceled
		}
	}
	return
}

func fetchUncanceled(deps []dependencyIndex, fetch func(dependencyIndex) error, errs []error) {
	switch len(deps) {
	case 0: // nothing to do
	case 1:
		errs[deps[0].index] = fetch(deps[0])
	default:
		var wg sync.WaitGroup
		// one calculation can be done on this thread
		wg.Add(len(deps) - 1)
		for i := 1; i < len(deps); i++ {
			go func(i int) {
				errs[deps[i].index] = fetch(deps[i])
				wg.Done()
			}(i)
		}
		errs[deps[0].index] = fetch(deps[0])
		wg.Wait()
	}
}

func fetchSingleCancel(cancelingDep dependencyIndex, nonCancelingDeps []dependencyIndex, fetch func(dependencyIndex) error, errs []error) (abort bool, abortErr error) {
	if len(nonCancelingDeps) == 0 {
		// nothing being canceled
		errs[cancelingDep.index] = fetch(cancelingDep)
		if cancelingDep.onCompletion.Abort() && cancelingDep.onCompletion.Match(errs[cancelingDep.index]) {
			abort = true
			abortErr = errs[cancelingDep.index]
		}
	} else {
		// need to be careful to only set each err once
		once := make([]sync.Once, len(nonCancelingDeps))
		var wg sync.WaitGroup
		wg.Add(len(nonCancelingDeps))
		for i := range nonCancelingDeps {
			go func(i int) {
				err := fetch(nonCancelingDeps[i])
				once[i].Do(func() { errs[nonCancelingDeps[i].index] = err })
				wg.Done()
			}(i)
		}
		errs[cancelingDep.index] = fetch(cancelingDep)
		if cancelingDep.onCompletion.Match(errs[cancelingDep.index]) {
			if cancelingDep.onCompletion.Abort() {
				abort = true
				abortErr = errs[cancelingDep.index]
			} else {
				// canceled, set unfinished dependencies' error
				for i := range nonCancelingDeps {
					once[i].Do(func() { errs[nonCancelingDeps[i].index] = context.Canceled })
				}
			}
		} else {
			// did not cause cancellation, await other dependencies
			wg.Wait()
		}
	}
	return
}

func fetchMultiCancel(cancelingDeps []dependencyIndex, nonCancelingDeps []dependencyIndex, fetch func(dependencyIndex) error, errs []error) (abort bool, abortErr error) {
	type doneEvent struct {
		index int
		err   error
	}
	// these channels are chosen large enough to hold all events in case of cancellation/abortion
	doneChan := make(chan doneEvent, len(cancelingDeps)+len(nonCancelingDeps))
	cancelChan := make(chan doneEvent, len(cancelingDeps))
	abortChan := make(chan doneEvent, len(cancelingDeps))
	remainingIndices := map[int]struct{}{}
	for _, dep := range nonCancelingDeps {
		remainingIndices[dep.index] = struct{}{}
		go func(dep dependencyIndex) {
			doneChan <- doneEvent{dep.index, fetch(dep)}
		}(dep)
	}
	for _, dep := range cancelingDeps {
		remainingIndices[dep.index] = struct{}{}
		go func(dep dependencyIndex) {
			ev := doneEvent{dep.index, fetch(dep)}
			if dep.onCompletion.Abort() && dep.onCompletion.Match(ev.err) {
				abortChan <- ev
			} else if dep.onCompletion.Cancel() && dep.onCompletion.Match(ev.err) {
				cancelChan <- ev
			} else {
				doneChan <- ev
			}
		}(dep)
	}
	for len(remainingIndices) > 0 {
		select {
		case ev := <-doneChan:
			errs[ev.index] = ev.err
			delete(remainingIndices, ev.index)
		case ev := <-cancelChan:
			errs[ev.index] = ev.err
			delete(remainingIndices, ev.index)
			for i := range remainingIndices {
				errs[i] = context.Canceled
			}
			remainingIndices = nil
		case ev := <-abortChan:
			abort = true
			abortErr = ev.err
			remainingIndices = nil
		}
	}
	return
}

func (n *node) fetched() bool {
	fetched := atomic.LoadInt32(&n.iFetched) != 0
	if fetched {
		// atomic reads are no write barriers, so we still need to synchronize before we can access the result
		<-n.fetchedChan
	}
	return fetched
}

func (n *node) noUserImplementations() {}

type completionStrategy int

func (cs completionStrategy) Match(err error) bool {
	return cs&csSuccess != 0 && err == nil ||
		cs&csError != 0 && err != nil
}

func (cs completionStrategy) Cancel() bool {
	return cs&csCancel != 0
}

func (cs completionStrategy) Abort() bool {
	return cs&csAbort != 0
}

const (
	csError completionStrategy = 1 << iota
	csSuccess
	csCancel
	csAbort
)
<|endoftext|>


<commit_before>package apps

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

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = random_name.CATSRandomName("APP")
	})

	AfterEach(func() {
		app_helpers.AppReport(appName, DEFAULT_TIMEOUT)

		Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)

				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				app2 = random_name.CATSRandomName("APP")
				Expect(cf.Cf("push", app2, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(app2)
				Expect(cf.Cf("start", app2).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		Context("multiple instances", func() {
			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)
				Expect(cf.Cf("scale", appName, "-i", "2").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("is able to start all instances", func() {
				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}).Should(Say("#0   running"))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(CF_PUSH_TIMEOUT)
				}).Should(Say("#1   running"))
			})
		})

		It("makes system environment variables available", func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"\[(\{\\"external\\":[0-9]+,\\"internal\\":[0-9]+\},?)+\]"`))
		})

		It("generates an app usage 'started' event", func() {
			Expect(cf.Cf(
				"push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT),
			).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			It("makes the app reachable again", func() {
				Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)
			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<commit_msg>Make the eventually block wait for longer than 1 second before giving up trying to check whether the app in lifecycle_test has started<commit_after>package apps

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

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name"
)

type AppUsageEvent struct {
	Entity struct {
		AppName       string `json:"app_name"`
		State         string `json:"state"`
		BuildpackName string `json:"buildpack_name"`
		BuildpackGuid string `json:"buildpack_guid"`
	} `json:"entity"`
}

type AppUsageEvents struct {
	Resources []AppUsageEvent `struct:"resources"`
}

func lastAppUsageEvent(appName string, state string) (bool, AppUsageEvent) {
	var response AppUsageEvents
	cf.AsUser(context.AdminUserContext(), DEFAULT_TIMEOUT, func() {
		cf.ApiRequest("GET", "/v2/app_usage_events?order-direction=desc&page=1&results-per-page=150", &response, DEFAULT_TIMEOUT)
	})

	for _, event := range response.Resources {
		if event.Entity.AppName == appName && event.Entity.State == state {
			return true, event
		}
	}

	return false, AppUsageEvent{}
}

var _ = Describe("Application Lifecycle", func() {
	var appName string

	BeforeEach(func() {
		appName = random_name.CATSRandomName("APP")
	})

	AfterEach(func() {
		app_helpers.AppReport(appName, DEFAULT_TIMEOUT)

		Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Describe("pushing", func() {
		It("makes the app reachable via its bound route", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
		})

		Describe("Context path", func() {
			var app2 string
			var path = "/imposter_dora"

			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)

				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				app2 = random_name.CATSRandomName("APP")
				Expect(cf.Cf("push", app2, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(app2)
				Expect(cf.Cf("start", app2).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			})

			AfterEach(func() {
				Expect(cf.Cf("delete", app2, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("makes another app available via same host and domain, but different path", func() {
				getRoutePath := fmt.Sprintf("/v2/routes?q=host:%s", appName)
				routeBody := cf.Cf("curl", getRoutePath).Wait(DEFAULT_TIMEOUT).Out.Contents()
				var routeJSON struct {
					Resources []struct {
						Entity struct {
							SpaceGuid  string `json:"space_guid"`
							DomainGuid string `json:"domain_guid"`
						} `json:"entity"`
					} `json:"resources"`
				}
				json.Unmarshal([]byte(routeBody), &routeJSON)

				spaceGuid := routeJSON.Resources[0].Entity.SpaceGuid
				domainGuid := routeJSON.Resources[0].Entity.DomainGuid
				appGuid := cf.Cf("app", app2, "--guid").Wait(DEFAULT_TIMEOUT).Out.Contents()

				jsonBody := "{\"host\":\"" + appName + "\", \"path\":\"" + path + "\", \"domain_guid\":\"" + domainGuid + "\",\"space_guid\":\"" + spaceGuid + "\"}"
				routePostResponseBody := cf.Cf("curl", "/v2/routes", "-X", "POST", "-d", jsonBody).Wait(CF_PUSH_TIMEOUT).Out.Contents()

				var routeResponseJSON struct {
					Metadata struct {
						Guid string `json:"guid"`
					} `json:"metadata"`
				}
				json.Unmarshal([]byte(routePostResponseBody), &routeResponseJSON)
				routeGuid := routeResponseJSON.Metadata.Guid

				Expect(cf.Cf("curl", "/v2/apps/"+strings.TrimSpace(string(appGuid))+"/routes/"+string(routeGuid), "-X", "PUT").Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

				Eventually(func() string {
					return helpers.CurlApp(appName, path)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
			})
		})

		Context("multiple instances", func() {
			BeforeEach(func() {
				Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
				app_helpers.SetBackend(appName)
				Expect(cf.Cf("scale", appName, "-i", "2").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			})

			It("is able to start all instances", func() {
				Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#0   running"))

				Eventually(func() *Session {
					return cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
				}, DEFAULT_TIMEOUT).Should(Say("#1   running"))
			})
		})

		It("makes system environment variables available", func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			var envOutput string
			Eventually(func() string {
				envOutput = helpers.CurlApp(appName, "/env")
				return envOutput
			}, DEFAULT_TIMEOUT).Should(ContainSubstring(`"CF_INSTANCE_INDEX"=>"0"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_IP"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORT"=>"[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_ADDR"=>"[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`))
			Expect(envOutput).To(MatchRegexp(`"CF_INSTANCE_PORTS"=>"\[(\{\\"external\\":[0-9]+,\\"internal\\":[0-9]+\},?)+\]"`))
		})

		It("generates an app usage 'started' event", func() {
			Expect(cf.Cf(
				"push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT),
			).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STARTED")
			Expect(found).To(BeTrue())
		})

		It("generates an app usage 'buildpack_set' event", func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			found, matchingEvent := lastAppUsageEvent(appName, "BUILDPACK_SET")

			Expect(found).To(BeTrue())
			Expect(matchingEvent.Entity.BuildpackName).To(Equal("ruby_buildpack"))
			Expect(matchingEvent.Entity.BuildpackGuid).ToNot(BeZero())
		})
	})

	Describe("stopping", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})

		Describe("and then starting", func() {
			It("makes the app reachable again", func() {
				Expect(cf.Cf("stop", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Expect(cf.Cf("start", appName).Wait(DEFAULT_TIMEOUT)).To(Exit(0))

				Eventually(func() string {
					return helpers.CurlAppRoot(appName)
				}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))
			})
		})
	})

	Describe("updating", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push",
				appName,
				"--no-start",
				"-b", config.RubyBuildpackName,
				"-m", DEFAULT_MEMORY_LIMIT,
				"-p", assets.NewAssets().Dora,
				"-d", config.AppsDomain).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("is reflected through another push", func() {
			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hi, I'm Dora!"))

			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().HelloWorld, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)
			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("Hello, world!"))
		})
	})

	Describe("deleting", func() {
		BeforeEach(func() {
			Expect(cf.Cf("push", appName, "--no-start", "-b", config.RubyBuildpackName, "-m", DEFAULT_MEMORY_LIMIT, "-p", assets.NewAssets().Dora, "-d", config.AppsDomain).Wait(DEFAULT_TIMEOUT)).To(Exit(0))
			app_helpers.SetBackend(appName)

			Expect(cf.Cf("start", appName).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
		})

		It("removes the application", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			app := cf.Cf("app", appName).Wait(DEFAULT_TIMEOUT)
			Expect(app).To(Exit(1))
			Expect(app).To(Say("not found"))
		})

		It("makes the app unreachable", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			Eventually(func() string {
				return helpers.CurlAppRoot(appName)
			}, DEFAULT_TIMEOUT).Should(ContainSubstring("404"))
		})

		It("generates an app usage 'stopped' event", func() {
			Expect(cf.Cf("delete", appName, "-f", "-r").Wait(DEFAULT_TIMEOUT)).To(Exit(0))

			found, _ := lastAppUsageEvent(appName, "STOPPED")
			Expect(found).To(BeTrue())
		})
	})
})
<|endoftext|>


<commit_before>// Stocks daily price update
// Usage: go run orca.go [csv_file]
//			 [update_time]
//
// update_time := format HH:mm UTC time
// csv_file := [symbol,price,amount,isThaiStock]
// see sample_input.csv for more info

// TODO: Support ErrorMessage handling from API call

package main

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/jasonlvhit/gocron"
	"github.com/mailgun/mailgun-go"
)

const MAILGUN_URL = "___URL___"
const MAILGUN_PRIVATE_KEY = "___API_PRIVATE_KEY___"
const MAILGUN_PUBLIC_KEY = "___API_PUBLIC_KEY___"
const USER_EMAIL = "___EMAIL___"

const QUOTE_BASE_URL = "https://finance.yahoo.com/webservice/v1/symbols/"
const QUOTE_TH_STOCK = ".BK"
const QUOTE_URL_SUFFIX = "/quote?format=json"
const QUOTE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0.1; MotoG3 Build/MPI24.107-55) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537.36"

const DEBUG = true

var user_stocks map[string]*UserStock
var update_time string

type UserStock struct {
	Shares []*Share
	IsThai bool
}

type Share struct {
	Price  float64
	Amount int
}

type Response struct {
	Quotes []*Quote
}

type Quote struct {
	Symbol    string
	LastPrice float64
}

// DEBUG: Print message to console.
func debugMessage(message ...interface{}) {
	if DEBUG {
		fmt.Println(message)
	}
}

// Check if an error has occured.
func checkError(e error) {
	if e != nil {
		fmt.Printf("Error: %s\n", e)
		os.Exit(1)
	}
}

// Override: flatten structure of response into Quote.
func (response *Response) UnmarshalJSON(b []byte) error {
	var f interface{}
	json.Unmarshal(b, &f)
	m := f.(map[string]interface{})
	n := m["list"].(map[string]interface{})
	v := n["resources"]
	resources := v.([]interface{})

	for _, resource := range resources {
		r := resource.(map[string]interface{})["resource"].(map[string]interface{})
		stock := r["fields"].(map[string]interface{})
		symbol := strings.ToUpper(stock["symbol"].(string))
		price, _ := strconv.ParseFloat(stock["price"].(string), 64)
		response.Quotes = append(response.Quotes, &Quote{symbol, price})
	}

	return nil
}

// Parse input data into user_stocks.
func populateUserStocks(csv_file string) {
	debugMessage("populateUserStocks csv: ", csv_file)

	file, err := os.Open(csv_file)
	checkError(err)

	r := csv.NewReader(file)
	records, err := r.ReadAll()
	checkError(err)

	debugMessage("records:", records)

	for i := 1; i < len(records); i++ {
		record := records[i]
		symbol := strings.ToUpper(record[0])
		price, err := strconv.ParseFloat(record[1], 64)
		checkError(err)
		amount, err := strconv.Atoi(record[2])
		checkError(err)
		isBKKStock, err := strconv.ParseBool(record[3])
		checkError(err)

		stock := user_stocks[symbol]
		share := &Share{price, amount}

		if stock == nil {
			stock = &UserStock{IsThai: isBKKStock, Shares: []*Share{share}}
			user_stocks[symbol] = stock
		} else {
			stock.Shares = append(stock.Shares, share)
		}
	}

	debugMessage("user_stocks:", user_stocks)
}

// Fetch updated stocks and send email.
func fetchAndSendUpdate() {
	update := getUserStockPriceResult()
	debugMessage("\n\nOutput result:", update)

	if DEBUG {
		return
	}

	mg := mailgun.NewMailgun(
		MAILGUN_URL,
		MAILGUN_PRIVATE_KEY,
		MAILGUN_PUBLIC_KEY,
	)

	email := mg.NewMessage(
		"ORCA PROJECT <orca@"+MAILGUN_URL+">",
		"ORCA Daily Update",
		update,
		USER_EMAIL,
	)

	_, _, err := mg.Send(email)
	checkError(err)

	fmt.Println("[", update_time, "] Sent an update to", USER_EMAIL)
}

// Return a well formatted result of current market prices.
func getUserStockPriceResult() string {
	investment := 0.0
	returnInvestment := 0.0
	result := "Stocks Update - " + time.Now().Format("Mon Jan 2") + "\n"
	result += "-----------------------------\n"
	result += fmt.Sprintf("%-*s %-*s\n", 10, "Symbol", 15, "M.Price")

	quotes := fetchStocksUpdate()
	for symbol, stock := range user_stocks {
		price := quotes[symbol].LastPrice
		result += fmt.Sprintf("%-*s %-*.2f\n", 10, symbol, 15, price)
		for _, share := range stock.Shares {
			investment += share.Price * float64(share.Amount)
			returnInvestment += price * float64(share.Amount)
		}
	}

	result += "-----------------------------\n"
	result += "Total amount invested: " + fmt.Sprintf("%.2f\n", investment)
	result += "Current amount: " + fmt.Sprintf("%.2f\n", returnInvestment)
	result += "Gain/Loss = " + fmt.Sprintf("%.2f\n", returnInvestment-investment)
	return result
}

// Return market prices of user's stocks.
func fetchStocksUpdate() map[string]*Quote {
	var symbols []string
	for k, v := range user_stocks {
		s := k
		if v.IsThai {
			s += QUOTE_TH_STOCK
		}
		symbols = append(symbols, s)
	}
	debugMessage("stocks to fetch:", symbols)
	return fetchQuoteForStocks(strings.Join(symbols, ","))
}

// Return market price of stocks.
// Args: codes - list of comma separated symbols (eg. AAPL,MSFT)
func fetchQuoteForStocks(codes string) map[string]*Quote {
	url := QUOTE_BASE_URL + codes + QUOTE_URL_SUFFIX
	debugMessage("url:", url)

	httpResponse := getHttpResponse(url)
	debugMessage("http response:", string(httpResponse))

	var r Response
	err := json.Unmarshal(httpResponse, &r)
	checkError(err)

	quotes := make(map[string]*Quote)
	for _, q := range r.Quotes {
		// Remove country specific annotation.
		symbol := strings.Split(q.Symbol, ".")[0]
		quotes[symbol] = q
	}

	return quotes
}

// Return byte array response of an API call.
func getHttpResponse(url string) []byte {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	checkError(err)

	req.Header.Set("User-Agent", QUOTE_USER_AGENT)
	resp, err := client.Do(req)
	checkError(err)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	checkError(err)
	return body
}

func main() {
	if len(os.Args) < 3 {
		fmt.Printf("Usage: ./orca [csv_file] [update_time]\n")
		os.Exit(1)
	}

	fmt.Println("[", time.Now().UTC().Format("15:04"), "] Started running..")

	user_stocks = make(map[string]*UserStock)
	populateUserStocks(os.Args[1])
	update_time = os.Args[2]

	gocron.Remove(fetchAndSendUpdate)
	gocron.Clear()

	if DEBUG {
		fetchAndSendUpdate()
	} else {
		gocron.ChangeLoc(time.UTC)
		gocron.Every(1).Day().At(update_time).Do(fetchAndSendUpdate)
		<-gocron.Start()
	}
}
<commit_msg>Separated output into THB and USD sections, improved currency formatting<commit_after>// Stocks daily price update
// Usage: go run orca.go [csv_file]
//			 [update_time]
//
// update_time := format HH:mm UTC time
// csv_file := [symbol,price,amount,isThaiStock]
// see sample_input.csv for more info

// TODO: Support ErrorMessage handling from API call

package main

import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/jasonlvhit/gocron"
	"github.com/leekchan/accounting"
	"github.com/mailgun/mailgun-go"
)

const MAILGUN_URL = "___URL___"
const MAILGUN_PRIVATE_KEY = "___API_PRIVATE_KEY___"
const MAILGUN_PUBLIC_KEY = "___API_PUBLIC_KEY___"
const USER_EMAIL = "___EMAIL___"

const QUOTE_BASE_URL = "https://finance.yahoo.com/webservice/v1/symbols/"
const QUOTE_TH_STOCK = ".BK"
const QUOTE_URL_SUFFIX = "/quote?format=json"
const QUOTE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0.1; MotoG3 Build/MPI24.107-55) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.81 Mobile Safari/537.36"

const DEBUG = true

var user_stocks map[string]*UserStock
var update_time string

type UserStock struct {
	Shares []*Share
	IsThai bool
}

type Share struct {
	Price  float64
	Amount int
}

type Response struct {
	Quotes []*Quote
}

type Quote struct {
	Symbol    string
	LastPrice float64
}

// DEBUG: Print message to console.
func debugMessage(message ...interface{}) {
	if DEBUG {
		fmt.Println(message)
	}
}

// Check if an error has occured.
func checkError(e error) {
	if e != nil {
		fmt.Printf("Error: %s\n", e)
		os.Exit(1)
	}
}

// Override: flatten structure of response into Quote.
func (response *Response) UnmarshalJSON(b []byte) error {
	var f interface{}
	json.Unmarshal(b, &f)
	m := f.(map[string]interface{})
	n := m["list"].(map[string]interface{})
	v := n["resources"]
	resources := v.([]interface{})

	for _, resource := range resources {
		r := resource.(map[string]interface{})["resource"].(map[string]interface{})
		stock := r["fields"].(map[string]interface{})
		symbol := strings.ToUpper(stock["symbol"].(string))
		price, _ := strconv.ParseFloat(stock["price"].(string), 64)
		response.Quotes = append(response.Quotes, &Quote{symbol, price})
	}

	return nil
}

// Parse input data into user_stocks.
func populateUserStocks(csv_file string) {
	debugMessage("populateUserStocks csv: ", csv_file)

	file, err := os.Open(csv_file)
	checkError(err)

	r := csv.NewReader(file)
	records, err := r.ReadAll()
	checkError(err)

	debugMessage("records:", records)

	for i := 1; i < len(records); i++ {
		record := records[i]
		symbol := strings.ToUpper(record[0])
		price, err := strconv.ParseFloat(record[1], 64)
		checkError(err)
		amount, err := strconv.Atoi(record[2])
		checkError(err)
		isBKKStock, err := strconv.ParseBool(record[3])
		checkError(err)

		stock := user_stocks[symbol]
		share := &Share{price, amount}

		if stock == nil {
			stock = &UserStock{IsThai: isBKKStock, Shares: []*Share{share}}
			user_stocks[symbol] = stock
		} else {
			stock.Shares = append(stock.Shares, share)
		}
	}

	debugMessage("user_stocks:", user_stocks)
}

// Fetch updated stocks and send email.
func fetchAndSendUpdate() {
	update := getUserStockPriceResult()
	debugMessage("\n\nOutput result:\n", update)

	if DEBUG {
		return
	}

	mg := mailgun.NewMailgun(
		MAILGUN_URL,
		MAILGUN_PRIVATE_KEY,
		MAILGUN_PUBLIC_KEY,
	)

	email := mg.NewMessage(
		"ORCA PROJECT <orca@"+MAILGUN_URL+">",
		"ORCA Daily Update",
		update,
		USER_EMAIL,
	)

	_, _, err := mg.Send(email)
	checkError(err)

	fmt.Println("[", update_time, "] Sent an update to", USER_EMAIL)
}

// Return a well formatted result of current market prices.
func getUserStockPriceResult() string {
	investmentUSD := 0.0
	investmentTHB := 0.0
	returnInvestmentUSD := 0.0
	returnInvestmentTHB := 0.0
	result := "Stocks Update - " + time.Now().Format("Mon Jan 2") + "\n"
	result += "-----------------------------\n"
	result += fmt.Sprintf("%-*s %-*s\n", 10, "Symbol", 15, "M.Price")

	quotes := fetchStocksUpdate()
	for symbol, stock := range user_stocks {
		price := quotes[symbol].LastPrice
		result += fmt.Sprintf("%-*s %-*.2f\n", 10, symbol, 15, price)
		for _, share := range stock.Shares {
			if stock.IsThai {
				investmentTHB += share.Price * float64(share.Amount)
				returnInvestmentTHB += price * float64(share.Amount)
			} else {
				investmentUSD += share.Price * float64(share.Amount)
				returnInvestmentUSD += price * float64(share.Amount)
			}
		}
	}

	if investmentUSD > 0.0 {
		ac := accounting.Accounting{Symbol: "$", Precision: 2}
		result += "-----------------------------\n"
		result += "USD Summary\n"
		result += "Total amount invested: " + ac.FormatMoney(investmentUSD) + "\n"
		result += "Current investments: " + ac.FormatMoney(returnInvestmentUSD) + "\n"
		result += "Gain/Loss = " + ac.FormatMoney(returnInvestmentUSD-investmentUSD) + "\n"
	}

	if investmentTHB > 0.0 {
		ac := accounting.Accounting{Symbol: "฿", Precision: 2}
		result += "-----------------------------\n"
		result += "THB Summary\n"
		result += "Total amount invested: " + ac.FormatMoney(investmentTHB) + "\n"
		result += "Current investments: " + ac.FormatMoney(returnInvestmentTHB) + "\n"
		result += "Gain/Loss = " + ac.FormatMoney(returnInvestmentTHB-investmentTHB) + "\n"
	}

	result += "-----------------------------"
	return result
}

// Return market prices of user's stocks.
func fetchStocksUpdate() map[string]*Quote {
	var symbols []string
	for k, v := range user_stocks {
		s := k
		if v.IsThai {
			s += QUOTE_TH_STOCK
		}
		symbols = append(symbols, s)
	}
	debugMessage("stocks to fetch:", symbols)
	return fetchQuoteForStocks(strings.Join(symbols, ","))
}

// Return market price of stocks.
// Args: codes - list of comma separated symbols (eg. AAPL,MSFT)
func fetchQuoteForStocks(codes string) map[string]*Quote {
	url := QUOTE_BASE_URL + codes + QUOTE_URL_SUFFIX
	debugMessage("url:", url)

	httpResponse := getHttpResponse(url)
	debugMessage("http response:", string(httpResponse))

	var r Response
	err := json.Unmarshal(httpResponse, &r)
	checkError(err)

	quotes := make(map[string]*Quote)
	for _, q := range r.Quotes {
		// Remove country specific annotation.
		symbol := strings.Split(q.Symbol, ".")[0]
		quotes[symbol] = q
	}

	return quotes
}

// Return byte array response of an API call.
func getHttpResponse(url string) []byte {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	checkError(err)

	req.Header.Set("User-Agent", QUOTE_USER_AGENT)
	resp, err := client.Do(req)
	checkError(err)

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	checkError(err)
	return body
}

func main() {
	if len(os.Args) < 3 {
		fmt.Printf("Usage: ./orca [csv_file] [update_time]\n")
		os.Exit(1)
	}

	fmt.Println("[", time.Now().UTC().Format("15:04"), "] Started running..")

	user_stocks = make(map[string]*UserStock)
	populateUserStocks(os.Args[1])
	update_time = os.Args[2]

	gocron.Remove(fetchAndSendUpdate)
	gocron.Clear()

	if DEBUG {
		fetchAndSendUpdate()
	} else {
		gocron.ChangeLoc(time.UTC)
		gocron.Every(1).Day().At(update_time).Do(fetchAndSendUpdate)
		<-gocron.Start()
	}
}
<|endoftext|>


<commit_before>package gapi

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

type Org struct {
	Id   int64
	Name string
}

type OrgUser struct {
	OrgId   int64
	UserId  int64
	Email   string
	Login   string
	Role    string
}

func (c *Client) Orgs() ([]Org, error) {
	orgs := make([]Org, 0)

	req, err := c.newRequest("GET", "/api/orgs/", nil)
	if err != nil {
		return orgs, err
	}
	resp, err := c.Do(req)
	if err != nil {
		return orgs, err
	}
	if resp.StatusCode != 200 {
		return orgs, errors.New(resp.Status)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return orgs, err
	}
	err = json.Unmarshal(data, &orgs)
	return orgs, err
}

func (c *Client) NewOrg(name string) error {
	settings := map[string]string{
		"name": name,
	}
	data, err := json.Marshal(settings)
	req, err := c.newRequest("POST", "/api/orgs", bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}

func (c *Client) DeleteOrg(id int64) error {
	req, err := c.newRequest("DELETE", fmt.Sprintf("/api/orgs/%d", id), nil)
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}

func (c *Client) OrgUsers(id int64) ([]OrgUser, error) {
	users := make([]OrgUser, 0)
	req, err := c.newRequest("GET", fmt.Sprintf("/api/orgs/%d/users", id), nil)
	if err != nil {
		return users, err
	}
	resp, err := c.Do(req)
	if err != nil {
		return users, err
	}
	if resp.StatusCode != 200 {
		return users, errors.New(resp.Status)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return users, err
	}
	err = json.Unmarshal(data, &users)
	if err != nil {
		return users, err
	}
	return users, err
}
<commit_msg>Add methods to add/update/delete org users<commit_after>package gapi

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

type Org struct {
	Id   int64
	Name string
}

type OrgUser struct {
	OrgId   int64
	UserId  int64
	Email   string
	Login   string
	Role    string
}

func (c *Client) Orgs() ([]Org, error) {
	orgs := make([]Org, 0)

	req, err := c.newRequest("GET", "/api/orgs/", nil)
	if err != nil {
		return orgs, err
	}
	resp, err := c.Do(req)
	if err != nil {
		return orgs, err
	}
	if resp.StatusCode != 200 {
		return orgs, errors.New(resp.Status)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return orgs, err
	}
	err = json.Unmarshal(data, &orgs)
	return orgs, err
}

func (c *Client) NewOrg(name string) error {
	settings := map[string]string{
		"name": name,
	}
	data, err := json.Marshal(settings)
	req, err := c.newRequest("POST", "/api/orgs", bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}

func (c *Client) DeleteOrg(id int64) error {
	req, err := c.newRequest("DELETE", fmt.Sprintf("/api/orgs/%d", id), nil)
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}

func (c *Client) OrgUsers(id int64) ([]OrgUser, error) {
	users := make([]OrgUser, 0)
	req, err := c.newRequest("GET", fmt.Sprintf("/api/orgs/%d/users", id), nil)
	if err != nil {
		return users, err
	}
	resp, err := c.Do(req)
	if err != nil {
		return users, err
	}
	if resp.StatusCode != 200 {
		return users, errors.New(resp.Status)
	}
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return users, err
	}
	err = json.Unmarshal(data, &users)
	if err != nil {
		return users, err
	}
	return users, err
}

func (c *Client) AddOrgUser(orgId int64, user, role string) error {
		dataMap := map[string]string{
			"loginOrEmail": user,
			"role": role,
		}
		data, err := json.Marshal(dataMap)
		req, err := c.newRequest("POST", fmt.Sprintf("/api/orgs/%d/users", orgId), bytes.NewBuffer(data))
		if err != nil {
			return err
		}
		resp, err := c.Do(req)
		if err != nil {
			return err
		}
		if resp.StatusCode != 200 {
			return errors.New(resp.Status)
		}
		return err
}

func (c *Client) UpdateOrgUser(orgId, userId int64, role string) error {
	dataMap := map[string]string{
		"role": role,
	}
	data, err := json.Marshal(dataMap)
	req, err := c.newRequest("PATCH", fmt.Sprintf("/api/orgs/%d/users/%d", orgId, userId), bytes.NewBuffer(data))
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}

func (c *Client) RemoveOrgUser(orgId, userId int64) error {
	req, err := c.newRequest("DELETE", fmt.Sprintf("/api/orgs/%d/users/%d", orgId, userId), nil)
	if err != nil {
		return err
	}
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	if resp.StatusCode != 200 {
		return errors.New(resp.Status)
	}
	return err
}
<|endoftext|>


<commit_before>package runtimes

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/aisk/logp"
	"github.com/facebookgo/parseignore"
	"github.com/facebookgo/symwalk"
	"github.com/leancloud/lean-cli/utils"
)

var ErrRuntimeNotFound = errors.New("Unsupported project structure. Please inspect your directory structure to make sure it is a valid LeanEngine project.")

type filesPattern struct {
	Includes []string
	Excludes []string
}

// Runtime stands for a language runtime
type Runtime struct {
	command     *exec.Cmd
	WorkDir     string
	ProjectPath string
	Name        string
	Exec        string
	Args        []string
	Envs        []string
	Remote      string
	Port        string
	// DeployFiles is the patterns for source code to deploy to the remote server
	DeployFiles filesPattern
	// Errors is the channel that receives the command's error result
	Errors chan error
}

// Run the project, and watch file changes
func (runtime *Runtime) Run() {
	go func() {
		runtime.command = exec.Command(runtime.Exec, runtime.Args...)
		runtime.command.Dir = runtime.WorkDir
		runtime.command.Stdout = os.Stdout
		runtime.command.Stderr = os.Stderr
		runtime.command.Env = os.Environ()

		for _, env := range runtime.Envs {
			runtime.command.Env = append(runtime.command.Env, env)
		}

		logp.Infof("Use %s to start the project\r\n", runtime.command.Args)
		logp.Infof("The project is running at: http://localhost:%s\r\n", runtime.Port)
		err := runtime.command.Run()
		if err != nil {
			runtime.Errors <- err
		}
	}()
}

func (runtime *Runtime) ArchiveUploadFiles(archiveFile string, ignoreFilePath string) error {
	return runtime.defaultArchive(archiveFile, ignoreFilePath)
}

func (runtime *Runtime) defaultArchive(archiveFile string, ignoreFilePath string) error {
	matcher, err := runtime.readIgnore(ignoreFilePath)
	if os.IsNotExist(err) {
		return fmt.Errorf("The designated ignore file '%s' doesn't exist", ignoreFilePath)
	} else if err != nil {
		return err
	}

	files := []struct{ Name, Path string }{}
	err = symwalk.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// convert DOS's '\' path seprater to UNIX style
		path = filepath.ToSlash(path)
		decision, err := matcher.Match(path, info)
		if err != nil {
			return err
		}

		if info.IsDir() {
			if decision == parseignore.Exclude {
				return filepath.SkipDir
			}
			return nil
		}

		if decision != parseignore.Exclude {
			files = append(files, struct{ Name, Path string }{
				Name: path,
				Path: path,
			})
		}
		return nil
	})

	if err != nil {
		return err
	}

	return utils.ArchiveFiles(archiveFile, files)
}

// DetectRuntime returns the project's runtime
func DetectRuntime(projectPath string) (*Runtime, error) {
	// order is important
	if utils.IsFileExists(filepath.Join(projectPath, "cloud", "main.js")) {
		logp.Info("cloudcode runtime detected")
		return &Runtime{
			Name: "cloudcode",
		}, nil
	}
	packageFilePath := filepath.Join(projectPath, "package.json")
	if utils.IsFileExists(filepath.Join(projectPath, "server.js")) && utils.IsFileExists(packageFilePath) {
		logp.Info("Node.js runtime detected")
		return newNodeRuntime(projectPath)
	}
	if utils.IsFileExists(packageFilePath) {
		data, err := ioutil.ReadFile(packageFilePath)
		if err == nil {
			data = utils.StripUTF8BOM(data)
			var result struct {
				Scripts struct {
					Start string `json:"start"`
				} `json:"scripts"`
			}
			if err = json.Unmarshal(data, &result); err == nil {
				if result.Scripts.Start != "" {
					logp.Info("Node.js runtime detected")
					return newNodeRuntime(projectPath)
				}
			}
		}
	}
	if utils.IsFileExists(filepath.Join(projectPath, "requirements.txt")) && utils.IsFileExists(filepath.Join(projectPath, "wsgi.py")) {
		logp.Info("Python runtime detected")
		return newPythonRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "composer.json")) && utils.IsFileExists(filepath.Join("public", "index.php")) {
		logp.Info("PHP runtime detected")
		return newPhpRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "pom.xml")) {
		logp.Info("Java runtime detected")
		return newJavaRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "app.sln")) {
		logp.Info("DotNet runtime detected")
		return newDotnetRuntime(projectPath)
	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "Unknown",
		Errors:      make(chan error),
	}, ErrRuntimeNotFound
}

func lookupBin(fallbacks []string) (string, error) {
	for i, bin := range fallbacks {
		binPath, err := exec.LookPath(bin)
		if err == nil { // found
			if i == 0 {
				logp.Infof("Found executable file: `%s`\r\n", binPath)
			} else {
				logp.Warnf("Cannot find command `%s`, using `%s` instead of \r\n", fallbacks[i-1], fallbacks[i])
			}
			return bin, nil
		}
	}

	return "", fmt.Errorf("`%s` not found", fallbacks[0])
}

func newPythonRuntime(projectPath string) (*Runtime, error) {
	// TODO: reafactor this shit
	if config, err := getEngineConfig(projectPath); err == nil {
		if config.CMD != "" {
			return newPythonRuntimeFromEngineConfig(projectPath, config)
		}
	}
	content, err := ioutil.ReadFile(filepath.Join(projectPath, ".python-version"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		execName := "python2.7"
		content, err = ioutil.ReadFile(filepath.Join(projectPath, "runtime.txt"))
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}
			// the default content
			content = []byte("python-2.7")
		}
		if strings.HasPrefix(string(content), "python-2.7") {
			execName, err = lookupBin([]string{"python2.7", "python2", "python"})
			if err != nil {
				return nil, err
			}
		} else if strings.HasPrefix(string(content), "python-3.5") {
			execName, err = lookupBin([]string{"python3.5", "python3", "python"})
			if err != nil {
				return nil, err
			}
		} else {
			return nil, errors.New("invalid python runtime.txt format, only `python-2.7` and `python-3.5` were allowed")
		}

		return &Runtime{
			ProjectPath: projectPath,
			Name:        "python",
			Exec:        execName,
			Args:        []string{"wsgi.py"},
			Errors:      make(chan error),
		}, nil
	}
	pythonVersion := string(content)
	if !(strings.HasPrefix(pythonVersion, "2.") || strings.HasPrefix(pythonVersion, "3.")) {
		return nil, errors.New("Wrong pyenv version. We only support CPython. Please check and correct .python-version")
	}
	logp.Info("pyenv detected. Please make sure pyenv is configured properly.")

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        "python",
		Args:        []string{"wsgi.py"},
		Errors:      make(chan error),
	}, nil
}

func newPythonRuntimeFromEngineConfig(projectPath string, config *engineConfig) (*Runtime, error) {
	exec, args := config.parseCMD()
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        exec,
		Args:        args,
		Errors:      make(chan error),
	}, nil

}

func newNodeRuntime(projectPath string) (*Runtime, error) {
	execName := "node"
	args := []string{"server.js"}
	pkgFile := filepath.Join(projectPath, "package.json")
	if content, err := ioutil.ReadFile(pkgFile); err == nil {
		content = utils.StripUTF8BOM(content)
		pkg := new(struct {
			Scripts struct {
				Start string `json:"start"`
				Dev   string `json:"dev"`
			} `json:"scripts"`
			Dependencies map[string]string `json:"dependencies"`
		})
		err = json.Unmarshal(content, pkg)
		if err != nil {
			return nil, err
		}

		if pkg.Scripts.Dev != "" {
			execName = "npm"
			args = []string{"run", "dev"}
		} else if pkg.Scripts.Start != "" {
			execName = "npm"
			args = []string{"start"}
		}

		if sdkVersion, ok := pkg.Dependencies["leanengine"]; ok {
			if strings.HasPrefix(sdkVersion, "0.") ||
				strings.HasPrefix(sdkVersion, "~0.") ||
				strings.HasPrefix(sdkVersion, "^0.") {
				logp.Warn("The current leanengine SDK is too low. Local debugging of cloud functions is not supported. Please refer to http://url.leanapp.cn/Og1cVia for upgrade instructions")
			}
		}

	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "node.js",
		Exec:        execName,
		Args:        args,
		Errors:      make(chan error),
	}, nil
}

func newJavaRuntime(projectPath string) (*Runtime, error) {
	exec := "mvn"
	args := []string{"jetty:run"}
	if config, err := getEngineConfig(projectPath); err == nil {
		if config.CMD != "" {
			exec, args = config.parseCMD()
		}
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "java",
		Exec:        exec,
		Args:        args,
		Errors:      make(chan error),
	}, nil
}

func newPhpRuntime(projectPath string) (*Runtime, error) {
	entryScript, err := getPHPEntryScriptPath()
	if err != nil {
		return nil, err
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "php",
		Exec:        "php",
		Args:        []string{"-S", "127.0.0.1:3000", "-t", "public", entryScript},
		Errors:      make(chan error),
	}, nil
}

func newDotnetRuntime(projectPath string) (*Runtime, error) {
	return &Runtime{
		WorkDir:     filepath.Join(projectPath, "web"),
		ProjectPath: projectPath,
		Name:        "dotnet",
		Exec:        "dotnet",
		Args:        []string{"run"},
		Envs:        []string{"ASPNETCORE_URLS=http://0.0.0.0:3000"},
		Errors:      make(chan error),
	}, nil
}
<commit_msg>feat: lean up now supports for static project<commit_after>package runtimes

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strings"

	"github.com/aisk/logp"
	"github.com/facebookgo/parseignore"
	"github.com/facebookgo/symwalk"
	"github.com/leancloud/lean-cli/utils"
)

var ErrRuntimeNotFound = errors.New("Unsupported project structure. Please inspect your directory structure to make sure it is a valid LeanEngine project.")

type filesPattern struct {
	Includes []string
	Excludes []string
}

// Runtime stands for a language runtime
type Runtime struct {
	command     *exec.Cmd
	WorkDir     string
	ProjectPath string
	Name        string
	Exec        string
	Args        []string
	Envs        []string
	Remote      string
	Port        string
	// DeployFiles is the patterns for source code to deploy to the remote server
	DeployFiles filesPattern
	// Errors is the channel that receives the command's error result
	Errors chan error
}

// Run the project, and watch file changes
func (runtime *Runtime) Run() {
	go func() {
		runtime.command = exec.Command(runtime.Exec, runtime.Args...)
		runtime.command.Dir = runtime.WorkDir
		runtime.command.Stdout = os.Stdout
		runtime.command.Stderr = os.Stderr
		runtime.command.Env = os.Environ()

		for _, env := range runtime.Envs {
			runtime.command.Env = append(runtime.command.Env, env)
		}

		logp.Infof("Use %s to start the project\r\n", runtime.command.Args)
		logp.Infof("The project is running at: http://localhost:%s\r\n", runtime.Port)
		err := runtime.command.Run()
		if err != nil {
			runtime.Errors <- err
		}
	}()
}

func (runtime *Runtime) ArchiveUploadFiles(archiveFile string, ignoreFilePath string) error {
	return runtime.defaultArchive(archiveFile, ignoreFilePath)
}

func (runtime *Runtime) defaultArchive(archiveFile string, ignoreFilePath string) error {
	matcher, err := runtime.readIgnore(ignoreFilePath)
	if os.IsNotExist(err) {
		return fmt.Errorf("The designated ignore file '%s' doesn't exist", ignoreFilePath)
	} else if err != nil {
		return err
	}

	files := []struct{ Name, Path string }{}
	err = symwalk.Walk(".", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		// convert DOS's '\' path seprater to UNIX style
		path = filepath.ToSlash(path)
		decision, err := matcher.Match(path, info)
		if err != nil {
			return err
		}

		if info.IsDir() {
			if decision == parseignore.Exclude {
				return filepath.SkipDir
			}
			return nil
		}

		if decision != parseignore.Exclude {
			files = append(files, struct{ Name, Path string }{
				Name: path,
				Path: path,
			})
		}
		return nil
	})

	if err != nil {
		return err
	}

	return utils.ArchiveFiles(archiveFile, files)
}

// DetectRuntime returns the project's runtime
func DetectRuntime(projectPath string) (*Runtime, error) {
	// order is important
	if utils.IsFileExists(filepath.Join(projectPath, "cloud", "main.js")) {
		logp.Info("cloudcode runtime detected")
		return &Runtime{
			Name: "cloudcode",
		}, nil
	}
	packageFilePath := filepath.Join(projectPath, "package.json")
	if utils.IsFileExists(filepath.Join(projectPath, "server.js")) && utils.IsFileExists(packageFilePath) {
		logp.Info("Node.js runtime detected")
		return newNodeRuntime(projectPath)
	}
	if utils.IsFileExists(packageFilePath) {
		data, err := ioutil.ReadFile(packageFilePath)
		if err == nil {
			data = utils.StripUTF8BOM(data)
			var result struct {
				Scripts struct {
					Start string `json:"start"`
				} `json:"scripts"`
			}
			if err = json.Unmarshal(data, &result); err == nil {
				if result.Scripts.Start != "" {
					logp.Info("Node.js runtime detected")
					return newNodeRuntime(projectPath)
				}
			}
		}
	}
	if utils.IsFileExists(filepath.Join(projectPath, "requirements.txt")) && utils.IsFileExists(filepath.Join(projectPath, "wsgi.py")) {
		logp.Info("Python runtime detected")
		return newPythonRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "composer.json")) && utils.IsFileExists(filepath.Join("public", "index.php")) {
		logp.Info("PHP runtime detected")
		return newPhpRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "pom.xml")) {
		logp.Info("Java runtime detected")
		return newJavaRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "app.sln")) {
		logp.Info("DotNet runtime detected")
		return newDotnetRuntime(projectPath)
	}
	if utils.IsFileExists(filepath.Join(projectPath, "index.html")) {
		logp.Info("Static runtime detected")
		return newStaticRuntime(projectPath)
	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "Unknown",
		Errors:      make(chan error),
	}, ErrRuntimeNotFound
}

func lookupBin(fallbacks []string) (string, error) {
	for i, bin := range fallbacks {
		binPath, err := exec.LookPath(bin)
		if err == nil { // found
			if i == 0 {
				logp.Infof("Found executable file: `%s`\r\n", binPath)
			} else {
				logp.Warnf("Cannot find command `%s`, using `%s` instead of \r\n", fallbacks[i-1], fallbacks[i])
			}
			return bin, nil
		}
	}

	return "", fmt.Errorf("`%s` not found", fallbacks[0])
}

func newPythonRuntime(projectPath string) (*Runtime, error) {
	// TODO: reafactor this shit
	if config, err := getEngineConfig(projectPath); err == nil {
		if config.CMD != "" {
			return newPythonRuntimeFromEngineConfig(projectPath, config)
		}
	}
	content, err := ioutil.ReadFile(filepath.Join(projectPath, ".python-version"))
	if err != nil {
		if !os.IsNotExist(err) {
			return nil, err
		}

		execName := "python2.7"
		content, err = ioutil.ReadFile(filepath.Join(projectPath, "runtime.txt"))
		if err != nil {
			if !os.IsNotExist(err) {
				return nil, err
			}
			// the default content
			content = []byte("python-2.7")
		}
		if strings.HasPrefix(string(content), "python-2.7") {
			execName, err = lookupBin([]string{"python2.7", "python2", "python"})
			if err != nil {
				return nil, err
			}
		} else if strings.HasPrefix(string(content), "python-3.5") {
			execName, err = lookupBin([]string{"python3.5", "python3", "python"})
			if err != nil {
				return nil, err
			}
		} else {
			return nil, errors.New("invalid python runtime.txt format, only `python-2.7` and `python-3.5` were allowed")
		}

		return &Runtime{
			ProjectPath: projectPath,
			Name:        "python",
			Exec:        execName,
			Args:        []string{"wsgi.py"},
			Errors:      make(chan error),
		}, nil
	}
	pythonVersion := string(content)
	if !(strings.HasPrefix(pythonVersion, "2.") || strings.HasPrefix(pythonVersion, "3.")) {
		return nil, errors.New("Wrong pyenv version. We only support CPython. Please check and correct .python-version")
	}
	logp.Info("pyenv detected. Please make sure pyenv is configured properly.")

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        "python",
		Args:        []string{"wsgi.py"},
		Errors:      make(chan error),
	}, nil
}

func newPythonRuntimeFromEngineConfig(projectPath string, config *engineConfig) (*Runtime, error) {
	exec, args := config.parseCMD()
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "python",
		Exec:        exec,
		Args:        args,
		Errors:      make(chan error),
	}, nil

}

func newNodeRuntime(projectPath string) (*Runtime, error) {
	execName := "node"
	args := []string{"server.js"}
	pkgFile := filepath.Join(projectPath, "package.json")
	if content, err := ioutil.ReadFile(pkgFile); err == nil {
		content = utils.StripUTF8BOM(content)
		pkg := new(struct {
			Scripts struct {
				Start string `json:"start"`
				Dev   string `json:"dev"`
			} `json:"scripts"`
			Dependencies map[string]string `json:"dependencies"`
		})
		err = json.Unmarshal(content, pkg)
		if err != nil {
			return nil, err
		}

		if pkg.Scripts.Dev != "" {
			execName = "npm"
			args = []string{"run", "dev"}
		} else if pkg.Scripts.Start != "" {
			execName = "npm"
			args = []string{"start"}
		}

		if sdkVersion, ok := pkg.Dependencies["leanengine"]; ok {
			if strings.HasPrefix(sdkVersion, "0.") ||
				strings.HasPrefix(sdkVersion, "~0.") ||
				strings.HasPrefix(sdkVersion, "^0.") {
				logp.Warn("The current leanengine SDK is too low. Local debugging of cloud functions is not supported. Please refer to http://url.leanapp.cn/Og1cVia for upgrade instructions")
			}
		}

	}

	return &Runtime{
		ProjectPath: projectPath,
		Name:        "node.js",
		Exec:        execName,
		Args:        args,
		Errors:      make(chan error),
	}, nil
}

func newJavaRuntime(projectPath string) (*Runtime, error) {
	exec := "mvn"
	args := []string{"jetty:run"}
	if config, err := getEngineConfig(projectPath); err == nil {
		if config.CMD != "" {
			exec, args = config.parseCMD()
		}
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "java",
		Exec:        exec,
		Args:        args,
		Errors:      make(chan error),
	}, nil
}

func newPhpRuntime(projectPath string) (*Runtime, error) {
	entryScript, err := getPHPEntryScriptPath()
	if err != nil {
		return nil, err
	}
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "php",
		Exec:        "php",
		Args:        []string{"-S", "127.0.0.1:3000", "-t", "public", entryScript},
		Errors:      make(chan error),
	}, nil
}

func newDotnetRuntime(projectPath string) (*Runtime, error) {
	return &Runtime{
		WorkDir:     filepath.Join(projectPath, "web"),
		ProjectPath: projectPath,
		Name:        "dotnet",
		Exec:        "dotnet",
		Args:        []string{"run"},
		Envs:        []string{"ASPNETCORE_URLS=http://0.0.0.0:3000"},
		Errors:      make(chan error),
	}, nil
}

func newStaticRuntime(projectPath string) (*Runtime, error) {
	return &Runtime{
		ProjectPath: projectPath,
		Name:        "static",
		Exec:        "npx",
		Args:        []string{"live-server", "--port=3000"},
		Errors:      make(chan error),
	}, nil
}
<|endoftext|>


<commit_before>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// **********************************************************************
// NOTE :: This is getting closer to a Opus<->PCM layer for Discordgo and will
// probably eventually move into a sub-folder of the Discordgo package.
// **********************************************************************

// Package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	speakers    map[uint32]*gopus.Decoder
	opusEncoder *gopus.Encoder
	run         *exec.Cmd
	sendpcm     bool
	recvpcm     bool
	recv        chan *discordgo.Packet
	send        chan []int16
	mu          sync.Mutex
)

// SendPCM will receive on the provied channel encode
// received PCM data into Opus then send that to Discordgo
func SendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if sendpcm || pcm == nil {
		mu.Unlock()
		return
	}
	sendpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recv, ok := <-pcm
		if !ok {
			fmt.Println("PCM Channel closed.")
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recv, frameSize, maxBytes)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		if v.Ready == false || v.OpusSend == nil {
			fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.OpusSend)
			return
		}
		// send encoded opus data to the sendOpus channel
		v.OpusSend <- opus
	}
}

// ReceivePCM will receive on the the Discordgo OpusRecv channel and decode
// the opus audio into PCM then send it on the provided channel.
func ReceivePCM(v *discordgo.VoiceConnection, c chan *discordgo.Packet) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if recvpcm || c == nil {
		mu.Unlock()
		return
	}
	recvpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()
	var err error

	for {

		if v.Ready == false || v.OpusRecv == nil {
			fmt.Printf("Discordgo not ready to receive opus packets. %+v : %+v", v.Ready, v.OpusSend)
			return
		}

		p, ok := <-v.OpusRecv
		if !ok {
			return
		}

		if speakers == nil {
			speakers = make(map[uint32]*gopus.Decoder)
		}

		_, ok = speakers[p.SSRC]
		if !ok {
			speakers[p.SSRC], err = gopus.NewDecoder(48000, 2)
			if err != nil {
				fmt.Println("error creating opus decoder:", err)
				continue
			}
		}

		p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 960, false)
		if err != nil {
			fmt.Println("Error decoding opus data: ", err)
			continue
		}

		c <- p
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(v *discordgo.VoiceConnection, filename string) {

	// Create a shell command "object" to run.
	run = exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	ffmpegout, err := run.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return
	}

	ffmpegbuf := bufio.NewReaderSize(ffmpegout, 16384)

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return
	}

	// Send "speaking" packet over the voice websocket
	v.Speaking(true)

	// Send not "speaking" packet over the websocket when we finish
	defer v.Speaking(false)

	// will actually only spawn one instance, a bit hacky.
	if send == nil {
		send = make(chan []int16, 2)
	}
	go SendPCM(v, send)

	for {

		// read data from ffmpeg stdout
		audiobuf := make([]int16, frameSize*channels)
		err = binary.Read(ffmpegbuf, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return
		}

		// Send received PCM to the sendPCM channel
		send <- audiobuf
	}
}

// KillPlayer forces the player to stop by killing the ffmpeg cmd process
// this method may be removed later in favor of using chans or bools to
// request a stop.
func KillPlayer() {
	run.Process.Kill()
}
<commit_msg>Fix error message<commit_after>/*******************************************************************************
 * This is very experimental code and probably a long way from perfect or
 * ideal.  Please provide feed back on areas that would improve performance
 *
 */

// **********************************************************************
// NOTE :: This is getting closer to a Opus<->PCM layer for Discordgo and will
// probably eventually move into a sub-folder of the Discordgo package.
// **********************************************************************

// Package dgvoice provides opus encoding and audio file playback for the
// Discordgo package.
package dgvoice

import (
	"bufio"
	"encoding/binary"
	"fmt"
	"io"
	"os/exec"
	"strconv"
	"sync"

	"github.com/bwmarrin/discordgo"
	"github.com/layeh/gopus"
)

// NOTE: This API is not final and these are likely to change.

// Technically the below settings can be adjusted however that poses
// a lot of other problems that are not handled well at this time.
// These below values seem to provide the best overall performance
const (
	channels  int = 2                   // 1 for mono, 2 for stereo
	frameRate int = 48000               // audio sampling rate
	frameSize int = 960                 // uint16 size of each audio frame
	maxBytes  int = (frameSize * 2) * 2 // max size of opus data
)

var (
	speakers    map[uint32]*gopus.Decoder
	opusEncoder *gopus.Encoder
	run         *exec.Cmd
	sendpcm     bool
	recvpcm     bool
	recv        chan *discordgo.Packet
	send        chan []int16
	mu          sync.Mutex
)

// SendPCM will receive on the provied channel encode
// received PCM data into Opus then send that to Discordgo
func SendPCM(v *discordgo.VoiceConnection, pcm <-chan []int16) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if sendpcm || pcm == nil {
		mu.Unlock()
		return
	}
	sendpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()

	var err error

	opusEncoder, err = gopus.NewEncoder(frameRate, channels, gopus.Audio)

	if err != nil {
		fmt.Println("NewEncoder Error:", err)
		return
	}

	for {

		// read pcm from chan, exit if channel is closed.
		recv, ok := <-pcm
		if !ok {
			fmt.Println("PCM Channel closed.")
			return
		}

		// try encoding pcm frame with Opus
		opus, err := opusEncoder.Encode(recv, frameSize, maxBytes)
		if err != nil {
			fmt.Println("Encoding Error:", err)
			return
		}

		if v.Ready == false || v.OpusSend == nil {
			fmt.Printf("Discordgo not ready for opus packets. %+v : %+v", v.Ready, v.OpusSend)
			return
		}
		// send encoded opus data to the sendOpus channel
		v.OpusSend <- opus
	}
}

// ReceivePCM will receive on the the Discordgo OpusRecv channel and decode
// the opus audio into PCM then send it on the provided channel.
func ReceivePCM(v *discordgo.VoiceConnection, c chan *discordgo.Packet) {

	// make sure this only runs one instance at a time.
	mu.Lock()
	if recvpcm || c == nil {
		mu.Unlock()
		return
	}
	recvpcm = true
	mu.Unlock()

	defer func() { sendpcm = false }()
	var err error

	for {

		if v.Ready == false || v.OpusRecv == nil {
			fmt.Printf("Discordgo not ready to receive opus packets. %+v : %+v", v.Ready, v.OpusRecv)
			return
		}

		p, ok := <-v.OpusRecv
		if !ok {
			return
		}

		if speakers == nil {
			speakers = make(map[uint32]*gopus.Decoder)
		}

		_, ok = speakers[p.SSRC]
		if !ok {
			speakers[p.SSRC], err = gopus.NewDecoder(48000, 2)
			if err != nil {
				fmt.Println("error creating opus decoder:", err)
				continue
			}
		}

		p.PCM, err = speakers[p.SSRC].Decode(p.Opus, 960, false)
		if err != nil {
			fmt.Println("Error decoding opus data: ", err)
			continue
		}

		c <- p
	}
}

// PlayAudioFile will play the given filename to the already connected
// Discord voice server/channel.  voice websocket and udp socket
// must already be setup before this will work.
func PlayAudioFile(v *discordgo.VoiceConnection, filename string) {

	// Create a shell command "object" to run.
	run = exec.Command("ffmpeg", "-i", filename, "-f", "s16le", "-ar", strconv.Itoa(frameRate), "-ac", strconv.Itoa(channels), "pipe:1")
	ffmpegout, err := run.StdoutPipe()
	if err != nil {
		fmt.Println("StdoutPipe Error:", err)
		return
	}

	ffmpegbuf := bufio.NewReaderSize(ffmpegout, 16384)

	// Starts the ffmpeg command
	err = run.Start()
	if err != nil {
		fmt.Println("RunStart Error:", err)
		return
	}

	// Send "speaking" packet over the voice websocket
	v.Speaking(true)

	// Send not "speaking" packet over the websocket when we finish
	defer v.Speaking(false)

	// will actually only spawn one instance, a bit hacky.
	if send == nil {
		send = make(chan []int16, 2)
	}
	go SendPCM(v, send)

	for {

		// read data from ffmpeg stdout
		audiobuf := make([]int16, frameSize*channels)
		err = binary.Read(ffmpegbuf, binary.LittleEndian, &audiobuf)
		if err == io.EOF || err == io.ErrUnexpectedEOF {
			return
		}
		if err != nil {
			fmt.Println("error reading from ffmpeg stdout :", err)
			return
		}

		// Send received PCM to the sendPCM channel
		send <- audiobuf
	}
}

// KillPlayer forces the player to stop by killing the ffmpeg cmd process
// this method may be removed later in favor of using chans or bools to
// request a stop.
func KillPlayer() {
	run.Process.Kill()
}
<|endoftext|>


<commit_before>package urlcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			errorstr += err.Error()
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<commit_msg>manifest: fix newlines in multiple error string<commit_after>package urlcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<|endoftext|>