<commit_before>package image_ecosystem

import (
	"context"
	"fmt"
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	exutil "github.com/openshift/origin/test/extended/util"
)

var _ = g.Describe("[sig-devex][Feature:ImageEcosystem][python][Slow] hot deploy for openshift python image", func() {
	defer g.GinkgoRecover()

	var (
		oc             = exutil.NewCLI("s2i-python")
		djangoTemplate = "django-psql-example"
		modifyCommand  = []string{"sed", "-ie", `s/'count': PageView.objects.count()/'count': 1337/`, "welcome/views.py"}
		pageCountFn    = func(count int) string { return fmt.Sprintf("Page views: %d", count) }
		dcName         = "django-psql-example"
		rcNameOne      = fmt.Sprintf("%s-1", dcName)
		rcNameTwo      = fmt.Sprintf("%s-2", dcName)
		dcLabelOne     = exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", rcNameOne))
		dcLabelTwo     = exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", rcNameTwo))
	)

	g.Context("", func() {
		g.JustBeforeEach(func() {
			exutil.PreTestDump()
		})

		g.AfterEach(func() {
			if g.CurrentGinkgoTestDescription().Failed {
				exutil.DumpPodStates(oc)
				exutil.DumpPodLogsStartingWith("", oc)
			}
		})

		g.Describe("Django example", func() {
			g.It(fmt.Sprintf("should work with hot deploy"), func() {

				err := exutil.WaitForOpenShiftNamespaceImageStreams(oc)
				o.Expect(err).NotTo(o.HaveOccurred())
				g.By(fmt.Sprintf("calling oc new-app %s", djangoTemplate))
				// gunicorn workers read the application source lazily.  For
				// this test to succeed reliably, we must have one worker only
				// (WEB_CONCURRENCY=1).  Having primed the worker via
				// assertPageCountIs, we can then expect it not to read in the
				// modified application source when hot deploy is disabled.
				err = oc.Run("new-app").Args(djangoTemplate, "-e", "WEB_CONCURRENCY=1").Execute()
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for build to finish")
				err = exutil.WaitForABuild(oc.BuildClient().BuildV1().Builds(oc.Namespace()), rcNameOne, nil, nil, nil)
				if err != nil {
					exutil.DumpBuildLogs(dcName, oc)
				}
				o.Expect(err).NotTo(o.HaveOccurred())

				err = exutil.WaitForDeploymentConfig(oc.KubeClient(), oc.AppsClient().AppsV1(), oc.Namespace(), dcName, 1, true, oc)
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for endpoint")
				err = exutil.WaitForEndpoint(oc.KubeFramework().ClientSet, oc.Namespace(), dcName)
				o.Expect(err).NotTo(o.HaveOccurred())
				oldEndpoint, err := oc.KubeFramework().ClientSet.CoreV1().Endpoints(oc.Namespace()).Get(context.Background(), dcName, metav1.GetOptions{})
				o.Expect(err).NotTo(o.HaveOccurred())

				assertPageCountIs := func(i int, dcLabel labels.Selector) {
					_, err := exutil.WaitForPods(oc.KubeClient().CoreV1().Pods(oc.Namespace()), dcLabel, exutil.CheckPodIsRunning, 1, 4*time.Minute)
					o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred())

					result, err := CheckPageContains(oc, dcName, "", pageCountFn(i))
					if err != nil || !result {
						exutil.DumpApplicationPodLogs(dcName, oc)
					}
					o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred())
					o.ExpectWithOffset(1, result).To(o.BeTrue())
				}

				g.By("checking page count")
				assertPageCountIs(1, dcLabelOne)
				assertPageCountIs(2, dcLabelOne)

				g.By("modifying the source code with disabled hot deploy")
				err = RunInPodContainer(oc, dcLabelOne, modifyCommand)
				o.Expect(err).NotTo(o.HaveOccurred())
				assertPageCountIs(3, dcLabelOne)

				pods, err := oc.KubeClient().CoreV1().Pods(oc.Namespace()).List(context.Background(), metav1.ListOptions{LabelSelector: dcLabelOne.String()})
				o.Expect(err).NotTo(o.HaveOccurred())
				o.Expect(len(pods.Items)).To(o.Equal(1))

				g.By("turning on hot-deploy")
				err = oc.Run("set", "env").Args("dc", dcName, "APP_CONFIG=conf/reload.py").Execute()
				o.Expect(err).NotTo(o.HaveOccurred())
				err = exutil.WaitForDeploymentConfig(oc.KubeClient(), oc.AppsClient().AppsV1(), oc.Namespace(), dcName, 2, true, oc)
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for a new endpoint")
				err = exutil.WaitForEndpoint(oc.KubeFramework().ClientSet, oc.Namespace(), dcName)
				o.Expect(err).NotTo(o.HaveOccurred())

				// Ran into an issue where we'd try to hit the endpoint before it was updated, resulting in
				// request timeouts against the previous pod's ip.  So make sure the endpoint is pointing to the
				// new pod before hitting it.
				err = wait.Poll(1*time.Second, 1*time.Minute, func() (bool, error) {
					newEndpoint, err := oc.KubeFramework().ClientSet.CoreV1().Endpoints(oc.Namespace()).Get(context.Background(), dcName, metav1.GetOptions{})
					if err != nil {
						return false, err
					}
					if !strings.Contains(newEndpoint.Subsets[0].Addresses[0].TargetRef.Name, rcNameTwo) {
						e2e.Logf("waiting on endpoint address ref %s to contain %s", newEndpoint.Subsets[0].Addresses[0].TargetRef.Name, rcNameTwo)
						return false, nil
					}
					e2e.Logf("old endpoint was %#v, new endpoint is %#v", oldEndpoint, newEndpoint)
					return true, nil
				})
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("modifying the source code with enabled hot deploy")
				// now using a persistent template, the count does not reset
				assertPageCountIs(4, dcLabelTwo)
				err = RunInPodContainer(oc, dcLabelTwo, modifyCommand)
				o.Expect(err).NotTo(o.HaveOccurred())
				assertPageCountIs(1337, dcLabelTwo)
			})
		})
	})
})
<commit_msg>Bug 2023238: Skipping Django Test until bug is fixed<commit_after>package image_ecosystem

import (
	"context"
	"fmt"
	"strings"
	"time"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/apimachinery/pkg/util/wait"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	exutil "github.com/openshift/origin/test/extended/util"
)

var _ = g.Describe("[sig-devex][Feature:ImageEcosystem][python][Slow] hot deploy for openshift python image", func() {
	defer g.GinkgoRecover()

	var (
		oc             = exutil.NewCLI("s2i-python")
		djangoTemplate = "django-psql-example"
		modifyCommand  = []string{"sed", "-ie", `s/'count': PageView.objects.count()/'count': 1337/`, "welcome/views.py"}
		pageCountFn    = func(count int) string { return fmt.Sprintf("Page views: %d", count) }
		dcName         = "django-psql-example"
		rcNameOne      = fmt.Sprintf("%s-1", dcName)
		rcNameTwo      = fmt.Sprintf("%s-2", dcName)
		dcLabelOne     = exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", rcNameOne))
		dcLabelTwo     = exutil.ParseLabelsOrDie(fmt.Sprintf("deployment=%s", rcNameTwo))
	)

	g.Context("", func() {
		g.JustBeforeEach(func() {
			exutil.PreTestDump()
		})

		g.AfterEach(func() {
			if g.CurrentGinkgoTestDescription().Failed {
				exutil.DumpPodStates(oc)
				exutil.DumpPodLogsStartingWith("", oc)
			}
		})

		g.Describe("Django example", func() {
			g.It(fmt.Sprintf("should work with hot deploy"), func() {

				// skipping this test for now until we figure out why is not
				// passing on CI bz: 2023238
				g.Skip("Skipping until bz_2023238 is fixed.")
				err := exutil.WaitForOpenShiftNamespaceImageStreams(oc)
				o.Expect(err).NotTo(o.HaveOccurred())
				g.By(fmt.Sprintf("calling oc new-app %s", djangoTemplate))
				// gunicorn workers read the application source lazily.  For
				// this test to succeed reliably, we must have one worker only
				// (WEB_CONCURRENCY=1).  Having primed the worker via
				// assertPageCountIs, we can then expect it not to read in the
				// modified application source when hot deploy is disabled.
				err = oc.Run("new-app").Args(djangoTemplate, "-e", "WEB_CONCURRENCY=1").Execute()
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for build to finish")
				err = exutil.WaitForABuild(oc.BuildClient().BuildV1().Builds(oc.Namespace()), rcNameOne, nil, nil, nil)
				if err != nil {
					exutil.DumpBuildLogs(dcName, oc)
				}
				o.Expect(err).NotTo(o.HaveOccurred())

				err = exutil.WaitForDeploymentConfig(oc.KubeClient(), oc.AppsClient().AppsV1(), oc.Namespace(), dcName, 1, true, oc)
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for endpoint")
				err = exutil.WaitForEndpoint(oc.KubeFramework().ClientSet, oc.Namespace(), dcName)
				o.Expect(err).NotTo(o.HaveOccurred())
				oldEndpoint, err := oc.KubeFramework().ClientSet.CoreV1().Endpoints(oc.Namespace()).Get(context.Background(), dcName, metav1.GetOptions{})
				o.Expect(err).NotTo(o.HaveOccurred())

				assertPageCountIs := func(i int, dcLabel labels.Selector) {
					_, err := exutil.WaitForPods(oc.KubeClient().CoreV1().Pods(oc.Namespace()), dcLabel, exutil.CheckPodIsRunning, 1, 4*time.Minute)
					o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred())

					result, err := CheckPageContains(oc, dcName, "", pageCountFn(i))
					if err != nil || !result {
						exutil.DumpApplicationPodLogs(dcName, oc)
					}
					o.ExpectWithOffset(1, err).NotTo(o.HaveOccurred())
					o.ExpectWithOffset(1, result).To(o.BeTrue())
				}

				g.By("checking page count")
				assertPageCountIs(1, dcLabelOne)
				assertPageCountIs(2, dcLabelOne)

				g.By("modifying the source code with disabled hot deploy")
				err = RunInPodContainer(oc, dcLabelOne, modifyCommand)
				o.Expect(err).NotTo(o.HaveOccurred())
				assertPageCountIs(3, dcLabelOne)

				pods, err := oc.KubeClient().CoreV1().Pods(oc.Namespace()).List(context.Background(), metav1.ListOptions{LabelSelector: dcLabelOne.String()})
				o.Expect(err).NotTo(o.HaveOccurred())
				o.Expect(len(pods.Items)).To(o.Equal(1))

				g.By("turning on hot-deploy")
				err = oc.Run("set", "env").Args("dc", dcName, "APP_CONFIG=conf/reload.py").Execute()
				o.Expect(err).NotTo(o.HaveOccurred())
				err = exutil.WaitForDeploymentConfig(oc.KubeClient(), oc.AppsClient().AppsV1(), oc.Namespace(), dcName, 2, true, oc)
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("waiting for a new endpoint")
				err = exutil.WaitForEndpoint(oc.KubeFramework().ClientSet, oc.Namespace(), dcName)
				o.Expect(err).NotTo(o.HaveOccurred())

				// Ran into an issue where we'd try to hit the endpoint before it was updated, resulting in
				// request timeouts against the previous pod's ip.  So make sure the endpoint is pointing to the
				// new pod before hitting it.
				err = wait.Poll(1*time.Second, 1*time.Minute, func() (bool, error) {
					newEndpoint, err := oc.KubeFramework().ClientSet.CoreV1().Endpoints(oc.Namespace()).Get(context.Background(), dcName, metav1.GetOptions{})
					if err != nil {
						return false, err
					}
					if !strings.Contains(newEndpoint.Subsets[0].Addresses[0].TargetRef.Name, rcNameTwo) {
						e2e.Logf("waiting on endpoint address ref %s to contain %s", newEndpoint.Subsets[0].Addresses[0].TargetRef.Name, rcNameTwo)
						return false, nil
					}
					e2e.Logf("old endpoint was %#v, new endpoint is %#v", oldEndpoint, newEndpoint)
					return true, nil
				})
				o.Expect(err).NotTo(o.HaveOccurred())

				g.By("modifying the source code with enabled hot deploy")
				// now using a persistent template, the count does not reset
				assertPageCountIs(4, dcLabelTwo)
				err = RunInPodContainer(oc, dcLabelTwo, modifyCommand)
				o.Expect(err).NotTo(o.HaveOccurred())
				assertPageCountIs(1337, dcLabelTwo)
			})
		})
	})
})
<|endoftext|>


<commit_before>package compute

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

const (
	// ServiceDownActionNone indicates no action will be taken when a pool service is down.
	ServiceDownActionNone = "NONE"

	// ServiceDownActionDrop indicates that a pool service will be dropped when it is down.
	ServiceDownActionDrop = "DROP"

	// ServiceDownActionReselect indicates that a pool service will be reselected when it is down.
	ServiceDownActionReselect = "RESELECT"

	// LoadBalanceMethodRoundRobin indicates that requests will be directed to pool nodes in round-robin fashion.
	LoadBalanceMethodRoundRobin = "ROUND_ROBIN"

	// LoadBalanceMethodLeastConnectionsNode indicates that requests will be directed to the pool node that has the smallest number of active connections at the moment of connection.
	// All connections to the node are considered.
	LoadBalanceMethodLeastConnectionsNode = "LEAST_CONNECTIONS_NODE"

	// LoadBalanceMethodLeastConnectionsMember indicates that requests will be directed to the pool node that has the smallest number of active connections at the moment of connection.
	// Only connections to the node as a member of the current pool are considered.
	LoadBalanceMethodLeastConnectionsMember = "LEAST_CONNECTIONS_MEMBER"

	// LoadBalanceMethodObservedNode indicates that requests will be directed to the pool node that has the smallest number of active connections over time.
	// All connections to the node are considered.
	LoadBalanceMethodObservedNode = "OBSERVED_NODE"

	// LoadBalanceMethodObservedMember indicates that requests will be directed to the pool node that has the smallest number of active connections over time.
	// Only connections to the node as a member of the current pool are considered.
	LoadBalanceMethodObservedMember = "OBSERVED_MEMBER"

	// LoadBalanceMethodPredictiveNode indicates that requests will be directed to the pool node that is predicted to have the smallest number of active connections.
	// All connections to the pool are considered.
	LoadBalanceMethodPredictiveNode = "PREDICTIVE_NODE"

	// LoadBalanceMethodPredictiveMember indicates that requests will be directed to the pool node that is predicted to have the smallest number of active connections over time.
	// Only connections to the pool as a member of the current pool are considered.
	LoadBalanceMethodPredictiveMember = "PREDICTIVE_MEMBER"
)

// VIPPool represents a VIP pool.
type VIPPool struct {
	ID                string            `json:"id"`
	Name              string            `json:"name"`
	Description       string            `json:"description"`
	LoadBalanceMethod string            `json:"loadBalanceMethod"`
	HealthMonitors    []EntityReference `json:"healthMonitor"`
	ServiceDownAction string            `json:"serviceDownAction"`
	SlowRampTime      int               `json:"slowRampTime"`
	State             string            `json:"state"`
	NetworkDomainID   string            `json:"networkDomainID"`
	DataCenterID      string            `json:"datacenterId"`
	CreateTime        string            `json:"createTime"`
}

// GetID returns the pool's Id.
func (pool *VIPPool) GetID() string {
	return pool.ID
}

// GetResourceType returns the pool's resource type.
func (pool *VIPPool) GetResourceType() ResourceType {
	return ResourceTypeVIPPool
}

// GetName returns the pool's name.
func (pool *VIPPool) GetName() string {
	return pool.Name
}

// GetState returns the pool's current state.
func (pool *VIPPool) GetState() string {
	return pool.State
}

// IsDeleted determines whether the pool has been deleted (is nil).
func (pool *VIPPool) IsDeleted() bool {
	return pool == nil
}

var _ Resource = &VIPPool{}

// ToEntityReference creates an EntityReference representing the VIPNode.
func (pool *VIPPool) ToEntityReference() EntityReference {
	return EntityReference{
		ID:   pool.ID,
		Name: pool.Name,
	}
}

var _ NamedEntity = &VIPNode{}

// VIPPools represents a page of VIPPool results.
type VIPPools struct {
	Items []VIPPool

	PagedResult
}

// NewVIPPoolConfiguration represents the configuration for a new VIP pool.
type NewVIPPoolConfiguration struct {
	// The VIP pool name.
	Name string `json:"name"`

	// The VIP pool description.
	Description string `json:"description"`

	// The load-balancing method used for pools in the pool.
	LoadBalanceMethod string `json:"loadBalanceMethod"`

	// The Id of the pool's associated health monitors (if any).
	// Up to 2 health monitors can be specified per pool.
	HealthMonitorIDs []string `json:"healthMonitorId,omitempty"`

	// The action performed when a pool in the pool is down.
	ServiceDownAction string `json:"serviceDownAction"`

	// The time, in seconds, over which the the pool will ramp new pools up to their full request rate.
	SlowRampTime int `json:"slowRampTime"`

	// The Id of the network domain where the pool is located.
	NetworkDomainID string `json:"networkDomainId"`

	// The Id of the data centre where the pool is located.
	DatacenterID string `json:"datacenterId,omitempty"`
}

// EditVIPPoolConfiguration represents the request body when editing a VIP pool.
type EditVIPPoolConfiguration struct {
	// The VIP pool Id.
	ID string `json:"id"`

	// The VIP pool description.
	Description *string `json:"description,omitempty"`

	// The load-balancing method used for pools in the pool.
	LoadBalanceMethod *string `json:"loadBalanceMethod"`

	// The Id of the pool's associated health monitors (if any).
	// Up to 2 health monitors can be specified per pool.
	HealthMonitorIDs *[]string `json:"healthMonitorId,omitempty"`

	// The action performed when a pool in the pool is down.
	ServiceDownAction *string `json:"serviceDownAction"`

	// The time, in seconds, over which the the pool will ramp new pools up to their full request rate.
	SlowRampTime *int `json:"slowRampTime"`
}

// Request body for deleting a VIP pool.
type deleteVIPPool struct {
	// The VIP pool ID.
	ID string `json:"id"`
}

// ListVIPPoolsInNetworkDomain retrieves a list of all VIP pools in the specified network domain.
func (client *Client) ListVIPPoolsInNetworkDomain(networkDomainID string, paging *Paging) (pools *VIPPools, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/pool?networkDomainId=%s&%s",
		organizationID,
		url.QueryEscape(networkDomainID),
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list VIP pools in network domain '%s' failed with status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	pools = &VIPPools{}
	err = json.Unmarshal(responseBody, pools)
	if err != nil {
		return nil, err
	}

	return pools, nil
}

// GetVIPPool retrieves the VIP pool with the specified Id.
// Returns nil if no VIP pool is found with the specified Id.
func (client *Client) GetVIPPool(id string) (pool *VIPPool, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/pool/%s", organizationID, id)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve VIP pool with Id '%s' failed with status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	pool = &VIPPool{}
	err = json.Unmarshal(responseBody, pool)
	if err != nil {
		return nil, err
	}

	return pool, nil
}

// CreateVIPPool creates a new VIP pool.
// Returns the Id of the new pool.
func (client *Client) CreateVIPPool(poolConfiguration NewVIPPoolConfiguration) (poolID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/createPool", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &poolConfiguration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to create VIP pool '%s' failed with status code %d (%s): %s", poolConfiguration.Name, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "poolId", "value": "the-Id-of-the-new-pool" }
	poolIDMessage := apiResponse.GetFieldMessage("poolId")
	if poolIDMessage == nil {
		return "", apiResponse.ToError("Received an unexpected response (missing 'poolId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return *poolIDMessage, nil
}

// EditVIPPool updates an existing VIP pool.
func (client *Client) EditVIPPool(id string, poolConfiguration EditVIPPoolConfiguration) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	editPoolConfiguration := &poolConfiguration
	editPoolConfiguration.ID = id

	requestURI := fmt.Sprintf("%s/networkDomainVip/editPool", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, editPoolConfiguration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if statusCode != http.StatusOK {
		return apiResponse.ToError("Request to edit VIP pool '%s' failed with status code %d (%s): %s", poolConfiguration.ID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}

// DeleteVIPPool deletes an existing VIP pool.
// Returns an error if the operation was not successful.
func (client *Client) DeleteVIPPool(id string) (err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/deletePool", organizationID)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &deleteVIPPool{id})
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to delete VIP pool '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}
<commit_msg>Escape query parameters for VIP pool request URIs (#7).<commit_after>package compute

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

const (
	// ServiceDownActionNone indicates no action will be taken when a pool service is down.
	ServiceDownActionNone = "NONE"

	// ServiceDownActionDrop indicates that a pool service will be dropped when it is down.
	ServiceDownActionDrop = "DROP"

	// ServiceDownActionReselect indicates that a pool service will be reselected when it is down.
	ServiceDownActionReselect = "RESELECT"

	// LoadBalanceMethodRoundRobin indicates that requests will be directed to pool nodes in round-robin fashion.
	LoadBalanceMethodRoundRobin = "ROUND_ROBIN"

	// LoadBalanceMethodLeastConnectionsNode indicates that requests will be directed to the pool node that has the smallest number of active connections at the moment of connection.
	// All connections to the node are considered.
	LoadBalanceMethodLeastConnectionsNode = "LEAST_CONNECTIONS_NODE"

	// LoadBalanceMethodLeastConnectionsMember indicates that requests will be directed to the pool node that has the smallest number of active connections at the moment of connection.
	// Only connections to the node as a member of the current pool are considered.
	LoadBalanceMethodLeastConnectionsMember = "LEAST_CONNECTIONS_MEMBER"

	// LoadBalanceMethodObservedNode indicates that requests will be directed to the pool node that has the smallest number of active connections over time.
	// All connections to the node are considered.
	LoadBalanceMethodObservedNode = "OBSERVED_NODE"

	// LoadBalanceMethodObservedMember indicates that requests will be directed to the pool node that has the smallest number of active connections over time.
	// Only connections to the node as a member of the current pool are considered.
	LoadBalanceMethodObservedMember = "OBSERVED_MEMBER"

	// LoadBalanceMethodPredictiveNode indicates that requests will be directed to the pool node that is predicted to have the smallest number of active connections.
	// All connections to the pool are considered.
	LoadBalanceMethodPredictiveNode = "PREDICTIVE_NODE"

	// LoadBalanceMethodPredictiveMember indicates that requests will be directed to the pool node that is predicted to have the smallest number of active connections over time.
	// Only connections to the pool as a member of the current pool are considered.
	LoadBalanceMethodPredictiveMember = "PREDICTIVE_MEMBER"
)

// VIPPool represents a VIP pool.
type VIPPool struct {
	ID                string            `json:"id"`
	Name              string            `json:"name"`
	Description       string            `json:"description"`
	LoadBalanceMethod string            `json:"loadBalanceMethod"`
	HealthMonitors    []EntityReference `json:"healthMonitor"`
	ServiceDownAction string            `json:"serviceDownAction"`
	SlowRampTime      int               `json:"slowRampTime"`
	State             string            `json:"state"`
	NetworkDomainID   string            `json:"networkDomainID"`
	DataCenterID      string            `json:"datacenterId"`
	CreateTime        string            `json:"createTime"`
}

// GetID returns the pool's Id.
func (pool *VIPPool) GetID() string {
	return pool.ID
}

// GetResourceType returns the pool's resource type.
func (pool *VIPPool) GetResourceType() ResourceType {
	return ResourceTypeVIPPool
}

// GetName returns the pool's name.
func (pool *VIPPool) GetName() string {
	return pool.Name
}

// GetState returns the pool's current state.
func (pool *VIPPool) GetState() string {
	return pool.State
}

// IsDeleted determines whether the pool has been deleted (is nil).
func (pool *VIPPool) IsDeleted() bool {
	return pool == nil
}

var _ Resource = &VIPPool{}

// ToEntityReference creates an EntityReference representing the VIPNode.
func (pool *VIPPool) ToEntityReference() EntityReference {
	return EntityReference{
		ID:   pool.ID,
		Name: pool.Name,
	}
}

var _ NamedEntity = &VIPNode{}

// VIPPools represents a page of VIPPool results.
type VIPPools struct {
	Items []VIPPool

	PagedResult
}

// NewVIPPoolConfiguration represents the configuration for a new VIP pool.
type NewVIPPoolConfiguration struct {
	// The VIP pool name.
	Name string `json:"name"`

	// The VIP pool description.
	Description string `json:"description"`

	// The load-balancing method used for pools in the pool.
	LoadBalanceMethod string `json:"loadBalanceMethod"`

	// The Id of the pool's associated health monitors (if any).
	// Up to 2 health monitors can be specified per pool.
	HealthMonitorIDs []string `json:"healthMonitorId,omitempty"`

	// The action performed when a pool in the pool is down.
	ServiceDownAction string `json:"serviceDownAction"`

	// The time, in seconds, over which the the pool will ramp new pools up to their full request rate.
	SlowRampTime int `json:"slowRampTime"`

	// The Id of the network domain where the pool is located.
	NetworkDomainID string `json:"networkDomainId"`

	// The Id of the data centre where the pool is located.
	DatacenterID string `json:"datacenterId,omitempty"`
}

// EditVIPPoolConfiguration represents the request body when editing a VIP pool.
type EditVIPPoolConfiguration struct {
	// The VIP pool Id.
	ID string `json:"id"`

	// The VIP pool description.
	Description *string `json:"description,omitempty"`

	// The load-balancing method used for pools in the pool.
	LoadBalanceMethod *string `json:"loadBalanceMethod"`

	// The Id of the pool's associated health monitors (if any).
	// Up to 2 health monitors can be specified per pool.
	HealthMonitorIDs *[]string `json:"healthMonitorId,omitempty"`

	// The action performed when a pool in the pool is down.
	ServiceDownAction *string `json:"serviceDownAction"`

	// The time, in seconds, over which the the pool will ramp new pools up to their full request rate.
	SlowRampTime *int `json:"slowRampTime"`
}

// Request body for deleting a VIP pool.
type deleteVIPPool struct {
	// The VIP pool ID.
	ID string `json:"id"`
}

// ListVIPPoolsInNetworkDomain retrieves a list of all VIP pools in the specified network domain.
func (client *Client) ListVIPPoolsInNetworkDomain(networkDomainID string, paging *Paging) (pools *VIPPools, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/pool?networkDomainId=%s&%s",
		url.QueryEscape(organizationID),
		url.QueryEscape(networkDomainID),
		paging.EnsurePaging().toQueryParameters(),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}

	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		return nil, apiResponse.ToError("Request to list VIP pools in network domain '%s' failed with status code %d (%s): %s", networkDomainID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	pools = &VIPPools{}
	err = json.Unmarshal(responseBody, pools)
	if err != nil {
		return nil, err
	}

	return pools, nil
}

// GetVIPPool retrieves the VIP pool with the specified Id.
// Returns nil if no VIP pool is found with the specified Id.
func (client *Client) GetVIPPool(id string) (pool *VIPPool, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return nil, err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/pool/%s",
		url.QueryEscape(organizationID),
		url.QueryEscape(id),
	)
	request, err := client.newRequestV22(requestURI, http.MethodGet, nil)
	if err != nil {
		return nil, err
	}
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return nil, err
	}

	if statusCode != http.StatusOK {
		var apiResponse *APIResponseV2

		apiResponse, err = readAPIResponseAsJSON(responseBody, statusCode)
		if err != nil {
			return nil, err
		}

		if apiResponse.ResponseCode == ResponseCodeResourceNotFound {
			return nil, nil // Not an error, but was not found.
		}

		return nil, apiResponse.ToError("Request to retrieve VIP pool with Id '%s' failed with status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	pool = &VIPPool{}
	err = json.Unmarshal(responseBody, pool)
	if err != nil {
		return nil, err
	}

	return pool, nil
}

// CreateVIPPool creates a new VIP pool.
// Returns the Id of the new pool.
func (client *Client) CreateVIPPool(poolConfiguration NewVIPPoolConfiguration) (poolID string, err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return "", err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/createPool",
		url.QueryEscape(organizationID),
	)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &poolConfiguration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return "", err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return "", err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return "", apiResponse.ToError("Request to create VIP pool '%s' failed with status code %d (%s): %s", poolConfiguration.Name, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	// Expected: "info" { "name": "poolId", "value": "the-Id-of-the-new-pool" }
	poolIDMessage := apiResponse.GetFieldMessage("poolId")
	if poolIDMessage == nil {
		return "", apiResponse.ToError("Received an unexpected response (missing 'poolId') with status code %d (%s): %s", statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return *poolIDMessage, nil
}

// EditVIPPool updates an existing VIP pool.
func (client *Client) EditVIPPool(id string, poolConfiguration EditVIPPoolConfiguration) error {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	editPoolConfiguration := &poolConfiguration
	editPoolConfiguration.ID = id

	requestURI := fmt.Sprintf("%s/networkDomainVip/editPool",
		url.QueryEscape(organizationID),
	)
	request, err := client.newRequestV22(requestURI, http.MethodPost, editPoolConfiguration)
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if statusCode != http.StatusOK {
		return apiResponse.ToError("Request to edit VIP pool '%s' failed with status code %d (%s): %s", poolConfiguration.ID, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}

// DeleteVIPPool deletes an existing VIP pool.
// Returns an error if the operation was not successful.
func (client *Client) DeleteVIPPool(id string) (err error) {
	organizationID, err := client.getOrganizationID()
	if err != nil {
		return err
	}

	requestURI := fmt.Sprintf("%s/networkDomainVip/deletePool",
		url.QueryEscape(organizationID),
	)
	request, err := client.newRequestV22(requestURI, http.MethodPost, &deleteVIPPool{id})
	responseBody, statusCode, err := client.executeRequest(request)
	if err != nil {
		return err
	}

	apiResponse, err := readAPIResponseAsJSON(responseBody, statusCode)
	if err != nil {
		return err
	}

	if apiResponse.ResponseCode != ResponseCodeOK {
		return apiResponse.ToError("Request to delete VIP pool '%s' failed with unexpected status code %d (%s): %s", id, statusCode, apiResponse.ResponseCode, apiResponse.Message)
	}

	return nil
}
<|endoftext|>


<commit_before>package config

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"os"
	"os/user"
	"sort"
)

// WARNING: never directly use this variable before calling getUserHome()
var configFilePath = "/.config/tfanc.json"

func getUserHome() error {
	cU, err := user.Current()
	if err != nil {
		return err
	}

	configFilePath = cU.HomeDir + configFilePath

	return nil
}

// LoadConfig loads a configuration file from the standard path, defined by "configFilePath"
func LoadConfig() (Configuration, error) {

	if err := getUserHome(); err != nil {
		return Configuration{}, err
	}

	f, _ := os.Open(configFilePath)
	defer f.Close()

	fReader := bufio.NewReader(f)

	buf := new(bytes.Buffer)
	buf.ReadFrom(fReader)

	var m Configuration
	json.Unmarshal(buf.Bytes(), &m)

	//
	// Configuration file sanity checks
	//

	// The first condition is true only if "targets" in the json file is not even defined, so
	// we'll check for array length too.
	if m.Targets == nil || len(m.Targets) == 0 {
		return Configuration{}, errors.New("configuration error: no \"targets\" field defined, cannot continue without. Check your configuration file, it's malformed")
	}

	// Assuming from here that our configuration file contains at least one target, we need to correctly
	// parse what the user was thinking when writing the configuration file aka sort all the Targets.Range contents to correctly
	// represent a range. [0] > [1]
	for _, target := range m.Targets {
		if !(target.Range[0] < target.Range[1]) {
			sort.Ints(target.Range)
		}
	}

	// Return everything!
	return m, nil
}
<commit_msg>Now the configuration file resides in /etc/tfanc/tfanc.json<commit_after>package config

import (
	"bufio"
	"bytes"
	"encoding/json"
	"errors"
	"os"
	"sort"
)

// WARNING: never directly use this variable before calling getUserHome()
var configFilePath = "/etc/tfanc/tfanc.json"

// LoadConfig loads a configuration file from the standard path, defined by "configFilePath"
func LoadConfig() (Configuration, error) {

	f, err := os.Open(configFilePath)
	defer f.Close()

	if err != nil {
		return Configuration{}, err
	}

	fReader := bufio.NewReader(f)

	buf := new(bytes.Buffer)
	buf.ReadFrom(fReader)

	var m Configuration
	json.Unmarshal(buf.Bytes(), &m)

	//
	// Configuration file sanity checks
	//

	// The first condition is true only if "targets" in the json file is not even defined, so
	// we'll check for array length too.
	if m.Targets == nil || len(m.Targets) == 0 {
		return Configuration{}, errors.New("configuration error: no \"targets\" field defined, cannot continue without. Check your configuration file, it's malformed")
	}

	// Assuming from here that our configuration file contains at least one target, we need to correctly
	// parse what the user was thinking when writing the configuration file aka sort all the Targets.Range contents to correctly
	// represent a range. [0] > [1]
	for _, target := range m.Targets {
		if !(target.Range[0] < target.Range[1]) {
			sort.Ints(target.Range)
		}
	}

	// Return everything!
	return m, nil
}
<|endoftext|>


<commit_before>
package main

import "fmt"
import "nmea"

func main() {
  sent := "$GPGSA,A,3,,,,,,16,18,,22,24,,,3.6,2.1,2.2*3C"
  s, err := nmea.Parse(sent)
  if err != nil {
    fmt.Printf("Error parsing: %s\n", err)
    return
  }

  switch t := s.(type) {
  case nmea.GPGGA:
    fmt.Printf("A GPGGA: %T\n", t)
  case nmea.GPGSA:
    fmt.Printf("A GPGSA: %T\n", t)
  }

  g, ok := s.(nmea.GPGSA)
  if !ok {
    fmt.Printf("not a GPGSA, it's a %T\n", s)
    return
  }

  fmt.Printf("%s\n", g.Raw)
  fmt.Printf("> %s\n", g.SType)
  fmt.Printf("%q\n", g.Fields)

  fmt.Printf("Mode: %s/%s\n", g.Mode, g.FixType)
  fmt.Printf("SVs: %v\n", g.SV)
  fmt.Printf("PDOP: %s\n", g.PDOP)
  fmt.Printf("HDOP: %s\n", g.HDOP)
  fmt.Printf("VDOP: %s\n", g.VDOP)
}
<commit_msg>removed nmea.go main func<commit_after><|endoftext|>


<commit_before>package scanln

import (
	"fmt"
	"time"
)

/*
REVISION HISTORY
-------- -------
24 Sep 22 -- First version.
28 Sep 22 -- Expanded on the comment in WithTimeOut below.
*/

const lastAltered = "Sep 28, 2022"
const maxTimeout = 10

// WithTimeout (timeOut int) string -- timeOut is in seconds.
// If it times out or <enter> is hit before the timeout, then it will return an empty string.
func WithTimeout(timeOut int) string {
	var ans string
	// Note that the buffer size of 1 is necessary to avoid deadlock of goroutines and guarantee garbage collection of the timeout channel.
	// On closer inspection, the go routine will send 2 strings down the channel if there's a timeout or I hit enter.  But there's only one read from the channel, so the
	// extra string sits in the buffer and is garbage collected when this routine returns to the caller.
	strChannel := make(chan string, 1)
	defer close(strChannel)
	ticker := time.NewTicker(1 * time.Second)
	if timeOut > maxTimeout {
		timeOut = maxTimeout
	}
	ticks := timeOut
	go func() {
		n, err := fmt.Scanln(&ans)
		if n == 0 || err != nil {
			strChannel <- ""
		}
		strChannel <- ans
	}()

	for {
		select {
		case <-ticker.C:
			if ticks < 1 {
				ticker.Stop()
				return ""
			}
			fmt.Printf("\r %d second(s)   ", ticks)
			ticks--

		case s := <-strChannel:
			return s
		}
	}
} // scanlnWithTimeout

// WithTimeoutAndPrompt (prompt string, timeOut int) string
// This uses Printf for the prompt, and then calls WithTimeout.
func WithTimeoutAndPrompt(prompt string, timeOut int) string {
	if len(prompt) > 0 {
		fmt.Printf(" %s \n", prompt)
	}
	return WithTimeout(timeOut)
}

/*
func main() {
	var err error
	fmt.Printf(" scanlineWithTimeout test last altered %s, len(os.Args) = %d.\n", lastAltered, len(os.Args))
	timeout := 0
	if len(os.Args) > 1 { // param is entered
		timeout, err = strconv.Atoi(os.Args[1])
		if err != nil {
			fmt.Printf(" Error from Atoi call is %v, timeout set to max of %d.\n", err, maxTimeout)
			timeout = maxTimeout
		}
	} else {
		var toutStr string // abbreviation for timeout string
		fmt.Printf(" Enter value for the timeout: ")
		n, er := fmt.Scanln(&toutStr)
		if n == 0 || er != nil {
			timeout = maxTimeout
		} else {
			timeout, err = strconv.Atoi(toutStr)
			if err != nil {
				fmt.Printf(" Error from Atoi call is %v, timeout set to max of %d.\n", err, maxTimeout)
				timeout = maxTimeout
			}
		}
		fmt.Printf(" Entered %s, timeout = %d\n", toutStr, timeout)
	}

	returnedString := scanlnWithTimeout("enter something before it times out", timeout)
	fmt.Printf(" returnedString is %q\n", returnedString)
}

*/
<commit_msg>09/28/2022 16:48:30 scanln/scanln.go<commit_after>package scanln

import (
	"fmt"
	"time"
)

/*
REVISION HISTORY
-------- -------
24 Sep 22 -- First version.
28 Sep 22 -- Expanded on the comment in WithTimeOut below.  And fixed an error in logic.
*/

const lastAltered = "Sep 28, 2022"
const maxTimeout = 10

// WithTimeout (timeOut int) string -- timeOut is in seconds.
// If it times out or <enter> is hit before the timeout, then it will return an empty string.
func WithTimeout(timeOut int) string {
	var ans string
	// Note that the buffer size of 1 is necessary to avoid deadlock of goroutines and guarantee garbage collection of the timeout channel.
	// On closer inspection, the go routine will send 2 strings down the channel if there's a timeout or I hit enter.  But there's only one read from the channel, so the
	// extra string sits in the buffer and is garbage collected when this routine returns to the caller.
	// Nevermind, I fixed this by using an else clause.
	strChannel := make(chan string, 1)
	defer close(strChannel)
	ticker := time.NewTicker(1 * time.Second)
	if timeOut > maxTimeout {
		timeOut = maxTimeout
	}
	ticks := timeOut
	go func() {
		n, err := fmt.Scanln(&ans)
		if n == 0 || err != nil {
			strChannel <- ""
		} else {
			strChannel <- ans
		}
	}()

	for {
		select {
		case <-ticker.C:
			if ticks < 1 {
				ticker.Stop()
				return ""
			}
			fmt.Printf("\r %d second(s)   ", ticks)
			ticks--

		case s := <-strChannel:
			return s
		}
	}
} // scanlnWithTimeout

// WithTimeoutAndPrompt (prompt string, timeOut int) string
// This uses Printf for the prompt, and then calls WithTimeout.
func WithTimeoutAndPrompt(prompt string, timeOut int) string {
	if len(prompt) > 0 {
		fmt.Printf(" %s \n", prompt)
	}
	return WithTimeout(timeOut)
}

/*
func main() {
	var err error
	fmt.Printf(" scanlineWithTimeout test last altered %s, len(os.Args) = %d.\n", lastAltered, len(os.Args))
	timeout := 0
	if len(os.Args) > 1 { // param is entered
		timeout, err = strconv.Atoi(os.Args[1])
		if err != nil {
			fmt.Printf(" Error from Atoi call is %v, timeout set to max of %d.\n", err, maxTimeout)
			timeout = maxTimeout
		}
	} else {
		var toutStr string // abbreviation for timeout string
		fmt.Printf(" Enter value for the timeout: ")
		n, er := fmt.Scanln(&toutStr)
		if n == 0 || er != nil {
			timeout = maxTimeout
		} else {
			timeout, err = strconv.Atoi(toutStr)
			if err != nil {
				fmt.Printf(" Error from Atoi call is %v, timeout set to max of %d.\n", err, maxTimeout)
				timeout = maxTimeout
			}
		}
		fmt.Printf(" Entered %s, timeout = %d\n", toutStr, timeout)
	}

	returnedString := scanlnWithTimeout("enter something before it times out", timeout)
	fmt.Printf(" returnedString is %q\n", returnedString)
}

*/
<|endoftext|>


<commit_before>package schedule

import (
	"errors"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/robfig/cron"

	"github.com/slok/khronos/config"
	"github.com/slok/khronos/job"
	"github.com/slok/khronos/storage"
)

// Cron is be the registerer of jobs
type Cron struct {
	// runner is the lowlevel cron that runs the jobs on separate gorutines
	runner *cron.Cron

	// Scheduler is the scheduler (chain of schedulers) that will be wrapped around the job
	// see `schedule/run` for the available default schedulers
	scheduler Scheduler

	// Results is a blocking channel where the crons will post their results as
	// a notification event of finished (ideally the actions of a finish result should
	// be in the chain of the scheduler (see `scheduler/run`)
	Results chan *job.Result

	// started flag is up if any other cron is up
	started    bool
	startMutex *sync.Mutex

	// loaded flag is up when stored jobs from database are loaded
	storedlJobsLoaded bool

	// application context
	cfg *config.AppConfig

	// Storage client
	storage storage.Client
}

// NewSimpleCron creates a new instance of a cron initialized with the basic functionality
func NewSimpleCron(cfg *config.AppConfig, storage storage.Client) *Cron {
	return &Cron{
		runner:            cron.New(),
		scheduler:         SimpleRun(),
		Results:           make(chan *job.Result, cfg.ResultBufferLen),
		started:           false,
		startMutex:        &sync.Mutex{},
		storedlJobsLoaded: false,
		cfg:               cfg,
		storage:           storage,
	}
}

// NewDummyCron creates a new instance of a cron that will execute dummy jobs
// (do nothing) when the time comes
func NewDummyCron(cfg *config.AppConfig, storage storage.Client, exitStatus int, out string) *Cron {
	return &Cron{
		runner:            cron.New(),
		scheduler:         DummyRun(exitStatus, out),
		Results:           make(chan *job.Result, cfg.ResultBufferLen),
		started:           false,
		startMutex:        &sync.Mutex{},
		storedlJobsLoaded: false,
		cfg:               cfg,
		storage:           storage,
	}
}

// startResultProcesser starts the processor for the results (runs in a goroutine)
func (c *Cron) startResultProcesser(f func(*job.Result)) error {
	// Started already aquired from Start
	if c.started {
		return errors.New("Already running")
	}

	// Apply default logic for default processing
	if f == nil {
		f = func(r *job.Result) {
			logrus.Debugf("received result from job '%d' with:\nstatus:%d;\nOutput:%s", r.Job.ID, r.Status, r.Out)

			// Save result
			err := c.storage.SaveResult(r)
			if err != nil {
				logrus.Errorf("error saving result '%d' from job '%d'", r.ID, r.Job.ID)
			}
			logrus.Debugf("Saved result '%d' for job id '%d'", r.ID, r.Job.ID)
			// TODO: apply result processing logic
		}
	}

	logrus.Info("Result processing started...")
	// Start job runner in a gouroutine. This anom func will execute the received
	// func for each result
	go func() {
		for r := range c.Results {
			f(r)
		}
	}()
	return nil
}

// Start starts cron job scheduler
// starts the result listener.
// loads stored jobs from database if DontScheduleJobsStart option is disabled
// f parameter is the function that will be executed for each result, could be nil.
func (c *Cron) Start(f func(*job.Result)) error {
	c.startMutex.Lock()
	defer c.startMutex.Unlock()

	if c.started {
		return errors.New("Already running")
	}

	// Start the cron runner
	c.runner.Start()

	// Start the result processor
	if err := c.startResultProcesser(f); err != nil {
		return err
	}

	// Register database cron jobs
	if !c.cfg.DontScheduleJobsStart && !c.storedlJobsLoaded {
		if err := c.registerStoredCronJobs(); err != nil {
			return err
		}
		c.storedlJobsLoaded = true
	}

	c.started = true
	return nil
}

// Stop stops cron job scheduler and result listener
func (c *Cron) Stop() error {
	c.startMutex.Lock()
	defer c.startMutex.Unlock()

	if !c.started {
		return errors.New("Not running")
	}
	c.runner.Stop()
	close(c.Results)
	c.started = false
	return nil
}

// RegisterCronJob registers a cron to be run when its it's time
func (c *Cron) RegisterCronJob(j *job.Job) {
	logrus.Debugf("Registering cron job: '%d'", j.ID)

	// Wrap the execution of job
	jobExec := func() {
		logrus.Debugf("Start running cron '%d' at %v", j.ID, time.Now().UTC())
		r := &job.Result{Job: j}
		c.scheduler.Run(r, j)
		c.Results <- r
		logrus.Debugf("Finished running cron '%d' at %v", j.ID, time.Now().UTC())
	}

	// Add job to  cron
	c.runner.AddFunc(j.When, jobExec)
}

// registerStoredCronJobs registers all the stored cron jobs
func (c *Cron) registerStoredCronJobs() error {
	logrus.Debug("Registering stored jobs")

	// Get storage jobs
	js, err := c.storage.GetJobs(0, 0)
	if err != nil {
		return err
	}

	// Register all the jobs
	for _, j := range js {
		// TODO: Register only active ones (check or retrieve only active ones from database?)
		c.RegisterCronJob(j)
	}
	return nil
}
<commit_msg>Fix results channel start. Channel created on instance creation but closed on stop, if starting again the instance the channel was already closed, being a nil channel and when sending a result panicking the application because of a closed channel<commit_after>package schedule

import (
	"errors"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/robfig/cron"

	"github.com/slok/khronos/config"
	"github.com/slok/khronos/job"
	"github.com/slok/khronos/storage"
)

// Cron is be the registerer of jobs
type Cron struct {
	// runner is the lowlevel cron that runs the jobs on separate gorutines
	runner *cron.Cron

	// Scheduler is the scheduler (chain of schedulers) that will be wrapped around the job
	// see `schedule/run` for the available default schedulers
	scheduler Scheduler

	// Results is a blocking channel where the crons will post their results as
	// a notification event of finished (ideally the actions of a finish result should
	// be in the chain of the scheduler (see `scheduler/run`)
	Results chan *job.Result

	// started flag is up if any other cron is up
	started    bool
	startMutex *sync.Mutex

	// loaded flag is up when stored jobs from database are loaded
	storedlJobsLoaded bool

	// application context
	cfg *config.AppConfig

	// Storage client
	storage storage.Client
}

// NewSimpleCron creates a new instance of a cron initialized with the basic functionality
func NewSimpleCron(cfg *config.AppConfig, storage storage.Client) *Cron {
	return &Cron{
		runner:            cron.New(),
		scheduler:         SimpleRun(),
		Results:           nil, // Create on startResultProcesser and close on stop
		started:           false,
		startMutex:        &sync.Mutex{},
		storedlJobsLoaded: false,
		cfg:               cfg,
		storage:           storage,
	}
}

// NewDummyCron creates a new instance of a cron that will execute dummy jobs
// (do nothing) when the time comes
func NewDummyCron(cfg *config.AppConfig, storage storage.Client, exitStatus int, out string) *Cron {
	return &Cron{
		runner:            cron.New(),
		scheduler:         DummyRun(exitStatus, out),
		Results:           nil, // Create on startResultProcesser and close on stop
		started:           false,
		startMutex:        &sync.Mutex{},
		storedlJobsLoaded: false,
		cfg:               cfg,
		storage:           storage,
	}
}

// startResultProcesser starts the processor for the results (runs in a goroutine)
func (c *Cron) startResultProcesser(f func(*job.Result)) error {
	// Started already aquired from Start
	if c.started {
		return errors.New("Already running")
	}

	// Apply default logic for default processing
	if f == nil {
		f = func(r *job.Result) {
			logrus.Debugf("received result from job '%d' with:\nstatus:%d;\nOutput:%s", r.Job.ID, r.Status, r.Out)

			// Save result
			err := c.storage.SaveResult(r)
			if err != nil {
				logrus.Errorf("error saving result '%d' from job '%d'", r.ID, r.Job.ID)
			}
			logrus.Debugf("Saved result '%d' for job id '%d'", r.ID, r.Job.ID)
			// TODO: apply result processing logic
		}
	}

	logrus.Info("Result processing started...")

	// Create results channel (will be closed on stop)
	c.Results = make(chan *job.Result, c.cfg.ResultBufferLen)

	// Start job runner in a gouroutine. This anom func will execute the received
	// func for each result
	go func() {
		for r := range c.Results {
			f(r)
		}
	}()
	return nil
}

// Start starts cron job scheduler
// starts the result listener.
// loads stored jobs from database if DontScheduleJobsStart option is disabled
// f parameter is the function that will be executed for each result, could be nil.
func (c *Cron) Start(f func(*job.Result)) error {
	c.startMutex.Lock()
	defer c.startMutex.Unlock()

	if c.started {
		return errors.New("Already running")
	}

	// Start the cron runner
	c.runner.Start()

	// Start the result processor
	if err := c.startResultProcesser(f); err != nil {
		return err
	}

	// Register database cron jobs
	if !c.cfg.DontScheduleJobsStart && !c.storedlJobsLoaded {
		if err := c.registerStoredCronJobs(); err != nil {
			return err
		}
		c.storedlJobsLoaded = true
	}

	c.started = true
	return nil
}

// Stop stops cron job scheduler and result listener
func (c *Cron) Stop() error {
	c.startMutex.Lock()
	defer c.startMutex.Unlock()

	if !c.started {
		return errors.New("Not running")
	}
	c.runner.Stop()
	close(c.Results)
	c.started = false
	return nil
}

// RegisterCronJob registers a cron to be run when its it's time
func (c *Cron) RegisterCronJob(j *job.Job) {
	logrus.Debugf("Registering cron job: '%d'", j.ID)

	// Wrap the execution of job
	jobExec := func() {
		logrus.Debugf("Start running cron '%d' at %v", j.ID, time.Now().UTC())
		r := &job.Result{Job: j}
		c.scheduler.Run(r, j)
		c.Results <- r
		logrus.Debugf("Finished running cron '%d' at %v", j.ID, time.Now().UTC())
	}

	// Add job to  cron
	c.runner.AddFunc(j.When, jobExec)
}

// registerStoredCronJobs registers all the stored cron jobs
func (c *Cron) registerStoredCronJobs() error {
	logrus.Debug("Registering stored jobs")

	// Get storage jobs
	js, err := c.storage.GetJobs(0, 0)
	if err != nil {
		return err
	}

	// Register all the jobs
	for _, j := range js {
		// TODO: Register only active ones (check or retrieve only active ones from database?)
		c.RegisterCronJob(j)
	}
	return nil
}
<|endoftext|>


<commit_before>package schedule

import (
	"sync"
	"time"
)

type Task struct {
	schResponseChan chan ScheduleResponse
	killChan        chan struct{}
	schedule        Schedule
	mu              sync.Mutex //protects state
	state           TaskState
	creationTime    time.Time
}

type TaskState int

const (
	//Task states
	TaskStopped TaskState = iota
	TaskSpinning
	TaskFiring
)

func NewTask(s Schedule) *Task {
	return &Task{
		schResponseChan: make(chan ScheduleResponse),
		killChan:        make(chan struct{}),
		schedule:        s,
		state:           TaskStopped,
		creationTime:    time.Now(),
	}
}

func (t *Task) Spin() {
	// We need to lock long enough to change state
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.state == TaskStopped {
		t.state = TaskSpinning
		// spin in a goroutine
		go t.spin()
	}
}

func (t *Task) Stop() {
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.state != TaskStopped {
		t.killChan <- struct{}{}
	}
}

func (t *Task) spin() {
	for {
		// Start go routine to wait on schedule
		go t.waitForSchedule()
		// wait here on
		//  schResponseChan - response from schedule
		//  killChan - signals task needs to be stopped
		select {
		case sr := <-t.schResponseChan:
			// If response show this schedule is stil active we fire
			if sr.State() == ScheduleActive {
				t.fire()
			}
			// TODO stop task on schedule error state or end state
		case <-t.killChan:
			// Only here can it truly be stopped
			t.state = TaskStopped
			break
		}
	}
}

func (t *Task) fire() {
	t.mu.Lock()
	defer t.mu.Unlock()

	// eventual worker manager goes here
	// simulate work by just a 500ms sleep
	t.state = TaskFiring
	time.Sleep(time.Millisecond * 500)
	t.state = TaskSpinning
}

func (t *Task) waitForSchedule() {
	t.schResponseChan <- t.schedule.Wait(t.creationTime)
}
<commit_msg>schedule/task - Addition of tracking the last time a task fired and passing this into schedules on Wait()<commit_after>package schedule

import (
	"sync"
	"time"
)

type Task struct {
	schResponseChan chan ScheduleResponse
	killChan        chan struct{}
	schedule        Schedule
	mu              sync.Mutex //protects state
	state           TaskState
	creationTime    time.Time
	lastFireTime    time.Time
}

type TaskState int

const (
	//Task states
	TaskStopped TaskState = iota
	TaskSpinning
	TaskFiring
)

func NewTask(s Schedule) *Task {
	return &Task{
		schResponseChan: make(chan ScheduleResponse),
		killChan:        make(chan struct{}),
		schedule:        s,
		state:           TaskStopped,
		creationTime:    time.Now(),
		lastFireTime:    time.Now(),
	}
}

func (t *Task) Spin() {
	// We need to lock long enough to change state
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.state == TaskStopped {
		t.state = TaskSpinning
		// spin in a goroutine
		go t.spin()
	}
}

func (t *Task) Stop() {
	t.mu.Lock()
	defer t.mu.Unlock()
	if t.state != TaskStopped {
		t.killChan <- struct{}{}
	}
}

func (t *Task) spin() {
	for {
		// Start go routine to wait on schedule
		go t.waitForSchedule()
		// wait here on
		//  schResponseChan - response from schedule
		//  killChan - signals task needs to be stopped
		select {
		case sr := <-t.schResponseChan:
			// If response show this schedule is stil active we fire
			if sr.State() == ScheduleActive {
				t.lastFireTime = time.Now()
				t.fire()
			}
			// TODO stop task on schedule error state or end state
		case <-t.killChan:
			// Only here can it truly be stopped
			t.state = TaskStopped
			break
		}
	}
}

func (t *Task) fire() {
	t.mu.Lock()
	defer t.mu.Unlock()

	// eventual worker manager goes here
	// simulate work by just a 500ms sleep
	t.state = TaskFiring
	time.Sleep(time.Millisecond * 500)
	t.state = TaskSpinning
}

func (t *Task) waitForSchedule() {
	t.schResponseChan <- t.schedule.Wait(t.lastFireTime)
}
<|endoftext|>


<commit_before>package taggolib

import (
	"bytes"
	"encoding/binary"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"time"

	"github.com/eaburns/bit"
)

var (
	// oggMagicNumber is the magic number used to identify an OGG container audio stream
	oggMagicNumber = []byte("OggS")
	// oggVorbisVorbisWord is used to denote the beginning of a Vorbis information block
	oggVorbisVorbisWord = []byte("vorbis")
)

// oggVorbisParser represents a OGGVorbis audio metadata tag parser
type oggVorbisParser struct {
	duration time.Duration
	encoder  string
	idHeader *oggVorbisIDHeader
	reader   io.ReadSeeker
	tags     map[string]string

	// Shared buffer and unsigned integers stored as fields to prevent unneeded allocations
	buffer []byte
	ui8    uint8
	ui32   uint32
	ui64   uint64
}

// Album returns the Album tag for this stream
func (o oggVorbisParser) Album() string {
	return o.tags[tagAlbum]
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (o oggVorbisParser) AlbumArtist() string {
	return o.tags[tagAlbumArtist]
}

// Artist returns the Artist tag for this stream
func (o oggVorbisParser) Artist() string {
	return o.tags[tagArtist]
}

// BitDepth returns the bits-per-sample of this stream
func (o oggVorbisParser) BitDepth() int {
	// Ogg Vorbis should always provide 16 bit depth
	return 16
}

// Bitrate calculates the audio bitrate for this stream
func (o oggVorbisParser) Bitrate() int {
	// TODO: see how max/min bitrate play into calculations
	return int(o.idHeader.NomBitrate) / 1000
}

// Channels returns the number of channels for this stream
func (o oggVorbisParser) Channels() int {
	return int(o.idHeader.ChannelCount)
}

// Comment returns the Comment tag for this stream
func (o oggVorbisParser) Comment() string {
	return o.tags[tagComment]
}

// Date returns the Date tag for this stream
func (o oggVorbisParser) Date() string {
	return o.tags[tagDate]
}

// DiscNumber returns the DiscNumber tag for this stream
func (o oggVorbisParser) DiscNumber() int {
	disc, err := strconv.Atoi(o.tags[tagDiscNumber])
	if err != nil {
		return 0
	}

	return disc
}

// Duration returns the time duration for this stream
func (o oggVorbisParser) Duration() time.Duration {
	return o.duration
}

// Encoder returns the encoder for this stream
func (o oggVorbisParser) Encoder() string {
	return o.encoder
}

// Format returns the name of the OGGVorbis format
func (o oggVorbisParser) Format() string {
	return "OGGVorbis"
}

// Genre returns the Genre tag for this stream
func (o oggVorbisParser) Genre() string {
	return o.tags[tagGenre]
}

// SampleRate returns the sample rate in Hertz for this stream
func (o oggVorbisParser) SampleRate() int {
	return int(o.idHeader.SampleRate)
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (o oggVorbisParser) Tag(name string) string {
	return o.tags[name]
}

// Title returns the Title tag for this stream
func (o oggVorbisParser) Title() string {
	return o.tags[tagTitle]
}

// TrackNumber returns the TrackNumber tag for this stream
func (o oggVorbisParser) TrackNumber() int {
	// Check for a /, such as 2/8
	track, err := strconv.Atoi(strings.Split(o.tags[tagTrackNumber], "/")[0])
	if err != nil {
		return 0
	}

	return track
}

// newOGGVorbisParser creates a parser for OGGVorbis audio streams
func newOGGVorbisParser(reader io.ReadSeeker) (*oggVorbisParser, error) {
	// Create OGGVorbis parser
	parser := &oggVorbisParser{
		buffer: make([]byte, 128),
		reader: reader,
	}

	// Parse the required ID header
	if err := parser.parseOGGVorbisIDHeader(); err != nil {
		return nil, err
	}

	// Parse the required comment header
	if err := parser.parseOGGVorbisCommentHeader(); err != nil {
		return nil, err
	}

	// Parse the file's duration
	if err := parser.parseOGGVorbisDuration(); err != nil {
		return nil, err
	}

	// Return parser
	return parser, nil
}

// oggVorbisPageHeader represents the information contained in an Ogg Page header
type oggVorbisPageHeader struct {
	CapturePattern  []byte
	Version         uint8
	HeaderType      uint8
	GranulePosition uint64
	BitstreamSerial uint32
	PageSequence    uint32
	Checksum        []byte
	PageSegments    uint8
}

// parseOGGVorbisPageHeader parses an Ogg page header
func (o *oggVorbisParser) parseOGGVorbisPageHeader(skipMagicNumber bool) (*oggVorbisPageHeader, error) {
	// Create page header
	pageHeader := new(oggVorbisPageHeader)

	// Unless skip is specified, check for capture pattern
	if !skipMagicNumber {
		if _, err := o.reader.Read(o.buffer[:4]); err != nil {
			return nil, err
		}
		pageHeader.CapturePattern = o.buffer[:4]

		// Verify proper capture pattern
		if !bytes.Equal(pageHeader.CapturePattern, oggMagicNumber) {
			return nil, ErrInvalidStream
		}
	} else {
		// If skipped, assume capture pattern is correct magic number
		pageHeader.CapturePattern = oggMagicNumber
	}

	// Version (must always be 0)
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.Version = o.ui8

	// Verify mandated version 0
	if pageHeader.Version != 0 {
		return nil, ErrInvalidStream
	}

	// Header type
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.HeaderType = o.ui8

	// Granule position
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui64); err != nil {
		return nil, err
	}
	pageHeader.GranulePosition = o.ui64

	// Bitstream serial number
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return nil, err
	}
	pageHeader.BitstreamSerial = o.ui32

	// Page sequence number
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return nil, err
	}
	pageHeader.PageSequence = o.ui32

	// Checksum
	if _, err := o.reader.Read(o.buffer[:4]); err != nil {
		return nil, err
	}
	pageHeader.Checksum = o.buffer[:4]

	// Page segments
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.PageSegments = o.ui8

	// Segment table is next, but we won't need it for tag parsing, so seek ahead
	// size of uint8 (1 byte) multiplied by number of page segments
	if _, err := o.reader.Seek(int64(pageHeader.PageSegments), 1); err != nil {
		return nil, err

	}
	return pageHeader, nil
}

// parseOGGVorbisCommonHeader parses information common to all Ogg Vorbis headers
func (o *oggVorbisParser) parseOGGVorbisCommonHeader() (byte, error) {
	// Read the first byte to get header type
	if _, err := o.reader.Read(o.buffer[:1]); err != nil {
		return 0, err
	}

	// Store first byte at end of buffer so we can return it later without more allocations
	o.buffer[len(o.buffer)-1] = o.buffer[0]

	// Read for 'vorbis' identification word
	if _, err := o.reader.Read(o.buffer[:len(oggVorbisVorbisWord)]); err != nil {
		return 0, err
	}

	// Ensure 'vorbis' identification word is present
	if !bytes.Equal(o.buffer[:len(oggVorbisVorbisWord)], oggVorbisVorbisWord) {
		return 0, ErrInvalidStream
	}

	// Return header type from end of buffer
	return o.buffer[len(o.buffer)-1], nil
}

// oggVorbisIDHeader represents the information contained in an Ogg Vorbis identification header
type oggVorbisIDHeader struct {
	VorbisVersion uint32
	ChannelCount  uint8
	SampleRate    uint32
	MaxBitrate    uint32
	NomBitrate    uint32
	MinBitrate    uint32
	Blocksize0    uint8
	Blocksize1    uint8
	Framing       bool
}

// parseOGGVorbisIDHeader parses the required identification header for an Ogg Vorbis stream
func (o *oggVorbisParser) parseOGGVorbisIDHeader() error {
	// Read OGGVorbis page header, skipping the capture pattern because New() already verified
	// the magic number for us
	if _, err := o.parseOGGVorbisPageHeader(true); err != nil {
		return err
	}

	// Check for valid common header
	headerType, err := o.parseOGGVorbisCommonHeader()
	if err != nil {
		return err
	}

	// Ensure header type 1: identification header
	if headerType != byte(1) {
		return ErrInvalidStream
	}

	// Read fields found in identification header
	header := new(oggVorbisIDHeader)

	// Vorbis version
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return err
	}
	header.VorbisVersion = o.ui32

	// Ensure Vorbis version is 0, per specification
	if header.VorbisVersion != 0 {
		return ErrInvalidStream
	}

	// Channel count
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return err
	}
	header.ChannelCount = o.ui8

	// uint32 x 4: sample rate, maximum bitrate, nominal bitrate, minimum bitrate
	uint32Slice := make([]uint32, 4)
	for i := 0; i < 4; i++ {
		// Read in one uint32
		if err := binary.Read(o.reader, binary.LittleEndian, &uint32Slice[i]); err != nil {
			return err
		}
	}

	// Copy out slice values
	header.SampleRate = uint32Slice[0]
	header.MaxBitrate = uint32Slice[1]
	header.NomBitrate = uint32Slice[2]
	header.MinBitrate = uint32Slice[3]

	// Create and use a bit reader to parse the following fields
	//    4 - Blocksize 0
	//    4 - Blocksize 1
	//    1 - Framing flag
	fields, err := bit.NewReader(o.reader).ReadFields(4, 4, 1)
	if err != nil {
		return err
	}

	header.Blocksize0 = uint8(fields[0])
	header.Blocksize1 = uint8(fields[1])
	header.Framing = fields[2] == 1

	// Store ID header
	o.idHeader = header
	return nil
}

// parseOGGVorbisCommentHeader parses the Vorbis Comment tags in an Ogg Vorbis file
func (o *oggVorbisParser) parseOGGVorbisCommentHeader() error {
	// Read OGGVorbis page header, specifying false to check the capture pattern
	if _, err := o.parseOGGVorbisPageHeader(false); err != nil {
		return err
	}

	// Parse common header
	headerType, err := o.parseOGGVorbisCommonHeader()
	if err != nil {
		return err
	}

	// Verify header type (3: Vorbis Comment)
	if headerType != byte(3) {
		return ErrInvalidStream
	}

	// Read vendor length
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return err
	}

	// Read vendor string, store as encoder
	if _, err := o.reader.Read(o.buffer[:o.ui32]); err != nil {
		return err
	}
	o.encoder = string(o.buffer[:o.ui32])

	// Read comment length (new allocation for use with loop counter)
	var commentLength uint32
	if err := binary.Read(o.reader, binary.LittleEndian, &commentLength); err != nil {
		return err
	}

	// Begin iterating tags, and building tag map
	tagMap := map[string]string{}
	for i := 0; i < int(commentLength); i++ {
		// Read tag string length
		if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
			return err
		}

		// Read tag string
		n, err := o.reader.Read(o.buffer[:o.ui32])
		if err != nil {
			return err
		}

		// Split tag name and data, store in map
		pair := strings.Split(string(o.buffer[:n]), "=")
		tagMap[strings.ToUpper(pair[0])] = pair[1]
	}

	// Seek one byte forward to prepare for the setup header
	if _, err := o.reader.Seek(1, 1); err != nil {
		return err
	}

	// Store tags
	o.tags = tagMap
	return nil
}

// parseOGGVorbisDuration reads out the rest of the file to find the last OGGVorbis page header, which
// contains information needed to parse the file duration
func (o *oggVorbisParser) parseOGGVorbisDuration() error {
	// Seek as far forward as sanely possible so we don't need to read tons of excess data
	// For now, a value of 4096 bytes before the end appears to work, and should give a bit
	// of wiggle-room without causing us to read the entire file
	if _, err := o.reader.Seek(-4096, 2); err != nil {
		return err
	}

	// Read the rest of the file to find the last page header
	vorbisFile, err := ioutil.ReadAll(o.reader)
	if err != nil {
		return err
	}

	// Find the index of the last OGGVorbis page header
	index := bytes.LastIndex(vorbisFile, oggMagicNumber)
	if index == -1 {
		return ErrInvalidStream
	}

	// Read using the in-memory bytes to grab the last page header information
	o.reader = bytes.NewReader(vorbisFile[index:])
	pageHeader, err := o.parseOGGVorbisPageHeader(false)
	if err != nil {
		return nil
	}

	// Calculate duration using last granule position divided by sample rate
	o.duration = time.Duration(pageHeader.GranulePosition/uint64(o.idHeader.SampleRate)) * time.Second
	return nil
}
<commit_msg>Fix a couple semantics with Ogg Vorbis<commit_after>package taggolib

import (
	"bytes"
	"encoding/binary"
	"io"
	"io/ioutil"
	"strconv"
	"strings"
	"time"

	"github.com/eaburns/bit"
)

var (
	// oggMagicNumber is the magic number used to identify an OGG container audio stream
	oggMagicNumber = []byte("OggS")
	// oggVorbisVorbisWord is used to denote the beginning of a Vorbis information block
	oggVorbisVorbisWord = []byte("vorbis")
)

// oggVorbisParser represents a OGGVorbis audio metadata tag parser
type oggVorbisParser struct {
	duration time.Duration
	encoder  string
	idHeader *oggVorbisIDHeader
	reader   io.ReadSeeker
	tags     map[string]string

	// Shared buffer and unsigned integers stored as fields to prevent unneeded allocations
	buffer []byte
	ui8    uint8
	ui32   uint32
	ui64   uint64
}

// Album returns the Album tag for this stream
func (o oggVorbisParser) Album() string {
	return o.tags[tagAlbum]
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (o oggVorbisParser) AlbumArtist() string {
	return o.tags[tagAlbumArtist]
}

// Artist returns the Artist tag for this stream
func (o oggVorbisParser) Artist() string {
	return o.tags[tagArtist]
}

// BitDepth returns the bits-per-sample of this stream
func (o oggVorbisParser) BitDepth() int {
	// Ogg Vorbis should always provide 16 bit depth
	return 16
}

// Bitrate calculates the audio bitrate for this stream
func (o oggVorbisParser) Bitrate() int {
	// TODO: see how max/min bitrate play into calculations
	return int(o.idHeader.NomBitrate) / 1000
}

// Channels returns the number of channels for this stream
func (o oggVorbisParser) Channels() int {
	return int(o.idHeader.ChannelCount)
}

// Comment returns the Comment tag for this stream
func (o oggVorbisParser) Comment() string {
	return o.tags[tagComment]
}

// Date returns the Date tag for this stream
func (o oggVorbisParser) Date() string {
	return o.tags[tagDate]
}

// DiscNumber returns the DiscNumber tag for this stream
func (o oggVorbisParser) DiscNumber() int {
	disc, err := strconv.Atoi(o.tags[tagDiscNumber])
	if err != nil {
		return 0
	}

	return disc
}

// Duration returns the time duration for this stream
func (o oggVorbisParser) Duration() time.Duration {
	return o.duration
}

// Encoder returns the encoder for this stream
func (o oggVorbisParser) Encoder() string {
	return o.encoder
}

// Format returns the name of the Ogg Vorbis format
func (o oggVorbisParser) Format() string {
	return "Ogg Vorbis"
}

// Genre returns the Genre tag for this stream
func (o oggVorbisParser) Genre() string {
	return o.tags[tagGenre]
}

// SampleRate returns the sample rate in Hertz for this stream
func (o oggVorbisParser) SampleRate() int {
	return int(o.idHeader.SampleRate)
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (o oggVorbisParser) Tag(name string) string {
	return o.tags[name]
}

// Title returns the Title tag for this stream
func (o oggVorbisParser) Title() string {
	return o.tags[tagTitle]
}

// TrackNumber returns the TrackNumber tag for this stream
func (o oggVorbisParser) TrackNumber() int {
	// Check for a /, such as 2/8
	track, err := strconv.Atoi(strings.Split(o.tags[tagTrackNumber], "/")[0])
	if err != nil {
		return 0
	}

	return track
}

// newOGGVorbisParser creates a parser for OGGVorbis audio streams
func newOGGVorbisParser(reader io.ReadSeeker) (*oggVorbisParser, error) {
	// Create OGGVorbis parser
	parser := &oggVorbisParser{
		buffer: make([]byte, 128),
		reader: reader,
	}

	// Parse the required ID header
	if err := parser.parseOGGVorbisIDHeader(); err != nil {
		return nil, err
	}

	// Parse the required comment header
	if err := parser.parseOGGVorbisCommentHeader(); err != nil {
		return nil, err
	}

	// Parse the file's duration
	if err := parser.parseOGGVorbisDuration(); err != nil {
		return nil, err
	}

	// Return parser
	return parser, nil
}

// oggVorbisPageHeader represents the information contained in an Ogg Page header
type oggVorbisPageHeader struct {
	CapturePattern  []byte
	Version         uint8
	HeaderType      uint8
	GranulePosition uint64
	BitstreamSerial uint32
	PageSequence    uint32
	Checksum        []byte
	PageSegments    uint8
}

// parseOGGVorbisPageHeader parses an Ogg page header
func (o *oggVorbisParser) parseOGGVorbisPageHeader(skipMagicNumber bool) (*oggVorbisPageHeader, error) {
	// Create page header
	pageHeader := new(oggVorbisPageHeader)

	// Unless skip is specified, check for capture pattern
	if !skipMagicNumber {
		if _, err := o.reader.Read(o.buffer[:4]); err != nil {
			return nil, err
		}
		pageHeader.CapturePattern = o.buffer[:4]

		// Verify proper capture pattern
		if !bytes.Equal(pageHeader.CapturePattern, oggMagicNumber) {
			return nil, ErrInvalidStream
		}
	} else {
		// If skipped, assume capture pattern is correct magic number
		pageHeader.CapturePattern = oggMagicNumber
	}

	// Version (must always be 0)
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.Version = o.ui8

	// Verify mandated version 0
	if pageHeader.Version != 0 {
		return nil, ErrInvalidStream
	}

	// Header type
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.HeaderType = o.ui8

	// Granule position
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui64); err != nil {
		return nil, err
	}
	pageHeader.GranulePosition = o.ui64

	// Bitstream serial number
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return nil, err
	}
	pageHeader.BitstreamSerial = o.ui32

	// Page sequence number
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return nil, err
	}
	pageHeader.PageSequence = o.ui32

	// Checksum
	if _, err := o.reader.Read(o.buffer[:4]); err != nil {
		return nil, err
	}
	pageHeader.Checksum = o.buffer[:4]

	// Page segments
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return nil, err
	}
	pageHeader.PageSegments = o.ui8

	// Segment table is next, but we won't need it for tag parsing, so seek ahead
	// size of uint8 (1 byte) multiplied by number of page segments
	if _, err := o.reader.Seek(int64(pageHeader.PageSegments), 1); err != nil {
		return nil, err

	}
	return pageHeader, nil
}

// parseOGGVorbisCommonHeader parses information common to all Ogg Vorbis headers
func (o *oggVorbisParser) parseOGGVorbisCommonHeader() (byte, error) {
	// Read the first byte to get header type
	if _, err := o.reader.Read(o.buffer[:1]); err != nil {
		return 0, err
	}

	// Store first byte at end of buffer so we can return it later without more allocations
	o.buffer[len(o.buffer)-1] = o.buffer[0]

	// Read for 'vorbis' identification word
	if _, err := o.reader.Read(o.buffer[:len(oggVorbisVorbisWord)]); err != nil {
		return 0, err
	}

	// Ensure 'vorbis' identification word is present
	if !bytes.Equal(o.buffer[:len(oggVorbisVorbisWord)], oggVorbisVorbisWord) {
		return 0, ErrInvalidStream
	}

	// Return header type from end of buffer
	return o.buffer[len(o.buffer)-1], nil
}

// oggVorbisIDHeader represents the information contained in an Ogg Vorbis identification header
type oggVorbisIDHeader struct {
	VorbisVersion uint32
	ChannelCount  uint8
	SampleRate    uint32
	MaxBitrate    uint32
	NomBitrate    uint32
	MinBitrate    uint32
	Blocksize0    uint8
	Blocksize1    uint8
	Framing       bool
}

// parseOGGVorbisIDHeader parses the required identification header for an Ogg Vorbis stream
func (o *oggVorbisParser) parseOGGVorbisIDHeader() error {
	// Read OGGVorbis page header, skipping the capture pattern because New() already verified
	// the magic number for us
	if _, err := o.parseOGGVorbisPageHeader(true); err != nil {
		return err
	}

	// Check for valid common header
	headerType, err := o.parseOGGVorbisCommonHeader()
	if err != nil {
		return err
	}

	// Ensure header type 1: identification header
	if headerType != byte(1) {
		return ErrInvalidStream
	}

	// Read fields found in identification header
	header := new(oggVorbisIDHeader)

	// Vorbis version
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return err
	}
	header.VorbisVersion = o.ui32

	// Ensure Vorbis version is 0, per specification
	if header.VorbisVersion != 0 {
		return ErrInvalidStream
	}

	// Channel count
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui8); err != nil {
		return err
	}
	header.ChannelCount = o.ui8

	// uint32 x 4: sample rate, maximum bitrate, nominal bitrate, minimum bitrate
	uint32Slice := make([]uint32, 4)
	for i := 0; i < 4; i++ {
		// Read in one uint32
		if err := binary.Read(o.reader, binary.LittleEndian, &uint32Slice[i]); err != nil {
			return err
		}
	}

	// Copy out slice values
	header.SampleRate = uint32Slice[0]
	header.MaxBitrate = uint32Slice[1]
	header.NomBitrate = uint32Slice[2]
	header.MinBitrate = uint32Slice[3]

	// Create and use a bit reader to parse the following fields
	//    4 - Blocksize 0
	//    4 - Blocksize 1
	//    1 - Framing flag
	fields, err := bit.NewReader(o.reader).ReadFields(4, 4, 1)
	if err != nil {
		return err
	}

	header.Blocksize0 = uint8(fields[0])
	header.Blocksize1 = uint8(fields[1])
	header.Framing = fields[2] == 1

	// Store ID header
	o.idHeader = header
	return nil
}

// parseOGGVorbisCommentHeader parses the Vorbis Comment tags in an Ogg Vorbis file
func (o *oggVorbisParser) parseOGGVorbisCommentHeader() error {
	// Read OGGVorbis page header, specifying false to check the capture pattern
	if _, err := o.parseOGGVorbisPageHeader(false); err != nil {
		return err
	}

	// Parse common header
	headerType, err := o.parseOGGVorbisCommonHeader()
	if err != nil {
		return err
	}

	// Verify header type (3: Vorbis Comment)
	if headerType != byte(3) {
		return ErrInvalidStream
	}

	// Read vendor length
	if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
		return err
	}

	// Read vendor string, store as encoder
	if _, err := o.reader.Read(o.buffer[:o.ui32]); err != nil {
		return err
	}
	o.encoder = string(o.buffer[:o.ui32])

	// Read comment length (new allocation for use with loop counter)
	var commentLength uint32
	if err := binary.Read(o.reader, binary.LittleEndian, &commentLength); err != nil {
		return err
	}

	// Begin iterating tags, and building tag map
	tagMap := map[string]string{}
	for i := 0; i < int(commentLength); i++ {
		// Read tag string length
		if err := binary.Read(o.reader, binary.LittleEndian, &o.ui32); err != nil {
			return err
		}

		// Read tag string
		n, err := o.reader.Read(o.buffer[:o.ui32])
		if err != nil {
			return err
		}

		// Split tag name and data, store in map
		pair := strings.Split(string(o.buffer[:n]), "=")
		tagMap[strings.ToUpper(pair[0])] = pair[1]
	}

	// Seek one byte forward to prepare for the setup header
	if _, err := o.reader.Seek(1, 1); err != nil {
		return err
	}

	// Store tags
	o.tags = tagMap
	return nil
}

// parseOGGVorbisDuration reads out the rest of the file to find the last Ogg Vorbis page header, which
// contains information needed to parse the file duration
func (o *oggVorbisParser) parseOGGVorbisDuration() error {
	// Seek as far forward as sanely possible so we don't need to read tons of excess data
	// For now, a value of 4096 bytes before the end appears to work, and should give a bit
	// of wiggle-room without causing us to read the entire file
	if _, err := o.reader.Seek(-4096, 2); err != nil {
		return err
	}

	// Read the rest of the file to find the last page header
	vorbisFile, err := ioutil.ReadAll(o.reader)
	if err != nil {
		return err
	}

	// Find the index of the last OGGVorbis page header
	index := bytes.LastIndex(vorbisFile, oggMagicNumber)
	if index == -1 {
		return ErrInvalidStream
	}

	// Read using the in-memory bytes to grab the last page header information
	o.reader = bytes.NewReader(vorbisFile[index:])
	pageHeader, err := o.parseOGGVorbisPageHeader(false)
	if err != nil {
		return nil
	}

	// Calculate duration using last granule position divided by sample rate
	o.duration = time.Duration(pageHeader.GranulePosition/uint64(o.idHeader.SampleRate)) * time.Second
	return nil
}
<|endoftext|>


<commit_before>package util

import (
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"sync"

	"github.com/op/go-logging"
	"github.com/spf13/viper"
)

var log = logging.MustGetLogger("Config")

// Config is the data structure for passing configuration info
type Config struct {
	Version int
	Storage struct {
		Filepath string
	}
	Authn struct {
		MintKeyName      	string
		ValidateKeyNames 	[]string
		TokenTTL         	float64
		PwdProvider      	struct {
			PwdFileName 	string
			Salt        	string
		}
	}
	Authz struct {
		Provider			string
		LDAPProvider		struct{
			User     		string
			Password 		string
		}
	}
}

// Sanitize the configuration
func sanitize(c *Config) {
	s := c.Storage.Filepath
	if len(s) > 0 {
		// Make sure the db path ends with a forwardslash
		if string(s[len(s)-1]) != "/" {
			s = s + "/"
			log.Debugf("Added forwardslash to db path '%s' ", s)
		}
		// Handle relative paths
		if string(s[0]) != "/" {
			pwd, _ := os.Getwd()
			s = pwd + "/" + s
			log.Debugf("Added pwd to db path '%s' ", s)
		}
		c.Storage.Filepath = s
	}
}

// LoadConfig loads configuration using a hard-coded name
// This is what gets called during normal operation
func LoadConfig() {
	LoadConfigByName("config")
}

// LoadConfigByName loads a config from a specific file
// Used for separating test from operational configuration
func LoadConfigByName(name string) {
	var isFatal bool
	var tmp *Config

	tmp = new(Config)

	cLock.RLock()
	isFatal = (config == nil)
	cLock.RUnlock()

	viper.SetConfigName(name)
	viper.SetConfigType("json")

	userConfigFolder := getUserConfigFolderPath()
	configFolder := getConfigFolderPath()

	viper.AddConfigPath(userConfigFolder) // user's own personal config file
	viper.AddConfigPath(configFolder) // General fallback config file
	viper.AddConfigPath(".") // default path

	if err := viper.ReadInConfig(); err != nil {
		// No config to start up on
		if isFatal {
			log.Debugf("Looking for config in: %s (user) or %s (fallback)", userConfigFolder, configFolder)
			panic(err)
		} else {
			log.Errorf("Failed to load configuration from %s\n", name)
			return
		}
	}

	log.Infof("Config file found: %s\n", viper.ConfigFileUsed())

	viper.Unmarshal(tmp)
	sanitize(tmp)

	// TODO viper can reload config too. Remove this?
	// Nope, the versioning is so we can trigger reloading of keys
	cLock.Lock()
	if config == nil {
		tmp.Version = 1
	} else {
		tmp.Version = config.Version + 1
	}

	config = tmp
	cLock.Unlock()

	log.Infof("Success loading configuration ver %d from %s", config.Version, viper.ConfigFileUsed())
}

// TODO: make it so it loads the config if it is not there
func GetConfig() *Config {
	cLock.RLock()
	defer cLock.RUnlock()
	return config
}

// GetPaths returns absolute paths for input filenames.
// If file exists in user's config folder, returns path to it,
// otherwise returns path to file in 'config/' folder.
func GetPaths(filenames []string) []string {
	cfgFolder := getConfigFolderPath()
	userCfgFolder := getUserConfigFolderPath()

	var paths []string

	for _, name := range filenames {
		if (strings.HasPrefix(name, "/")){
			if _, err := os.Stat(name); err == nil {
				// File exists and starts with a '/', must be an absolute path
				paths = append(paths, name)
				continue
			}
		}

		path := cfgFolder + name
		userPath := userCfgFolder + name

		if _, err := os.Stat(userPath); err == nil {
			paths = append(paths, userPath)
		} else {
			paths = append(paths, path)
		}
	}

	return paths
}

// Generates path to user's config folder
func getUserConfigFolderPath() string {

	userName := getUserName()

	cfgFolder := getConfigFolderPath()
	sep := string(filepath.Separator)

	path := cfgFolder + userName + sep

	return path
}

// Return currently logged in user's username
func getUserName() string {
	u, err := user.Current()
	if err != nil {
		log.Errorf("Cannot find current user")
	}
	return u.Username
}

// Generates path to general config folder (which contains all user's folders)
func getConfigFolderPath() string {
	sep := string(filepath.Separator)
	wd, _ := os.Getwd()

	wdPath := strings.Split(wd, sep)
	iSrc := lastIndexOf(wdPath, "src")
	iBin := lastIndexOf(wdPath, "bin")

	cfgPath := ""
	var pathEl []string
	if iBin > -1 && iBin > iSrc {
		pathEl = wdPath[:iBin] // take up to bin (exclusive)
	} else if iSrc > -1 {
		pathEl = wdPath[:iSrc] // take up to src (exclusive)
	} else {
		// If neither bin nor source is found, we are probably at
		// project home
		pathEl = wdPath
	}

	if len(pathEl) > 0 {
		cfgPath = strings.Join(pathEl, sep) + sep
		cfgPath += "config" + sep
	}

	return cfgPath
}

func lastIndexOf(h []string, n string) int {
	for i := len(h) - 1; i > 0; i-- {
		if h[i] == n {
			return i
		}
	}
	return -1
}

// Global to hold the conf and a lock
var (
	config *Config
	cLock  = new(sync.RWMutex)
)
<commit_msg>Added a comment on GetPaths behaviour change<commit_after>package util

import (
	"os"
	"os/user"
	"path/filepath"
	"strings"
	"sync"

	"github.com/op/go-logging"
	"github.com/spf13/viper"
)

var log = logging.MustGetLogger("Config")

// Config is the data structure for passing configuration info
type Config struct {
	Version int
	Storage struct {
		Filepath string
	}
	Authn struct {
		MintKeyName      	string
		ValidateKeyNames 	[]string
		TokenTTL         	float64
		PwdProvider      	struct {
			PwdFileName 	string
			Salt        	string
		}
	}
	Authz struct {
		Provider			string
		LDAPProvider		struct{
			User     		string
			Password 		string
		}
	}
}

// Sanitize the configuration
func sanitize(c *Config) {
	s := c.Storage.Filepath
	if len(s) > 0 {
		// Make sure the db path ends with a forwardslash
		if string(s[len(s)-1]) != "/" {
			s = s + "/"
			log.Debugf("Added forwardslash to db path '%s' ", s)
		}
		// Handle relative paths
		if string(s[0]) != "/" {
			pwd, _ := os.Getwd()
			s = pwd + "/" + s
			log.Debugf("Added pwd to db path '%s' ", s)
		}
		c.Storage.Filepath = s
	}
}

// LoadConfig loads configuration using a hard-coded name
// This is what gets called during normal operation
func LoadConfig() {
	LoadConfigByName("config")
}

// LoadConfigByName loads a config from a specific file
// Used for separating test from operational configuration
func LoadConfigByName(name string) {
	var isFatal bool
	var tmp *Config

	tmp = new(Config)

	cLock.RLock()
	isFatal = (config == nil)
	cLock.RUnlock()

	viper.SetConfigName(name)
	viper.SetConfigType("json")

	userConfigFolder := getUserConfigFolderPath()
	configFolder := getConfigFolderPath()

	viper.AddConfigPath(userConfigFolder) // user's own personal config file
	viper.AddConfigPath(configFolder) // General fallback config file
	viper.AddConfigPath(".") // default path

	if err := viper.ReadInConfig(); err != nil {
		// No config to start up on
		if isFatal {
			log.Debugf("Looking for config in: %s (user) or %s (fallback)", userConfigFolder, configFolder)
			panic(err)
		} else {
			log.Errorf("Failed to load configuration from %s\n", name)
			return
		}
	}

	log.Infof("Config file found: %s\n", viper.ConfigFileUsed())

	viper.Unmarshal(tmp)
	sanitize(tmp)

	// TODO viper can reload config too. Remove this?
	// Nope, the versioning is so we can trigger reloading of keys
	cLock.Lock()
	if config == nil {
		tmp.Version = 1
	} else {
		tmp.Version = config.Version + 1
	}

	config = tmp
	cLock.Unlock()

	log.Infof("Success loading configuration ver %d from %s", config.Version, viper.ConfigFileUsed())
}

// TODO: make it so it loads the config if it is not there
func GetConfig() *Config {
	cLock.RLock()
	defer cLock.RUnlock()
	return config
}

// GetPaths returns absolute paths for input filenames.
// If file exists in user's config folder, returns path to it,
// otherwise returns path to file in 'config/' folder.
// If the file starts with a '/' and exists, leaves it alone - we
// are using absolute paths for some reason
func GetPaths(filenames []string) []string {
	cfgFolder := getConfigFolderPath()
	userCfgFolder := getUserConfigFolderPath()

	var paths []string

	for _, name := range filenames {
		if (strings.HasPrefix(name, "/")){
			if _, err := os.Stat(name); err == nil {
				// File exists and starts with a '/', must be an absolute path
				paths = append(paths, name)
				continue
			}
		}

		path := cfgFolder + name
		userPath := userCfgFolder + name

		if _, err := os.Stat(userPath); err == nil {
			paths = append(paths, userPath)
		} else {
			paths = append(paths, path)
		}
	}

	return paths
}

// Generates path to user's config folder
func getUserConfigFolderPath() string {

	userName := getUserName()

	cfgFolder := getConfigFolderPath()
	sep := string(filepath.Separator)

	path := cfgFolder + userName + sep

	return path
}

// Return currently logged in user's username
func getUserName() string {
	u, err := user.Current()
	if err != nil {
		log.Errorf("Cannot find current user")
	}
	return u.Username
}

// Generates path to general config folder (which contains all user's folders)
func getConfigFolderPath() string {
	sep := string(filepath.Separator)
	wd, _ := os.Getwd()

	wdPath := strings.Split(wd, sep)
	iSrc := lastIndexOf(wdPath, "src")
	iBin := lastIndexOf(wdPath, "bin")

	cfgPath := ""
	var pathEl []string
	if iBin > -1 && iBin > iSrc {
		pathEl = wdPath[:iBin] // take up to bin (exclusive)
	} else if iSrc > -1 {
		pathEl = wdPath[:iSrc] // take up to src (exclusive)
	} else {
		// If neither bin nor source is found, we are probably at
		// project home
		pathEl = wdPath
	}

	if len(pathEl) > 0 {
		cfgPath = strings.Join(pathEl, sep) + sep
		cfgPath += "config" + sep
	}

	return cfgPath
}

func lastIndexOf(h []string, n string) int {
	for i := len(h) - 1; i > 0; i-- {
		if h[i] == n {
			return i
		}
	}
	return -1
}

// Global to hold the conf and a lock
var (
	config *Config
	cLock  = new(sync.RWMutex)
)
<|endoftext|>