<commit_before>// Copyright 2016-2020 The Libsacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sacloud

import (
	"context"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestClient_Do_Backoff(t *testing.T) {
	h := &dummyHandler{
		responseCode: http.StatusServiceUnavailable,
	}
	dummyServer := httptest.NewServer(h)
	defer dummyServer.Close()

	client := &Client{
		RetryMax:     7,
		RetryWaitMin: 100 * time.Millisecond,
		RetryWaitMax: 800 * time.Millisecond,
	}
	client.Do(context.Background(), http.MethodGet, dummyServer.URL, nil) // nolint

	require.Len(t, h.called, client.RetryMax+1) // initial call + RetryMax
	var previous time.Time
	for i, ct := range h.called {
		if !previous.IsZero() {
			diff := ct.Sub(previous).Truncate(100 * time.Millisecond)
			t.Logf("backoff: retry-%d -> %0.2fs waited\n", i, diff.Seconds())
			require.True(t, client.RetryWaitMin <= diff && diff <= client.RetryWaitMax)
		}
		previous = ct
	}
}

type dummyHandler struct {
	called       []time.Time
	responseCode int
}

func (s *dummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.called = append(s.called, time.Now())
	w.WriteHeader(s.responseCode)
}

func (s *dummyHandler) isRetried() bool {
	return len(s.called) > 1
}

func TestClient_Do_CheckRetryWithContext(t *testing.T) {
	client := &Client{RetryMax: 1, RetryWaitMin: 10 * time.Millisecond, RetryWaitMax: 10 * time.Millisecond}

	t.Run("context.Canceled", func(t *testing.T) {
		h := &dummyHandler{
			responseCode: http.StatusServiceUnavailable,
		}
		dummyServer := httptest.NewServer(h)
		defer dummyServer.Close()

		ctx, cancel := context.WithCancel(context.Background())
		// make ctx to Canceled
		cancel()

		client.Do(ctx, http.MethodGet, dummyServer.URL, nil) // nolint
		require.False(t, h.isRetried(), "don't retry when context was canceled")
	})

	t.Run("context.DeadlineExceeded", func(t *testing.T) {
		h := &dummyHandler{
			responseCode: http.StatusServiceUnavailable,
		}
		dummyServer := httptest.NewServer(h)
		defer dummyServer.Close()

		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
		defer cancel()
		// make ctx to DeadlineExceeded
		time.Sleep(time.Millisecond)

		client.Do(ctx, http.MethodGet, dummyServer.URL, nil) // nolint
		require.False(t, h.isRetried(), "don't retry when context exceeded deadline")
	})
}

func TestClient_RetryByStatusCode(t *testing.T) {
	cases := []struct {
		responseCode int
		shouldRetry  bool
	}{
		{responseCode: http.StatusOK, shouldRetry: false},
		{responseCode: http.StatusCreated, shouldRetry: false},
		{responseCode: http.StatusAccepted, shouldRetry: false},
		{responseCode: http.StatusNoContent, shouldRetry: false},
		{responseCode: http.StatusMovedPermanently, shouldRetry: false},
		{responseCode: http.StatusFound, shouldRetry: false},
		{responseCode: http.StatusBadRequest, shouldRetry: false},
		{responseCode: http.StatusUnauthorized, shouldRetry: false},
		{responseCode: http.StatusForbidden, shouldRetry: false},
		{responseCode: http.StatusNotFound, shouldRetry: false},
		{responseCode: http.StatusLocked, shouldRetry: true}, // Locked: 423
		{responseCode: http.StatusInternalServerError, shouldRetry: false},
		{responseCode: http.StatusBadGateway, shouldRetry: false},
		{responseCode: http.StatusServiceUnavailable, shouldRetry: true},
		{responseCode: http.StatusGatewayTimeout, shouldRetry: false},
	}

	client := &Client{RetryMax: 1, RetryWaitMin: 10 * time.Millisecond, RetryWaitMax: 10 * time.Millisecond}

	for _, tt := range cases {
		h := &dummyHandler{
			responseCode: tt.responseCode,
		}
		dummyServer := httptest.NewServer(h)
		client.Do(context.Background(), http.MethodGet, dummyServer.URL, nil) // nolint
		dummyServer.Close()

		require.Equal(t, tt.shouldRetry, h.isRetried(),
			"got unexpected retry status with status[%d]: expected:%t got:%t", h.responseCode, tt.shouldRetry, h.isRetried())
	}
}
<commit_msg>Fix broken test - TestClient_Do_Backoff on macos_latest - use sync.Mutex<commit_after>// Copyright 2016-2020 The Libsacloud Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package sacloud

import (
	"context"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestClient_Do_Backoff(t *testing.T) {
	h := &dummyHandler{
		responseCode: http.StatusServiceUnavailable,
	}
	dummyServer := httptest.NewServer(h)
	defer dummyServer.Close()

	client := &Client{
		RetryMax:     7,
		RetryWaitMin: 10 * time.Millisecond,
		RetryWaitMax: 320 * time.Millisecond,
	}
	client.Do(context.Background(), http.MethodGet, dummyServer.URL, nil) // nolint

	require.Len(t, h.called, client.RetryMax+1) // initial call + RetryMax
	var previous time.Time
	for i, ct := range h.called {
		if !previous.IsZero() {
			diff := ct.Sub(previous).Truncate(10 * time.Millisecond)
			t.Logf("backoff: retry-%d -> %0.2fs waited\n", i, diff.Seconds())
			require.True(t, client.RetryWaitMin <= diff && diff <= client.RetryWaitMax)
		}
		previous = ct
	}
}

type dummyHandler struct {
	called       []time.Time
	responseCode int
	mu           sync.Mutex
}

func (s *dummyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.called = append(s.called, time.Now())
	w.WriteHeader(s.responseCode)
}

func (s *dummyHandler) isRetried() bool {
	return len(s.called) > 1
}

func TestClient_Do_CheckRetryWithContext(t *testing.T) {
	client := &Client{RetryMax: 1, RetryWaitMin: 10 * time.Millisecond, RetryWaitMax: 10 * time.Millisecond}

	t.Run("context.Canceled", func(t *testing.T) {
		h := &dummyHandler{
			responseCode: http.StatusServiceUnavailable,
		}
		dummyServer := httptest.NewServer(h)
		defer dummyServer.Close()

		ctx, cancel := context.WithCancel(context.Background())
		// make ctx to Canceled
		cancel()

		client.Do(ctx, http.MethodGet, dummyServer.URL, nil) // nolint
		require.False(t, h.isRetried(), "don't retry when context was canceled")
	})

	t.Run("context.DeadlineExceeded", func(t *testing.T) {
		h := &dummyHandler{
			responseCode: http.StatusServiceUnavailable,
		}
		dummyServer := httptest.NewServer(h)
		defer dummyServer.Close()

		ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond)
		defer cancel()
		// make ctx to DeadlineExceeded
		time.Sleep(time.Millisecond)

		client.Do(ctx, http.MethodGet, dummyServer.URL, nil) // nolint
		require.False(t, h.isRetried(), "don't retry when context exceeded deadline")
	})
}

func TestClient_RetryByStatusCode(t *testing.T) {
	cases := []struct {
		responseCode int
		shouldRetry  bool
	}{
		{responseCode: http.StatusOK, shouldRetry: false},
		{responseCode: http.StatusCreated, shouldRetry: false},
		{responseCode: http.StatusAccepted, shouldRetry: false},
		{responseCode: http.StatusNoContent, shouldRetry: false},
		{responseCode: http.StatusMovedPermanently, shouldRetry: false},
		{responseCode: http.StatusFound, shouldRetry: false},
		{responseCode: http.StatusBadRequest, shouldRetry: false},
		{responseCode: http.StatusUnauthorized, shouldRetry: false},
		{responseCode: http.StatusForbidden, shouldRetry: false},
		{responseCode: http.StatusNotFound, shouldRetry: false},
		{responseCode: http.StatusLocked, shouldRetry: true}, // Locked: 423
		{responseCode: http.StatusInternalServerError, shouldRetry: false},
		{responseCode: http.StatusBadGateway, shouldRetry: false},
		{responseCode: http.StatusServiceUnavailable, shouldRetry: true},
		{responseCode: http.StatusGatewayTimeout, shouldRetry: false},
	}

	client := &Client{RetryMax: 1, RetryWaitMin: 10 * time.Millisecond, RetryWaitMax: 10 * time.Millisecond}

	for _, tt := range cases {
		h := &dummyHandler{
			responseCode: tt.responseCode,
		}
		dummyServer := httptest.NewServer(h)
		client.Do(context.Background(), http.MethodGet, dummyServer.URL, nil) // nolint
		dummyServer.Close()

		require.Equal(t, tt.shouldRetry, h.isRetried(),
			"got unexpected retry status with status[%d]: expected:%t got:%t", h.responseCode, tt.shouldRetry, h.isRetried())
	}
}
<|endoftext|>


<commit_before>package sakuracloud

var Version = "0.1.0"
<commit_msg>Bump to v0.1.1<commit_after>package sakuracloud

var Version = "0.1.1"
<|endoftext|>


<commit_before>package main

import (
	"net/http"

	"github.com/flynn/rpcplus"
)

func main() {
	rpcplus.HandleHTTP()
	http.ListenAndServe(":1112", nil)
}
<commit_msg>sampi: Register with service discovery<commit_after>package main

import (
	"flag"
	"log"
	"net"
	"net/http"
	"strings"

	"github.com/flynn/go-discover/discover"
	"github.com/flynn/rpcplus"
)

var listenAddr = flag.String("listen", ":1112", "listen address")

func main() {
	flag.Parse()
	rpcplus.HandleHTTP()
	l, err := net.Listen("tcp", *listenAddr)
	if err != nil {
		log.Fatal(err)
	}

	d, err := discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}
	if hostPort := strings.SplitN(*listenAddr, ":", 2); hostPort[0] != "" {
		err = d.RegisterWithHost("flynn-sampi", hostPort[0], hostPort[1], nil)
	} else {
		err = d.Register("flynn-sampi", hostPort[1], nil)
	}
	if err != nil {
		log.Fatal(err)
	}

	http.Serve(l, nil)
}
<|endoftext|>


<commit_before>// Package cluster holds the cluster CRD logic and definitions
// A cluster is comprised of a primary service, replica service,
// primary deployment, and replica deployment
package cluster

/*
 Copyright 2019 Crunchy Data Solutions, Inc.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

import (
	"errors"
	"fmt"
	crv1 "github.com/crunchydata/postgres-operator/apis/cr/v1"
	"github.com/crunchydata/postgres-operator/config"
	"github.com/crunchydata/postgres-operator/kubeapi"
	"github.com/crunchydata/postgres-operator/util"
	log "github.com/sirupsen/logrus"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"time"
)

func Failover(clientset *kubernetes.Clientset, client *rest.RESTClient, clusterName string, task *crv1.Pgtask, namespace string, restconfig *rest.Config) error {

	var pod *v1.Pod
	var err error
	target := task.ObjectMeta.Labels[config.LABEL_TARGET]

	log.Info("strategy 1 Failover called on " + clusterName + " target is " + target)

	pod, err = util.GetPod(clientset, target, namespace)
	if err != nil {
		log.Error(err)
		return err
	}
	log.Debugf("best pod to failover to is %s", pod.Name)

	//delete the primary deployment if it exists

	//in the autofail scenario, some user might accidentally remove
	//the primary deployment, this would cause an autofail to occur
	//so the deployment needs to be checked to be present before
	//we attempt to remove it...in a manual failover case, the
	//deployment should be found, and then you would proceed to remove
	//it

	selector := config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_SERVICE_NAME + "=" + clusterName
	log.Debugf("selector in failover get deployments is %s", selector)
	var depList *appsv1.DeploymentList
	depList, err = kubeapi.GetDeployments(clientset, selector, namespace)
	if len(depList.Items) > 0 {
		log.Debug("in failover, the primary deployment is found before removal")
		err = deletePrimary(clientset, namespace, clusterName)
		if err != nil {
			log.Error(err)
			return err
		}
	} else {
		log.Debug("in failover, the primary deployment is NOT found so we will not attempt to remove it")
	}

	updateFailoverStatus(client, task, namespace, clusterName, "deleted primary deployment "+clusterName)

	//trigger the failover on the replica
	err = promote(pod, clientset, client, namespace, restconfig)
	updateFailoverStatus(client, task, namespace, clusterName, "promoting pod "+pod.Name+" target "+target)

	//relabel the deployment with primary labels
	//by setting service-name=clustername
	var upod *v1.Pod
	upod, _, err = kubeapi.GetPod(clientset, pod.Name, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in getting pod during failover relabel")
		return err
	}

	//set the service-name label to the cluster name to match
	//the primary service selector
	log.Debugf("setting label on pod %s=%s", config.LABEL_SERVICE_NAME, clusterName)

	err = kubeapi.AddLabelToPod(clientset, upod, config.LABEL_SERVICE_NAME, clusterName, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in updating pod during failover relabel")
		return err
	}

	targetDepName := upod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]
	log.Debug("targetDepName %s", targetDepName)
	var targetDep *appsv1.Deployment
	targetDep, _, err = kubeapi.GetDeployment(clientset, targetDepName, namespace)
	if err != nil {
		log.Error(err)
		log.Errorf("not found error in getting Deployment during failover relabel %s", targetDepName)
		return err
	}

	err = kubeapi.AddLabelToDeployment(clientset, targetDep, config.LABEL_SERVICE_NAME, clusterName, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in updating deployment during failover relabel")
		return err
	}

	updateFailoverStatus(client, task, namespace, clusterName, "updating label deployment...pod "+pod.Name+"was the failover target...failover completed")

	return err

}

func updateFailoverStatus(client *rest.RESTClient, task *crv1.Pgtask, namespace, clusterName, message string) {

	log.Debugf("updateFailoverStatus namespace=[%s] taskName=[%s] message=[%s]", namespace, task.Name, message)

	//update the task
	_, err := kubeapi.Getpgtask(client, task, task.ObjectMeta.Name,
		task.ObjectMeta.Namespace)
	if err != nil {
		return
	}

	task.Status.Message = message

	err = kubeapi.Updatepgtask(client,
		task,
		task.ObjectMeta.Name,
		task.ObjectMeta.Namespace)
	if err != nil {
		return
	}

}

func deletePrimary(clientset *kubernetes.Clientset, namespace, clusterName string) error {

	//the primary will be the one with a pod that has a label
	//that looks like service-name=clustername and is not a backrest job
	selector := config.LABEL_SERVICE_NAME + "=" + clusterName + "," + config.LABEL_BACKREST_RESTORE + "!=true," + config.LABEL_BACKREST_JOB + "!=true"
	pods, err := kubeapi.GetPods(clientset, selector, namespace)
	if len(pods.Items) == 0 {
		log.Errorf("no primary pod found when trying to delete primary %s", selector)
		return errors.New("could not find primary pod")
	}
	if len(pods.Items) > 1 {
		log.Errorf("more than 1 primary pod found when trying to delete primary %s", selector)
		return errors.New("more than 1 primary pod found in delete primary logic")
	}

	//update the label to 'fenced' on the pod to fence off traffic from
	//any client or replica using the primary, this effectively
	//stops traffic from the Primary service to the primary pod
	//we are about to delete
	pod := pods.Items[0]

	deploymentToDelete := pod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]

	//delete the deployment with pg-cluster=clusterName,primary=true
	log.Debugf("deleting deployment %s", deploymentToDelete)
	err = kubeapi.DeleteDeployment(clientset, deploymentToDelete, namespace)

	err = waitForDelete(deploymentToDelete, pod.Name, clientset, namespace)

	return err
}

func promote(
	pod *v1.Pod,
	clientset *kubernetes.Clientset,
	client *rest.RESTClient, namespace string, restconfig *rest.Config) error {

	//get the target pod that matches the replica-name=target

	command := make([]string, 1)
	command[0] = "/opt/cpm/bin/promote.sh"

	log.Debugf("running Exec with namespace=[%s] podname=[%s] container name=[%s]", namespace, pod.Name, pod.Spec.Containers[0].Name)
	stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, pod.Spec.Containers[0].Name, pod.Name, namespace, nil)
	log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr)
	if err != nil {
		log.Error(err)
	}

	return err
}

func waitForDelete(deploymentToDelete, podName string, clientset *kubernetes.Clientset, namespace string) error {
	var tries = 10

	for i := 0; i < tries; i++ {
		pod, _, err := kubeapi.GetPod(clientset, podName, namespace)
		if kerrors.IsNotFound(err) {
			log.Debugf("%s deployment %s pod not found so its safe to proceed on failover", deploymentToDelete, podName)
			return nil
		} else if err != nil {
			log.Error(err)
			log.Error("error getting pod when evaluating old primary in failover %s %s", deploymentToDelete, podName)
			return err
		}
		log.Debugf("waitinf for %s to delete", pod.Name)
		time.Sleep(time.Second * time.Duration(9))
	}

	return errors.New(fmt.Sprintf("timeout waiting for %s %s to delete", deploymentToDelete, podName))

}
<commit_msg>logic to update the pgcluster.current-primary field when a failover happens, this is meant to hold the current primary deployment name<commit_after>// Package cluster holds the cluster CRD logic and definitions
// A cluster is comprised of a primary service, replica service,
// primary deployment, and replica deployment
package cluster

/*
 Copyright 2019 Crunchy Data Solutions, Inc.
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
*/

import (
	"errors"
	"fmt"
	crv1 "github.com/crunchydata/postgres-operator/apis/cr/v1"
	"github.com/crunchydata/postgres-operator/config"
	"github.com/crunchydata/postgres-operator/kubeapi"
	"github.com/crunchydata/postgres-operator/util"
	log "github.com/sirupsen/logrus"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/api/core/v1"
	kerrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"time"
)

func Failover(clientset *kubernetes.Clientset, client *rest.RESTClient, clusterName string, task *crv1.Pgtask, namespace string, restconfig *rest.Config) error {

	var pod *v1.Pod
	var err error
	target := task.ObjectMeta.Labels[config.LABEL_TARGET]

	log.Info("strategy 1 Failover called on " + clusterName + " target is " + target)

	pod, err = util.GetPod(clientset, target, namespace)
	if err != nil {
		log.Error(err)
		return err
	}
	log.Debugf("best pod to failover to is %s", pod.Name)

	//delete the primary deployment if it exists

	//in the autofail scenario, some user might accidentally remove
	//the primary deployment, this would cause an autofail to occur
	//so the deployment needs to be checked to be present before
	//we attempt to remove it...in a manual failover case, the
	//deployment should be found, and then you would proceed to remove
	//it

	selector := config.LABEL_PG_CLUSTER + "=" + clusterName + "," + config.LABEL_SERVICE_NAME + "=" + clusterName
	log.Debugf("selector in failover get deployments is %s", selector)
	var depList *appsv1.DeploymentList
	depList, err = kubeapi.GetDeployments(clientset, selector, namespace)
	if len(depList.Items) > 0 {
		log.Debug("in failover, the primary deployment is found before removal")
		err = deletePrimary(clientset, namespace, clusterName)
		if err != nil {
			log.Error(err)
			return err
		}
	} else {
		log.Debug("in failover, the primary deployment is NOT found so we will not attempt to remove it")
	}

	updateFailoverStatus(client, task, namespace, clusterName, "deleted primary deployment "+clusterName)

	//trigger the failover on the replica
	err = promote(pod, clientset, client, namespace, restconfig)
	updateFailoverStatus(client, task, namespace, clusterName, "promoting pod "+pod.Name+" target "+target)

	//relabel the deployment with primary labels
	//by setting service-name=clustername
	var upod *v1.Pod
	upod, _, err = kubeapi.GetPod(clientset, pod.Name, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in getting pod during failover relabel")
		return err
	}

	//set the service-name label to the cluster name to match
	//the primary service selector
	log.Debugf("setting label on pod %s=%s", config.LABEL_SERVICE_NAME, clusterName)

	err = kubeapi.AddLabelToPod(clientset, upod, config.LABEL_SERVICE_NAME, clusterName, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in updating pod during failover relabel")
		return err
	}

	targetDepName := upod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]
	log.Debug("targetDepName %s", targetDepName)
	var targetDep *appsv1.Deployment
	targetDep, _, err = kubeapi.GetDeployment(clientset, targetDepName, namespace)
	if err != nil {
		log.Error(err)
		log.Errorf("not found error in getting Deployment during failover relabel %s", targetDepName)
		return err
	}

	err = kubeapi.AddLabelToDeployment(clientset, targetDep, config.LABEL_SERVICE_NAME, clusterName, namespace)
	if err != nil {
		log.Error(err)
		log.Error("error in updating deployment during failover relabel")
		return err
	}

	updateFailoverStatus(client, task, namespace, clusterName, "updating label deployment...pod "+pod.Name+"was the failover target...failover completed")

	//update the pgcluster current-primary to new deployment name
	var found bool
	cluster := crv1.Pgcluster{}
	found, err = kubeapi.Getpgcluster(client, &cluster, clusterName, namespace)
	if !found {
		log.Errorf("could not find pgcluster %s with labels", clusterName)
		return err
	}
	cluster.Spec.UserLabels[config.LABEL_CURRENT_PRIMARY] = targetDepName
	err = util.PatchClusterCRD(client, cluster.Spec.UserLabels, &cluster, namespace)
	if err != nil {
		log.Errorf("failoverlogic: could not patch pgcluster %s with labels", clusterName)
		return err
	}

	return err

}

func updateFailoverStatus(client *rest.RESTClient, task *crv1.Pgtask, namespace, clusterName, message string) {

	log.Debugf("updateFailoverStatus namespace=[%s] taskName=[%s] message=[%s]", namespace, task.Name, message)

	//update the task
	_, err := kubeapi.Getpgtask(client, task, task.ObjectMeta.Name,
		task.ObjectMeta.Namespace)
	if err != nil {
		return
	}

	task.Status.Message = message

	err = kubeapi.Updatepgtask(client,
		task,
		task.ObjectMeta.Name,
		task.ObjectMeta.Namespace)
	if err != nil {
		return
	}

}

func deletePrimary(clientset *kubernetes.Clientset, namespace, clusterName string) error {

	//the primary will be the one with a pod that has a label
	//that looks like service-name=clustername and is not a backrest job
	selector := config.LABEL_SERVICE_NAME + "=" + clusterName + "," + config.LABEL_BACKREST_RESTORE + "!=true," + config.LABEL_BACKREST_JOB + "!=true"
	pods, err := kubeapi.GetPods(clientset, selector, namespace)
	if len(pods.Items) == 0 {
		log.Errorf("no primary pod found when trying to delete primary %s", selector)
		return errors.New("could not find primary pod")
	}
	if len(pods.Items) > 1 {
		log.Errorf("more than 1 primary pod found when trying to delete primary %s", selector)
		return errors.New("more than 1 primary pod found in delete primary logic")
	}

	//update the label to 'fenced' on the pod to fence off traffic from
	//any client or replica using the primary, this effectively
	//stops traffic from the Primary service to the primary pod
	//we are about to delete
	pod := pods.Items[0]

	deploymentToDelete := pod.ObjectMeta.Labels[config.LABEL_DEPLOYMENT_NAME]

	//delete the deployment with pg-cluster=clusterName,primary=true
	log.Debugf("deleting deployment %s", deploymentToDelete)
	err = kubeapi.DeleteDeployment(clientset, deploymentToDelete, namespace)

	err = waitForDelete(deploymentToDelete, pod.Name, clientset, namespace)

	return err
}

func promote(
	pod *v1.Pod,
	clientset *kubernetes.Clientset,
	client *rest.RESTClient, namespace string, restconfig *rest.Config) error {

	//get the target pod that matches the replica-name=target

	command := make([]string, 1)
	command[0] = "/opt/cpm/bin/promote.sh"

	log.Debugf("running Exec with namespace=[%s] podname=[%s] container name=[%s]", namespace, pod.Name, pod.Spec.Containers[0].Name)
	stdout, stderr, err := kubeapi.ExecToPodThroughAPI(restconfig, clientset, command, pod.Spec.Containers[0].Name, pod.Name, namespace, nil)
	log.Debugf("stdout=[%s] stderr=[%s]", stdout, stderr)
	if err != nil {
		log.Error(err)
	}

	return err
}

func waitForDelete(deploymentToDelete, podName string, clientset *kubernetes.Clientset, namespace string) error {
	var tries = 10

	for i := 0; i < tries; i++ {
		pod, _, err := kubeapi.GetPod(clientset, podName, namespace)
		if kerrors.IsNotFound(err) {
			log.Debugf("%s deployment %s pod not found so its safe to proceed on failover", deploymentToDelete, podName)
			return nil
		} else if err != nil {
			log.Error(err)
			log.Error("error getting pod when evaluating old primary in failover %s %s", deploymentToDelete, podName)
			return err
		}
		log.Debugf("waitinf for %s to delete", pod.Name)
		time.Sleep(time.Second * time.Duration(9))
	}

	return errors.New(fmt.Sprintf("timeout waiting for %s %s to delete", deploymentToDelete, podName))

}
<|endoftext|>


<commit_before>package importer

var ESSettings string = `{
    "settings": {
        "analysis": {
            "filter": {
                "map_poi_filter": {
                    "type": "synonym",
                    "synonyms": [
                    	"минфин,министертсво финансов",
                    	"минздрав,министерство здравоохранения",
                    	"минюст,министерство юстиции",
                    	"минтранспорта,министерство транспорта",
                    	"минобразования,министерство образования",
                    	"минкультуры,министерство культуры",
                    	"юракадемия,юридическая академия",
                    	"к-т,кинотеатр",
                    	"маг,магазин",
                    	"тц,торговый центр",
                    	"трк, торгово-развлекательный центр",
                    	"маг,магазин",
						"мц,медицинский центр",
						"мед центр,медицинский центр",
						"мкр,микрорайон",
						"нац (госпиталь),национальный (госпиталь)",
						"дет сад,детский сад",
						"ж/м,жилмассив",
						"жм,жилмассив",
						"КТР",
						"НБТ",
						"к/р,кинотеатр",
						"КНУ",
						"БГУ",
						"г-ца,гостиница",
						"гост,гостиница",
						"МВД,министерство внутренних дел",
						"АУЦА, Американский университет в центральной азии",
						"РОВД,районный отдел внутренних дел",
						"первом,первомайский",
						"сверд,свердловский",
						"октябрь,октябрьский",
						"ГУВД,государственное управление внутренних дел",
						"спец больница,специализированная больница",
						"с,село",
						"кисб,КИКБ",
						"бц,бизнес центр",
						"жд,железнодорожный",
						"сто,станция технического обслуживания",
						"азс,автомобильная заправочная станция",
						"заправка, автомобильная заправочная станция",
						"НХЦ,национальный хирургический центр",
						"ШВК,ШампанВинКомбинат",
						"шампанкомбинат,ШампанВинКомбинат",
						"ГПТ,ГлавПивТрест",
						"БЧК,большой чуйский канал",
						"ДК,дом культуры",
						"МИД,министерство иностранных дел",
						"роддом,родильный дом",
						"гор,городская",
						"корп,корпорация",
						"гор гаи,гаи города бишкек",
						"мед академия,кыргызская государственная медицинская академия",
						"юр академия,кыргызская государственная юридическая академия",
						"воен городок,военный городок",
						"кож завод,кожевенный завод",
						"вечерка,редакция газеты вечерний бишкек",
						"л толстого,льва толстого",
						"д асановой,динары асановой",
						"цсм,центр семейной медицины",
						"центр,центральный",
						"аламед,аламединский",
						"универ,университет",
						"ун-т,университет",
						"гм,гипермаркет",
						"дет больница,детская больница",
						"главпочтамт,главное почтовое отделение",
						"пер,переулок",
						"д сяопина,дэн сяопина",
						"мин юст,министерство юстиции",
						"морфо корпус,морг",
						"энергосбыт,Бишкекский Энергосбыт, ОсОО СеверЭлектро",
						"образ центр,образовательный центральный",
						"гсин,государственная служба исполнения наказаний",
						"респуб,республиканский",
						"обл,областной, областная",
						"мчс,министерство чрезвычайных ситуаций",
						"с батора,сухэ батора",
						"нац банк,национальный банк кыргызской республики",
						"нбкр,национальный банк кыргызской республики",
						"юр фак,юридический факультет",
						"гум фак,гуманитарный факультет",
						"эконом фак,экономический факультет",
						"ген прокуратура,генеральная прокуратура",
						"бгтс,бишкекская городская телефонная сеть",
						"к кийская,кызыл кийская",
						"пив бар,пивной бар",
						"юж ворота,южные ворота",
                    ]
                }
            },
            "analyzer": {
                "map_synonyms": {
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "map_poi_filter"
                    ]
                }
            }
        }
    },
    "mappings":
        {"address":
            {"properties":
                {
                    "street": {
                        "type": "string",
                        "analyzer": "simple"
                    },
                    "centroid": {
                        "type": "geo_point"
                    },
                    "name": {
                        "type": "string",
                        "analyzer": "simple"
                    }
                }
            }
        }
}`
<commit_msg>Fixed es settings<commit_after>package importer

var ESSettings string = `{
    "settings": {
        "analysis": {
            "filter": {
                "map_poi_filter": {
                    "type": "synonym",
                    "synonyms": [
                    	"минфин,министертсво финансов",
                    	"минздрав,министерство здравоохранения",
                    	"минюст,министерство юстиции",
                    	"минтранспорта,министерство транспорта",
                    	"минобразования,министерство образования",
                    	"минкультуры,министерство культуры",
                    	"юракадемия,юридическая академия",
                    	"к-т,кинотеатр",
                    	"маг,магазин",
                    	"тц,торговый центр",
                    	"трк, торгово-развлекательный центр",
                    	"маг,магазин",
						"мц,медицинский центр",
						"мед центр,медицинский центр",
						"мкр,микрорайон",
						"нац (госпиталь),национальный (госпиталь)",
						"дет сад,детский сад",
						"ж/м,жилмассив",
						"жм,жилмассив",
						"КТР",
						"НБТ",
						"к/р,кинотеатр",
						"КНУ",
						"БГУ",
						"г-ца,гостиница",
						"гост,гостиница",
						"МВД,министерство внутренних дел",
						"АУЦА, Американский университет в центральной азии",
						"РОВД,районный отдел внутренних дел",
						"первом,первомайский",
						"сверд,свердловский",
						"октябрь,октябрьский",
						"ГУВД,государственное управление внутренних дел",
						"спец больница,специализированная больница",
						"с,село",
						"кисб,КИКБ",
						"бц,бизнес центр",
						"жд,железнодорожный",
						"сто,станция технического обслуживания",
						"азс,автомобильная заправочная станция",
						"заправка, автомобильная заправочная станция",
						"НХЦ,национальный хирургический центр",
						"ШВК,ШампанВинКомбинат",
						"шампанкомбинат,ШампанВинКомбинат",
						"ГПТ,ГлавПивТрест",
						"БЧК,большой чуйский канал",
						"ДК,дом культуры",
						"МИД,министерство иностранных дел",
						"роддом,родильный дом",
						"гор,городская",
						"корп,корпорация",
						"гор гаи,гаи города бишкек",
						"мед академия,кыргызская государственная медицинская академия",
						"юр академия,кыргызская государственная юридическая академия",
						"воен городок,военный городок",
						"кож завод,кожевенный завод",
						"вечерка,редакция газеты вечерний бишкек",
						"л толстого,льва толстого",
						"д асановой,динары асановой",
						"цсм,центр семейной медицины",
						"центр,центральный",
						"аламед,аламединский",
						"универ,университет",
						"ун-т,университет",
						"гм,гипермаркет",
						"дет больница,детская больница",
						"главпочтамт,главное почтовое отделение",
						"пер,переулок",
						"д сяопина,дэн сяопина",
						"мин юст,министерство юстиции",
						"морфо корпус,морг",
						"энергосбыт,Бишкекский Энергосбыт, ОсОО СеверЭлектро",
						"образ центр,образовательный центральный",
						"гсин,государственная служба исполнения наказаний",
						"респуб,республиканский",
						"обл,областной, областная",
						"мчс,министерство чрезвычайных ситуаций",
						"с батора,сухэ батора",
						"нац банк,национальный банк кыргызской республики",
						"нбкр,национальный банк кыргызской республики",
						"юр фак,юридический факультет",
						"гум фак,гуманитарный факультет",
						"эконом фак,экономический факультет",
						"ген прокуратура,генеральная прокуратура",
						"бгтс,бишкекская городская телефонная сеть",
						"к кийская,кызыл кийская",
						"пив бар,пивной бар",
						"юж ворота,южные ворота"
                    ]
                }
            },
            "analyzer": {
                "map_synonyms": {
                    "tokenizer": "standard",
                    "filter": [
                        "lowercase",
                        "map_poi_filter"
                    ]
                }
            }
        }
    },
    "mappings":
        {"address":
            {"properties":
                {
                    "street": {
                        "type": "string",
                        "analyzer": "simple"
                    },
                    "centroid": {
                        "type": "geo_point"
                    },
                    "name": {
                        "type": "string",
                        "analyzer": "simple"
                    }
                }
            }
        }
}`
<|endoftext|>


<commit_before>// Package scheduler provides the core interface that Empire uses when
// interacting with a cluster of machines to run tasks.
package scheduler

import (
	"io"
	"sync"
	"time"

	"github.com/remind101/empire/pkg/image"
	"golang.org/x/net/context"
)

type App struct {
	// The id of the app.
	ID string

	// An identifier that represents the version of this release.
	Release string

	// The name of the app.
	Name string

	// The application environment.
	Env map[string]string

	// The application labels.
	Labels map[string]string

	// Process that belong to this app.
	Processes []*Process
}

type Process struct {
	// The type of process.
	Type string

	// The Image to run.
	Image image.Image

	// The Command to run.
	Command []string

	// Environment variables to set.
	Env map[string]string

	// Labels to set on the container.
	Labels map[string]string

	// Exposure is the level of exposure for this process.
	Exposure *Exposure

	// Instances is the desired instances of this service to run.
	Instances uint

	// The amount of RAM to allocate to this process in bytes.
	MemoryLimit uint

	// The amount of CPU to allocate to this process, out of 1024. Maps to
	// the --cpu-shares flag for docker.
	CPUShares uint

	// ulimit -u
	Nproc uint
}

// Exposure controls the exposure settings for a process.
type Exposure struct {
	// External means that this process will be exposed to internet facing
	// traffic, as opposed to being internal. How this is used is
	// implementation specific. For ECS, this means that the attached ELB
	// will be "internet-facing".
	External bool

	// The exposure type (e.g. HTTPExposure, HTTPSExposure, TCPExposure).
	Type ExposureType
}

// Exposure represents a service that a process exposes, like HTTP/HTTPS/TCP or
// SSL.
type ExposureType interface {
	Protocol() string
}

// HTTPExposure represents an HTTP exposure.
type HTTPExposure struct{}

func (e *HTTPExposure) Protocol() string { return "http" }

// HTTPSExposure represents an HTTPS exposure
type HTTPSExposure struct {
	// The certificate to attach to the process.
	Cert string
}

func (e *HTTPSExposure) Protocol() string { return "https" }

// Instance represents an Instance of a Process.
type Instance struct {
	Process *Process

	// The instance ID.
	ID string

	// The State that this Instance is in.
	State string

	// The time that this instance was last updated.
	UpdatedAt time.Time
}

type Runner interface {
	// Run runs a process.
	Run(ctx context.Context, app *App, process *Process, in io.Reader, out io.Writer) error
}

// Scheduler is an interface for interfacing with Services.
type Scheduler interface {
	Runner

	// Submit submits an app, creating it or updating it as necessary.
	Submit(context.Context, *App, StatusStream) error

	// Remove removes the App.
	Remove(ctx context.Context, app string) error

	// Instance lists the instances of a Process for an app.
	Instances(ctx context.Context, app string) ([]*Instance, error)

	// Stop stops an instance. The scheduler will automatically start a new
	// instance.
	Stop(ctx context.Context, instanceID string) error
}

// Env merges the App environment with any environment variables provided
// in the process.
func Env(app *App, process *Process) map[string]string {
	return merge(app.Env, process.Env)
}

// Labels merges the App labels with any labels provided in the process.
func Labels(app *App, process *Process) map[string]string {
	return merge(app.Labels, process.Labels)
}

// merges the maps together, favoring keys from the right to the left.
func merge(envs ...map[string]string) map[string]string {
	merged := make(map[string]string)
	for _, env := range envs {
		for k, v := range env {
			merged[k] = v
		}
	}
	return merged
}

type Status struct {
	// A friendly human readable message about the status change.
	Message string
}

// String implements the fmt.Stringer interface.
func (s *Status) String() string {
	return s.Message
}

// StatusStream is an interface for publishing status updates while a scheduler
// is executing.
type StatusStream interface {
	// Publish publishes an update to the status stream
	Publish(Status)

	// Done finalizes the status stream
	Done(error)
}

type SubscribableStream interface {
	Subscribe() <-chan Status
	Error() error
}

// stream implements the StatusStream interface with support for subscribing to
// updates published to the stream.
type stream struct {
	sync.Mutex
	done bool
	err  error
	ch   chan Status
}

// NewStatusStream returns a new instance of the default status stream.
func NewStatusStream() StatusStream {
	return &stream{ch: make(chan Status, 100)}
}

func (s *stream) Publish(status Status) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		// TODO look into using log here
		panic("Publish called on finalized status stream")
	}

	s.publish(status)
}

func (s *stream) publish(status Status) {
	select {
	case s.ch <- status:
	default:
		// Drop
	}
}

func (s *stream) Subscribe() <-chan Status {
	return s.ch
}

func (s *stream) Done(err error) {
	s.Lock()
	defer s.Unlock()

	if !s.done {
		s.done = true
		s.err = err
		close(s.ch)
	}
}

func (s *stream) Error() error {
	return s.err
}
<commit_msg>Warn instead of panicing<commit_after>// Package scheduler provides the core interface that Empire uses when
// interacting with a cluster of machines to run tasks.
package scheduler

import (
	"io"
	"sync"
	"time"

	"golang.org/x/net/context"

	"github.com/remind101/empire/pkg/image"
	"github.com/remind101/pkg/logger"
)

type App struct {
	// The id of the app.
	ID string

	// An identifier that represents the version of this release.
	Release string

	// The name of the app.
	Name string

	// The application environment.
	Env map[string]string

	// The application labels.
	Labels map[string]string

	// Process that belong to this app.
	Processes []*Process
}

type Process struct {
	// The type of process.
	Type string

	// The Image to run.
	Image image.Image

	// The Command to run.
	Command []string

	// Environment variables to set.
	Env map[string]string

	// Labels to set on the container.
	Labels map[string]string

	// Exposure is the level of exposure for this process.
	Exposure *Exposure

	// Instances is the desired instances of this service to run.
	Instances uint

	// The amount of RAM to allocate to this process in bytes.
	MemoryLimit uint

	// The amount of CPU to allocate to this process, out of 1024. Maps to
	// the --cpu-shares flag for docker.
	CPUShares uint

	// ulimit -u
	Nproc uint
}

// Exposure controls the exposure settings for a process.
type Exposure struct {
	// External means that this process will be exposed to internet facing
	// traffic, as opposed to being internal. How this is used is
	// implementation specific. For ECS, this means that the attached ELB
	// will be "internet-facing".
	External bool

	// The exposure type (e.g. HTTPExposure, HTTPSExposure, TCPExposure).
	Type ExposureType
}

// Exposure represents a service that a process exposes, like HTTP/HTTPS/TCP or
// SSL.
type ExposureType interface {
	Protocol() string
}

// HTTPExposure represents an HTTP exposure.
type HTTPExposure struct{}

func (e *HTTPExposure) Protocol() string { return "http" }

// HTTPSExposure represents an HTTPS exposure
type HTTPSExposure struct {
	// The certificate to attach to the process.
	Cert string
}

func (e *HTTPSExposure) Protocol() string { return "https" }

// Instance represents an Instance of a Process.
type Instance struct {
	Process *Process

	// The instance ID.
	ID string

	// The State that this Instance is in.
	State string

	// The time that this instance was last updated.
	UpdatedAt time.Time
}

type Runner interface {
	// Run runs a process.
	Run(ctx context.Context, app *App, process *Process, in io.Reader, out io.Writer) error
}

// Scheduler is an interface for interfacing with Services.
type Scheduler interface {
	Runner

	// Submit submits an app, creating it or updating it as necessary.
	Submit(context.Context, *App, StatusStream) error

	// Remove removes the App.
	Remove(ctx context.Context, app string) error

	// Instance lists the instances of a Process for an app.
	Instances(ctx context.Context, app string) ([]*Instance, error)

	// Stop stops an instance. The scheduler will automatically start a new
	// instance.
	Stop(ctx context.Context, instanceID string) error
}

// Env merges the App environment with any environment variables provided
// in the process.
func Env(app *App, process *Process) map[string]string {
	return merge(app.Env, process.Env)
}

// Labels merges the App labels with any labels provided in the process.
func Labels(app *App, process *Process) map[string]string {
	return merge(app.Labels, process.Labels)
}

// merges the maps together, favoring keys from the right to the left.
func merge(envs ...map[string]string) map[string]string {
	merged := make(map[string]string)
	for _, env := range envs {
		for k, v := range env {
			merged[k] = v
		}
	}
	return merged
}

type Status struct {
	// A friendly human readable message about the status change.
	Message string
}

// String implements the fmt.Stringer interface.
func (s *Status) String() string {
	return s.Message
}

// StatusStream is an interface for publishing status updates while a scheduler
// is executing.
type StatusStream interface {
	// Publish publishes an update to the status stream
	Publish(context.Context, Status)

	// Done finalizes the status stream
	Done(error)
}

type SubscribableStream interface {
	Subscribe() <-chan Status
	Error() error
}

// stream implements the StatusStream interface with support for subscribing to
// updates published to the stream.
type stream struct {
	sync.Mutex
	done bool
	err  error
	ch   chan Status
}

// NewStatusStream returns a new instance of the default status stream.
func NewStatusStream() StatusStream {
	return &stream{ch: make(chan Status, 100)}
}

func (s *stream) Publish(ctx context.Context, status Status) {
	s.Lock()
	defer s.Unlock()

	if s.done {
		logger.Warn(ctx, "Publish called on a finalized stream")
		return
	}

	s.publish(status)
}

func (s *stream) publish(status Status) {
	select {
	case s.ch <- status:
	default:
		// Drop
	}
}

func (s *stream) Subscribe() <-chan Status {
	return s.ch
}

func (s *stream) Done(err error) {
	s.Lock()
	defer s.Unlock()

	if !s.done {
		s.done = true
		s.err = err
		close(s.ch)
	}
}

func (s *stream) Error() error {
	return s.err
}
<|endoftext|>


<commit_before>package scheduler

import (
	"errors"
	"fmt"
	"time"

	"github.com/intelsdi-x/pulse/core"
	"github.com/intelsdi-x/pulse/core/cdata"
	"github.com/intelsdi-x/pulse/core/ctypes"
	"github.com/intelsdi-x/pulse/pkg/schedule"
	"github.com/intelsdi-x/pulse/scheduler/wmap"
)

var (
	ErrMetricManagerNotSet = errors.New("MetricManager is not set.")
	ErrSchedulerNotStarted = errors.New("Scheduler is not started.")
)

type schedulerState int

const (
	schedulerStopped schedulerState = iota
	schedulerStarted
)

// ManagesMetric is implemented by control
// On startup a scheduler will be created and passed a reference to control
type managesMetrics interface {
	collectsMetrics
	publishesMetrics
	processesMetrics
	managesPluginContentTypes
	SubscribeMetricType(mt core.RequestedMetric, cd *cdata.ConfigDataNode) (core.Metric, []error)
	UnsubscribeMetricType(mt core.Metric)
	SubscribeProcessor(name string, ver int, config map[string]ctypes.ConfigValue) []error
	SubscribePublisher(name string, ver int, config map[string]ctypes.ConfigValue) []error
}

// ManagesPluginContentTypes is an interface to a plugin manager that can tell us what content accept and returns are supported.
type managesPluginContentTypes interface {
	GetPluginContentTypes(n string, t core.PluginType, v int) ([]string, []string, error)
}

type collectsMetrics interface {
	CollectMetrics([]core.Metric, time.Time) ([]core.Metric, []error)
}

type publishesMetrics interface {
	PublishMetrics(contentType string, content []byte, pluginName string, pluginVersion int, config map[string]ctypes.ConfigValue) []error
}

type processesMetrics interface {
	ProcessMetrics(contentType string, content []byte, pluginName string, pluginVersion int, config map[string]ctypes.ConfigValue) (string, []byte, []error)
}

type scheduler struct {
	workManager   *workManager
	metricManager managesMetrics
	tasks         *taskCollection
	state         schedulerState
}

type managesWork interface {
	Work(job) job
}

// New returns an instance of the scheduler
// The MetricManager must be set before the scheduler can be started.
// The MetricManager must be started before it can be used.
func New(opts ...workManagerOption) *scheduler {
	s := &scheduler{
		tasks: newTaskCollection(),
	}

	// we are setting the size of the queue and number of workers for
	// collect, process and publish consistently for now
	s.workManager = newWorkManager(opts...)
	s.workManager.Start()

	return s
}

type taskErrors struct {
	errs []error
}

func (t *taskErrors) Errors() []error {
	return t.errs
}

func (s *scheduler) Name() string {
	return "Scheduler"
}

// CreateTask creates and returns task
func (s *scheduler) CreateTask(sch schedule.Schedule, wfMap *wmap.WorkflowMap, opts ...core.TaskOption) (core.Task, core.TaskErrors) {
	// Create a container for task errors
	te := &taskErrors{
		errs: make([]error, 0),
	}

	// Return error if we are not started.
	if s.state != schedulerStarted {
		te.errs = append(te.errs, ErrSchedulerNotStarted)
		return nil, te
	}

	// Ensure the schedule is valid at this point and time.
	if err := sch.Validate(); err != nil {
		te.errs = append(te.errs, err)
		return nil, te
	}

	// Generate a workflow from the workflow map
	wf, err := wmapToWorkflow(wfMap)
	if err != nil {
		te.errs = append(te.errs, ErrSchedulerNotStarted)
		return nil, te
	}

	// Bind plugin content type selections in workflow
	err = wf.BindPluginContentTypes(s.metricManager)

	// Subscribe to MT.
	// If we encounter an error we will unwind successful subscriptions.
	var subscriptions []core.Metric
	for _, m := range wf.metrics {
		cdt, er := wfMap.CollectNode.GetConfigTree()
		if er != nil {
			te.errs = append(te.errs, er)
			continue
		}
		cd := cdt.Get(m.Namespace())
		mt, err := s.metricManager.SubscribeMetricType(m, cd)
		if err == nil {
			subscriptions = append(subscriptions, mt)
		} else {
			te.errs = append(te.errs, err...)
		}
	}

	// Unwind successful subscriptions if we got here with errors (idempotent)
	if len(te.errs) > 0 {
		for _, sub := range subscriptions {
			s.metricManager.UnsubscribeMetricType(sub)
		}
		return nil, te
	}

	//subscribe to processors and publishers
	errs := subscribe(wf.processNodes, wf.publishNodes, s.metricManager)
	if len(errs) > 0 {
		te.errs = append(te.errs, errs...)
		//todo unwind successful pr and pu subscriptions
		return nil, te
	}

	// Create the task object
	task := newTask(sch, subscriptions, wf, s.workManager, s.metricManager, opts...)

	// Add task to taskCollection
	if err := s.tasks.add(task); err != nil {
		te.errs = append(te.errs, err)
		return nil, te
	}

	return task, te
}

//GetTasks returns a copy of the tasks in a map where the task id is the key
func (s *scheduler) GetTasks() map[uint64]core.Task {
	tasks := make(map[uint64]core.Task)
	for id, t := range s.tasks.Table() {
		tasks[id] = t
	}
	return tasks
}

//GetTask provided the task id a task is returned
func (s *scheduler) GetTask(id uint64) (core.Task, error) {
	task := s.tasks.Get(id)
	if task == nil {
		return nil, fmt.Errorf("No task with Id '%v'", id)
	}
	return task, nil
}

func (s *scheduler) StartTask(id uint64) error {
	t := s.tasks.Get(id)
	if t == nil {
		return fmt.Errorf("No task found with id '%v'", id)
	}
	t.Spin()
	return nil
}

// Start starts the scheduler
func (s *scheduler) Start() error {
	if s.metricManager == nil {
		return ErrMetricManagerNotSet
	}
	s.state = schedulerStarted
	return nil
}

func (s *scheduler) Stop() {
	s.state = schedulerStopped
}

// Set metricManager for scheduler
func (s *scheduler) SetMetricManager(mm managesMetrics) {
	s.metricManager = mm
}

// subscribe subscribes to all processors and publishers recursively
func subscribe(prnodes []*processNode, punodes []*publishNode, mm managesMetrics) []error {
	for _, pr := range prnodes {
		errs := mm.SubscribeProcessor(pr.Name, pr.Version, pr.Config.Table())
		if len(errs) > 0 {
			return errs
		}
		subscribe(pr.ProcessNodes, pr.PublishNodes, mm)
	}
	for _, pu := range punodes {
		errs := mm.SubscribePublisher(pu.Name, pu.Version, pu.Config.Table())
		if len(errs) > 0 {
			return errs
		}
	}
	return []error{}
}
<commit_msg>renamed scheduler name<commit_after>package scheduler

import (
	"errors"
	"fmt"
	"time"

	"github.com/intelsdi-x/pulse/core"
	"github.com/intelsdi-x/pulse/core/cdata"
	"github.com/intelsdi-x/pulse/core/ctypes"
	"github.com/intelsdi-x/pulse/pkg/schedule"
	"github.com/intelsdi-x/pulse/scheduler/wmap"
)

var (
	ErrMetricManagerNotSet = errors.New("MetricManager is not set.")
	ErrSchedulerNotStarted = errors.New("Scheduler is not started.")
)

type schedulerState int

const (
	schedulerStopped schedulerState = iota
	schedulerStarted
)

// ManagesMetric is implemented by control
// On startup a scheduler will be created and passed a reference to control
type managesMetrics interface {
	collectsMetrics
	publishesMetrics
	processesMetrics
	managesPluginContentTypes
	SubscribeMetricType(mt core.RequestedMetric, cd *cdata.ConfigDataNode) (core.Metric, []error)
	UnsubscribeMetricType(mt core.Metric)
	SubscribeProcessor(name string, ver int, config map[string]ctypes.ConfigValue) []error
	SubscribePublisher(name string, ver int, config map[string]ctypes.ConfigValue) []error
}

// ManagesPluginContentTypes is an interface to a plugin manager that can tell us what content accept and returns are supported.
type managesPluginContentTypes interface {
	GetPluginContentTypes(n string, t core.PluginType, v int) ([]string, []string, error)
}

type collectsMetrics interface {
	CollectMetrics([]core.Metric, time.Time) ([]core.Metric, []error)
}

type publishesMetrics interface {
	PublishMetrics(contentType string, content []byte, pluginName string, pluginVersion int, config map[string]ctypes.ConfigValue) []error
}

type processesMetrics interface {
	ProcessMetrics(contentType string, content []byte, pluginName string, pluginVersion int, config map[string]ctypes.ConfigValue) (string, []byte, []error)
}

type scheduler struct {
	workManager   *workManager
	metricManager managesMetrics
	tasks         *taskCollection
	state         schedulerState
}

type managesWork interface {
	Work(job) job
}

// New returns an instance of the scheduler
// The MetricManager must be set before the scheduler can be started.
// The MetricManager must be started before it can be used.
func New(opts ...workManagerOption) *scheduler {
	s := &scheduler{
		tasks: newTaskCollection(),
	}

	// we are setting the size of the queue and number of workers for
	// collect, process and publish consistently for now
	s.workManager = newWorkManager(opts...)
	s.workManager.Start()

	return s
}

type taskErrors struct {
	errs []error
}

func (t *taskErrors) Errors() []error {
	return t.errs
}

func (s *scheduler) Name() string {
	return "scheduler"
}

// CreateTask creates and returns task
func (s *scheduler) CreateTask(sch schedule.Schedule, wfMap *wmap.WorkflowMap, opts ...core.TaskOption) (core.Task, core.TaskErrors) {
	// Create a container for task errors
	te := &taskErrors{
		errs: make([]error, 0),
	}

	// Return error if we are not started.
	if s.state != schedulerStarted {
		te.errs = append(te.errs, ErrSchedulerNotStarted)
		return nil, te
	}

	// Ensure the schedule is valid at this point and time.
	if err := sch.Validate(); err != nil {
		te.errs = append(te.errs, err)
		return nil, te
	}

	// Generate a workflow from the workflow map
	wf, err := wmapToWorkflow(wfMap)
	if err != nil {
		te.errs = append(te.errs, ErrSchedulerNotStarted)
		return nil, te
	}

	// Bind plugin content type selections in workflow
	err = wf.BindPluginContentTypes(s.metricManager)

	// Subscribe to MT.
	// If we encounter an error we will unwind successful subscriptions.
	var subscriptions []core.Metric
	for _, m := range wf.metrics {
		cdt, er := wfMap.CollectNode.GetConfigTree()
		if er != nil {
			te.errs = append(te.errs, er)
			continue
		}
		cd := cdt.Get(m.Namespace())
		mt, err := s.metricManager.SubscribeMetricType(m, cd)
		if err == nil {
			subscriptions = append(subscriptions, mt)
		} else {
			te.errs = append(te.errs, err...)
		}
	}

	// Unwind successful subscriptions if we got here with errors (idempotent)
	if len(te.errs) > 0 {
		for _, sub := range subscriptions {
			s.metricManager.UnsubscribeMetricType(sub)
		}
		return nil, te
	}

	//subscribe to processors and publishers
	errs := subscribe(wf.processNodes, wf.publishNodes, s.metricManager)
	if len(errs) > 0 {
		te.errs = append(te.errs, errs...)
		//todo unwind successful pr and pu subscriptions
		return nil, te
	}

	// Create the task object
	task := newTask(sch, subscriptions, wf, s.workManager, s.metricManager, opts...)

	// Add task to taskCollection
	if err := s.tasks.add(task); err != nil {
		te.errs = append(te.errs, err)
		return nil, te
	}

	return task, te
}

//GetTasks returns a copy of the tasks in a map where the task id is the key
func (s *scheduler) GetTasks() map[uint64]core.Task {
	tasks := make(map[uint64]core.Task)
	for id, t := range s.tasks.Table() {
		tasks[id] = t
	}
	return tasks
}

//GetTask provided the task id a task is returned
func (s *scheduler) GetTask(id uint64) (core.Task, error) {
	task := s.tasks.Get(id)
	if task == nil {
		return nil, fmt.Errorf("No task with Id '%v'", id)
	}
	return task, nil
}

func (s *scheduler) StartTask(id uint64) error {
	t := s.tasks.Get(id)
	if t == nil {
		return fmt.Errorf("No task found with id '%v'", id)
	}
	t.Spin()
	return nil
}

// Start starts the scheduler
func (s *scheduler) Start() error {
	if s.metricManager == nil {
		return ErrMetricManagerNotSet
	}
	s.state = schedulerStarted
	return nil
}

func (s *scheduler) Stop() {
	s.state = schedulerStopped
}

// Set metricManager for scheduler
func (s *scheduler) SetMetricManager(mm managesMetrics) {
	s.metricManager = mm
}

// subscribe subscribes to all processors and publishers recursively
func subscribe(prnodes []*processNode, punodes []*publishNode, mm managesMetrics) []error {
	for _, pr := range prnodes {
		errs := mm.SubscribeProcessor(pr.Name, pr.Version, pr.Config.Table())
		if len(errs) > 0 {
			return errs
		}
		subscribe(pr.ProcessNodes, pr.PublishNodes, mm)
	}
	for _, pu := range punodes {
		errs := mm.SubscribePublisher(pu.Name, pu.Version, pu.Config.Table())
		if len(errs) > 0 {
			return errs
		}
	}
	return []error{}
}
<|endoftext|>


<commit_before>package goldb

import "syscall"

func init() {
	// set limit open files in process
	syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{999999, 999999})
}
<commit_msg>fix limit for darwin<commit_after>package goldb

import "syscall"

func init() {
	// set limit open files in process
	syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{10000, 50000})
}
<|endoftext|>


<commit_before>package input

import (
	"errors"
	"github.com/antonienko/goandroid/device"
	"github.com/antonienko/goandroid/display"
	"strconv"
	"strings"
)

// TouchScreen struct represensts touch input susbystem for associated device.
type TouchScreen struct {
	dev  device.Device   // Associated device
	disp display.Display // Associated device display
}

// NewTouchScreen method returns a new TouchScreen and associates it with
// given device.
func NewTouchScreen(dev device.Device) TouchScreen {
	disp := display.NewDisplay(dev)
	return TouchScreen{dev: dev, disp: disp}
}

// Tap method performs a touch operation on specified (x,y) coordinate. It
// returns error on adb operation failure.
func (ts TouchScreen) Tap(x int, y int) error {
	_, err := ts.dev.Shell("input", "tap", strconv.Itoa(x), strconv.Itoa(y))
	return err
}

// Swipe method performs touch swipe operation from given (x1, y1) coordinate
// to (x2, y2) coordinate with specified delay. It returns error on adb operation
// failure.
func (ts TouchScreen) Swipe(x1 int, y1 int, x2 int, y2 int, delay int) error {
	_, err := ts.dev.Shell("input", "touchscreen", "swipe", strconv.Itoa(x1), strconv.Itoa(y1), strconv.Itoa(x2), strconv.Itoa(y2), strconv.Itoa(delay))
	return err
}

// SwipeDown method performs touch swipe down (top --> bottom) operation for
// a number of times defined by given count parameter. It returns error on adb operation failure.
func (ts TouchScreen) SwipeDown(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 2
	x2 := x1
	y1 := h / 4
	y2 := y1 * 3
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// SwipeUp method performs touch swipe up (bottom --> top) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeUp(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 2
	x2 := x1
	y2 := h / 4
	y1 := y2 * 3
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// SwipeLeft method performs touch swipe left (right --> left) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeLeft(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x2 := w / 4
	x1 := x2 * 3
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

func (ts TouchScreen) SwipeLeftDoubleLength(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x2 := 0
	x1 := w-1
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// SwipeRight method performs touch swipe right (right --> left) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeRight(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 4
	x2 := x1 * 3
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

func (ts TouchScreen) SwipeRightDoubleLength(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := 0
	x2 := w-1
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// RawSendEvent sends raw touch input event on given touch device, it takes
// event type, event code and event value as parameter and returns error on
// adb operation failure. make sure you are using correct device path for
// touch device, and can be obtailed easily by GetTouchInputDevice method.
func (ts TouchScreen) RawSendEvent(dev string, eventType int, event int, value int) error {
	_, err := ts.dev.Shell("sendevent", dev, strconv.Itoa(eventType), strconv.Itoa(event), strconv.Itoa(value))
	return err
}

// GetTouchInputDevice method is used to determine correct touch input device
// path on associated android device. It returns error on adb operation failure or
// if device path can not be determined for any reason.
func (ts TouchScreen) GetTouchInputDevice() (string, error) {
	tag1 := "KEY (0001):"
	tag2 := "ABS (0003):"
	out, err := ts.dev.Shell("getevent", "-p")
	if err != nil {
		return "", err
	}
	lines := strings.Split(out, "\n")

	currentDevice := ""
	tag1_match := false
	tag2_match := false
	for _, line := range lines {

		if strings.Contains(line, "add device") {
			tag1_match = false
			tag2_match = false
			parts := strings.Split(line, ":")
			if len(parts) != 2 {
				return "", errors.New("Unable to parse device information")
			}
			currentDevice = strings.TrimSpace(parts[1])
			continue
		}

		if strings.Contains(line, tag1) {
			tag1_match = true
			continue
		}

		if strings.Contains(line, tag2) {
			tag2_match = true
			continue
		}

		if tag1_match && tag2_match {
			return currentDevice, nil
		}
	}
	return "", errors.New("Unable to determine touch device")
}
<commit_msg>Open notifications<commit_after>package input

import (
	"errors"
	"github.com/antonienko/goandroid/device"
	"github.com/antonienko/goandroid/display"
	"strconv"
	"strings"
)

// TouchScreen struct represensts touch input susbystem for associated device.
type TouchScreen struct {
	dev  device.Device   // Associated device
	disp display.Display // Associated device display
}

// NewTouchScreen method returns a new TouchScreen and associates it with
// given device.
func NewTouchScreen(dev device.Device) TouchScreen {
	disp := display.NewDisplay(dev)
	return TouchScreen{dev: dev, disp: disp}
}

// Tap method performs a touch operation on specified (x,y) coordinate. It
// returns error on adb operation failure.
func (ts TouchScreen) Tap(x int, y int) error {
	_, err := ts.dev.Shell("input", "tap", strconv.Itoa(x), strconv.Itoa(y))
	return err
}

// Swipe method performs touch swipe operation from given (x1, y1) coordinate
// to (x2, y2) coordinate with specified delay. It returns error on adb operation
// failure.
func (ts TouchScreen) Swipe(x1 int, y1 int, x2 int, y2 int, delay int) error {
	_, err := ts.dev.Shell("input", "touchscreen", "swipe", strconv.Itoa(x1), strconv.Itoa(y1), strconv.Itoa(x2), strconv.Itoa(y2), strconv.Itoa(delay))
	return err
}

// SwipeDown method performs touch swipe down (top --> bottom) operation for
// a number of times defined by given count parameter. It returns error on adb operation failure.
func (ts TouchScreen) SwipeDown(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 2
	x2 := x1
	y1 := h / 4
	y2 := y1 * 3
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

func (ts TouchScreen) OpenNotifications() error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 2
	x2 := x1
	y1 := 1
	y2 := h-1
	err = ts.Swipe(x1, y1, x2, y2, 1000)
	if err != nil {
		return err
	}
	return nil
}

// SwipeUp method performs touch swipe up (bottom --> top) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeUp(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 2
	x2 := x1
	y2 := h / 4
	y1 := y2 * 3
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// SwipeLeft method performs touch swipe left (right --> left) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeLeft(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x2 := w / 4
	x1 := x2 * 3
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

func (ts TouchScreen) SwipeLeftDoubleLength(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x2 := 0
	x1 := w-1
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// SwipeRight method performs touch swipe right (right --> left) operation for
// a number of times defined by given count parameter. It returns error on
// adb operation failure.
func (ts TouchScreen) SwipeRight(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := w / 4
	x2 := x1 * 3
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

func (ts TouchScreen) SwipeRightDoubleLength(count int) error {
	w, h, err := ts.disp.GetDisplaySize()
	if err != nil {
		return err
	}
	x1 := 0
	x2 := w-1
	y2 := h / 2
	y1 := y2
	for i := 0; i < count; i++ {
		err := ts.Swipe(x1, y1, x2, y2, 1000)
		if err != nil {
			return err
		}
	}
	return nil
}

// RawSendEvent sends raw touch input event on given touch device, it takes
// event type, event code and event value as parameter and returns error on
// adb operation failure. make sure you are using correct device path for
// touch device, and can be obtailed easily by GetTouchInputDevice method.
func (ts TouchScreen) RawSendEvent(dev string, eventType int, event int, value int) error {
	_, err := ts.dev.Shell("sendevent", dev, strconv.Itoa(eventType), strconv.Itoa(event), strconv.Itoa(value))
	return err
}

// GetTouchInputDevice method is used to determine correct touch input device
// path on associated android device. It returns error on adb operation failure or
// if device path can not be determined for any reason.
func (ts TouchScreen) GetTouchInputDevice() (string, error) {
	tag1 := "KEY (0001):"
	tag2 := "ABS (0003):"
	out, err := ts.dev.Shell("getevent", "-p")
	if err != nil {
		return "", err
	}
	lines := strings.Split(out, "\n")

	currentDevice := ""
	tag1_match := false
	tag2_match := false
	for _, line := range lines {

		if strings.Contains(line, "add device") {
			tag1_match = false
			tag2_match = false
			parts := strings.Split(line, ":")
			if len(parts) != 2 {
				return "", errors.New("Unable to parse device information")
			}
			currentDevice = strings.TrimSpace(parts[1])
			continue
		}

		if strings.Contains(line, tag1) {
			tag1_match = true
			continue
		}

		if strings.Contains(line, tag2) {
			tag2_match = true
			continue
		}

		if tag1_match && tag2_match {
			return currentDevice, nil
		}
	}
	return "", errors.New("Unable to determine touch device")
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// Edit opens a file in the users editor and returns the title and body.
func Edit(filePrefix, message string) (string, string, error) {
	contents, err := EditFile(filePrefix, message)
	if err != nil {
		return "", "", err
	}

	return parseTitleBody(strings.TrimSpace(string(contents)))
}

// EditFile opens a file in the users editor and returns the contents. It
// stores a temporary file in your .git directory or /tmp if accessed outside of
// a git repo.
func EditFile(filePrefix, message string) (string, error) {
	var (
		dir string
		err error
	)
	if InsideGitRepo() {
		dir, err = Dir()
		if err != nil {
			return "", err
		}
	} else {
		dir = "/tmp"
	}
	filePath := filepath.Join(dir, fmt.Sprintf("%s_EDITMSG", filePrefix))
	editorPath, err := editorPath()
	if err != nil {
		return "", err
	}
	defer os.Remove(filePath)

	// Write generated/template message to file
	if _, err := os.Stat(filePath); os.IsNotExist(err) && message != "" {
		err = ioutil.WriteFile(filePath, []byte(message), 0644)
		if err != nil {
			return "", err
		}
	}

	cmd := editorCMD(editorPath, filePath)
	err = cmd.Run()
	if err != nil {
		return "", err
	}

	contents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}

	return removeComments(string(contents))
}

func editorPath() (string, error) {
	cmd := New("var", "GIT_EDITOR")
	cmd.Stdout = nil
	e, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(e)), nil
}

func editorCMD(editorPath, filePath string) *exec.Cmd {
	parts := strings.Split(editorPath, " ")
	r := regexp.MustCompile("[nmg]?vi[m]?$")
	args := make([]string, 0, 3)
	if r.MatchString(editorPath) {
		args = append(args, "--cmd", "set ft=gitcommit tw=0 wrap lbr")
	}
	argparts := strings.Join(parts[1:], " ")
	argparts = strings.Replace(argparts, "'", "\"", -1)
	args = append(args, argparts, filePath)
	cmd := exec.Command(parts[0], args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func removeComments(message string) (string, error) {
	// Grab all the lines that don't start with the comment char
	cc := CommentChar()
	r := regexp.MustCompile(`(?m:^)[^` + cc + `].*(?m:$)`)
	cr := regexp.MustCompile(`(?m:^)\s*` + cc)
	parts := r.FindAllString(message, -1)
	noComments := make([]string, 0)
	for _, p := range parts {
		if !cr.MatchString(p) {
			noComments = append(noComments, p)
		}
	}
	return strings.TrimSpace(strings.Join(noComments, "\n")), nil
}

func parseTitleBody(message string) (string, string, error) {
	msg, err := removeComments(message)
	if err != nil {
		return "", "", err
	}

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

	r := regexp.MustCompile(`\n\s*\n`)
	msg = strings.Replace(msg, "\\#", "#", -1)
	parts := r.Split(msg, 2)

	if strings.Contains(parts[0], "\n") {
		return "\n", parts[0], nil
	}
	if len(parts) < 2 {
		return parts[0], "", nil
	}
	return parts[0], parts[1], nil
}
<commit_msg>edit: ignore empty argument<commit_after>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// Edit opens a file in the users editor and returns the title and body.
func Edit(filePrefix, message string) (string, string, error) {
	contents, err := EditFile(filePrefix, message)
	if err != nil {
		return "", "", err
	}

	return parseTitleBody(strings.TrimSpace(string(contents)))
}

// EditFile opens a file in the users editor and returns the contents. It
// stores a temporary file in your .git directory or /tmp if accessed outside of
// a git repo.
func EditFile(filePrefix, message string) (string, error) {
	var (
		dir string
		err error
	)
	if InsideGitRepo() {
		dir, err = Dir()
		if err != nil {
			return "", err
		}
	} else {
		dir = "/tmp"
	}
	filePath := filepath.Join(dir, fmt.Sprintf("%s_EDITMSG", filePrefix))
	editorPath, err := editorPath()
	if err != nil {
		return "", err
	}
	defer os.Remove(filePath)

	// Write generated/template message to file
	if _, err := os.Stat(filePath); os.IsNotExist(err) && message != "" {
		err = ioutil.WriteFile(filePath, []byte(message), 0644)
		if err != nil {
			return "", err
		}
	}

	cmd := editorCMD(editorPath, filePath)
	err = cmd.Run()
	if err != nil {
		return "", err
	}

	contents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}

	return removeComments(string(contents))
}

func editorPath() (string, error) {
	cmd := New("var", "GIT_EDITOR")
	cmd.Stdout = nil
	e, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(e)), nil
}

func editorCMD(editorPath, filePath string) *exec.Cmd {
	parts := strings.Split(editorPath, " ")
	r := regexp.MustCompile("[nmg]?vi[m]?$")
	args := make([]string, 0, 3)
	if r.MatchString(editorPath) {
		args = append(args, "--cmd", "set ft=gitcommit tw=0 wrap lbr")
	}
	argparts := strings.Join(parts[1:], " ")
	if len(argparts) == 0 {
		args = append(args, filePath)
	} else {
		argparts = strings.Replace(argparts, "'", "\"", -1)
		args = append(args, argparts, filePath)
	}
	cmd := exec.Command(parts[0], args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func removeComments(message string) (string, error) {
	// Grab all the lines that don't start with the comment char
	cc := CommentChar()
	r := regexp.MustCompile(`(?m:^)[^` + cc + `].*(?m:$)`)
	cr := regexp.MustCompile(`(?m:^)\s*` + cc)
	parts := r.FindAllString(message, -1)
	noComments := make([]string, 0)
	for _, p := range parts {
		if !cr.MatchString(p) {
			noComments = append(noComments, p)
		}
	}
	return strings.TrimSpace(strings.Join(noComments, "\n")), nil
}

func parseTitleBody(message string) (string, string, error) {
	msg, err := removeComments(message)
	if err != nil {
		return "", "", err
	}

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

	r := regexp.MustCompile(`\n\s*\n`)
	msg = strings.Replace(msg, "\\#", "#", -1)
	parts := r.Split(msg, 2)

	if strings.Contains(parts[0], "\n") {
		return "\n", parts[0], nil
	}
	if len(parts) < 2 {
		return parts[0], "", nil
	}
	return parts[0], parts[1], nil
}
<|endoftext|>


<commit_before>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// Edit opens a file in the users editor and returns the title and body.
func Edit(filePrefix, message string) (string, string, error) {
	contents, err := EditFile(filePrefix, message)
	if err != nil {
		return "", "", err
	}

	return parseTitleBody(strings.TrimSpace(string(contents)))
}

// EditFile opens a file in the users editor and returns the contents. It
// stores a temporary file in your .git directory or /tmp if accessed outside of
// a git repo.
func EditFile(filePrefix, message string) (string, error) {
	var (
		dir string
		err error
	)
	if InsideGitRepo() {
		dir, err = GitDir()
		if err != nil {
			return "", err
		}
	} else {
		dir = "/tmp"
	}
	filePath := filepath.Join(dir, fmt.Sprintf("%s_EDITMSG", filePrefix))
	editorPath, err := editorPath()
	if err != nil {
		return "", err
	}
	defer os.Remove(filePath)

	// Write generated/template message to file
	if _, err := os.Stat(filePath); os.IsNotExist(err) && message != "" {
		err = ioutil.WriteFile(filePath, []byte(message), 0644)
		if err != nil {
			return "", err
		}
	}

	cmd := editorCMD(editorPath, filePath)
	err = cmd.Run()
	if err != nil {
		return "", err
	}

	contents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}

	return removeComments(string(contents))
}

func editorPath() (string, error) {
	cmd := New("var", "GIT_EDITOR")
	cmd.Stdout = nil
	e, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(e)), nil
}

func editorCMD(editorPath, filePath string) *exec.Cmd {
	parts := strings.Split(editorPath, " ")
	r := regexp.MustCompile("[nmg]?vi[m]?$")
	args := make([]string, 0, 3)
	if r.MatchString(editorPath) {
		args = append(args, "--cmd", "set ft=gitcommit tw=0 wrap lbr")
	}
	args = append(args, parts[1:]...)
	args = append(args, filePath)
	cmd := exec.Command(parts[0], args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func removeComments(message string) (string, error) {
	// Grab all the lines that don't start with the comment char
	cc := CommentChar()
	r := regexp.MustCompile(`(?m:^)[^` + cc + `].*(?m:$)`)
	cr := regexp.MustCompile(`(?m:^)\s*` + cc)
	parts := r.FindAllString(message, -1)
	noComments := make([]string, 0)
	for _, p := range parts {
		if !cr.MatchString(p) {
			noComments = append(noComments, p)
		}
	}
	return strings.TrimSpace(strings.Join(noComments, "\n")), nil
}

func parseTitleBody(message string) (string, string, error) {
	msg, err := removeComments(message)
	if err != nil {
		return "", "", err
	}

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

	r := regexp.MustCompile(`\n\s*\n`)
	msg = strings.Replace(msg, "\\#", "#", -1)
	parts := r.Split(msg, 2)

	if strings.Contains(parts[0], "\n") {
		return "\n", parts[0], nil
	}
	if len(parts) < 2 {
		return parts[0], "", nil
	}
	return parts[0], parts[1], nil
}
<commit_msg>edit: Fix GIT_EDITOR evaluation<commit_after>package git

import (
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// Edit opens a file in the users editor and returns the title and body.
func Edit(filePrefix, message string) (string, string, error) {
	contents, err := EditFile(filePrefix, message)
	if err != nil {
		return "", "", err
	}

	return parseTitleBody(strings.TrimSpace(string(contents)))
}

// EditFile opens a file in the users editor and returns the contents. It
// stores a temporary file in your .git directory or /tmp if accessed outside of
// a git repo.
func EditFile(filePrefix, message string) (string, error) {
	var (
		dir string
		err error
	)
	if InsideGitRepo() {
		dir, err = GitDir()
		if err != nil {
			return "", err
		}
	} else {
		dir = "/tmp"
	}
	filePath := filepath.Join(dir, fmt.Sprintf("%s_EDITMSG", filePrefix))
	editorPath, err := editorPath()
	if err != nil {
		return "", err
	}
	defer os.Remove(filePath)

	// Write generated/template message to file
	if _, err := os.Stat(filePath); os.IsNotExist(err) && message != "" {
		err = ioutil.WriteFile(filePath, []byte(message), 0644)
		if err != nil {
			return "", err
		}
	}

	cmd := editorCMD(editorPath, filePath)
	err = cmd.Run()
	if err != nil {
		return "", err
	}

	contents, err := ioutil.ReadFile(filePath)
	if err != nil {
		return "", err
	}

	return removeComments(string(contents))
}

func editorPath() (string, error) {
	cmd := New("var", "GIT_EDITOR")
	cmd.Stdout = nil
	e, err := cmd.Output()
	if err != nil {
		return "", err
	}
	return strings.TrimSpace(string(e)), nil
}

func editorCMD(editorPath, filePath string) *exec.Cmd {
	parts := strings.Split(editorPath, " ")
	r := regexp.MustCompile("[nmg]?vi[m]?$")
	args := make([]string, 0, 3)
	if r.MatchString(editorPath) {
		args = append(args, "--cmd", "set ft=gitcommit tw=0 wrap lbr")
	}
	argparts := strings.Join(parts[1:], " ")
	argparts = strings.Replace(argparts, "'", "\"", -1)
	args = append(args, argparts, filePath)
	cmd := exec.Command(parts[0], args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	return cmd
}

func removeComments(message string) (string, error) {
	// Grab all the lines that don't start with the comment char
	cc := CommentChar()
	r := regexp.MustCompile(`(?m:^)[^` + cc + `].*(?m:$)`)
	cr := regexp.MustCompile(`(?m:^)\s*` + cc)
	parts := r.FindAllString(message, -1)
	noComments := make([]string, 0)
	for _, p := range parts {
		if !cr.MatchString(p) {
			noComments = append(noComments, p)
		}
	}
	return strings.TrimSpace(strings.Join(noComments, "\n")), nil
}

func parseTitleBody(message string) (string, string, error) {
	msg, err := removeComments(message)
	if err != nil {
		return "", "", err
	}

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

	r := regexp.MustCompile(`\n\s*\n`)
	msg = strings.Replace(msg, "\\#", "#", -1)
	parts := r.Split(msg, 2)

	if strings.Contains(parts[0], "\n") {
		return "\n", parts[0], nil
	}
	if len(parts) < 2 {
		return parts[0], "", nil
	}
	return parts[0], parts[1], nil
}
<|endoftext|>


<commit_before>package internal
<commit_msg>Remove unused file internal/testutil.go<commit_after><|endoftext|>


<commit_before>package iobit

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"math/rand"
	"testing"
)

func getNumBits(read, max, align int) int {
	bits := 1
	if align != 32 {
		bits += rand.Intn(32 / align)
	}
	bits *= align
	if read+bits > max {
		bits = max - read
	}
	if bits > 32 {
		panic("too many bits")
	}
	return bits
}

func makeSource(size int) []uint8 {
	src := make([]uint8, size)
	for i := range src {
		src[i] = uint8(rand.Intn(0xFF))
	}
	return src[:]
}

func flushCheck(t *testing.T, w *Writer) {
	err := w.Flush()
	if err != nil {
		t.Fatal("unexpected error during flush", err)
	}
}

func compare(t *testing.T, src, dst []uint8) {
	if bytes.Equal(src, dst) {
		return
	}
	t.Log(hex.Dump(src))
	t.Log(hex.Dump(dst))
	t.Fatal("invalid output")
}

func testWrites(t *testing.T, align int) {
	var buf bytes.Buffer
	w := NewWriter(&buf)
	src := makeSource(512)
	max := len(src) * 8
	for read := 0; read < max; {
		bits := getNumBits(read, max, align)
		idx := read >> 3
		fill := read - idx*8
		if idx*8 > max-64 {
			rewind := max - 64
			fill += idx*8 - rewind
			idx = rewind >> 3
		}
		block := binary.BigEndian.Uint64(src[idx:])
		block >>= uint(64 - bits - fill)
		value := uint32(block & 0xFFFFFFFF)
		BigEndian.PutUint32(w, uint(bits), value)
		read += bits
	}
	flushCheck(t, w)
	compare(t, src, buf.Bytes())
}

func TestWrites(t *testing.T) {
	for i := 32; i > 0; i >>= 1 {
		testWrites(t, i)
	}
}

func TestLittleEndian(t *testing.T) {
	var buf bytes.Buffer
	w := NewWriter(&buf)
	LittleEndian.PutUint64(w, 64, 0x0123456789ABCDEF)
	w.Flush()
	compare(t, buf.Bytes(), []uint8{0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01})
}

func benchWrites(b *testing.B, align int) {
	b.StopTimer()
	var buf bytes.Buffer
	w := NewWriter(&buf)
	for i := 0; i < b.N; i++ {
		bits := uint(getNumBits(0, 1024, align))
		value := rand.Uint32()
		b.StartTimer()
		BigEndian.PutUint32(w, bits, value)
		b.StopTimer()
		buf.Reset()
	}
}

func BenchmarkWrites(b *testing.B) {
	benchWrites(b, 1)
}

func TestFlushOverflow(t *testing.T) {
	var buf bytes.Buffer
	w := NewWriterSize(&buf, 8)
	BigEndian.PutUint64(w, 64, 0)
	BigEndian.PutUint32(w, 32, 0)
	// test w.fill > 32 during flush
	flushCheck(t, w)
}

func TestSmallWriter(t *testing.T) {
	for i := CacheSize; i >= 0; i-- {
		var buf bytes.Buffer
		w := NewWriterSize(&buf, i)
		BigEndian.PutUint64(w, 64, 0)
		BigEndian.PutUint64(w, 64, 0)
		flushCheck(t, w)
	}
}
<commit_msg>writer: add better test against flush overflows<commit_after>package iobit

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"math/rand"
	"testing"
)

func getNumBits(read, max, align int) int {
	bits := 1
	if align != 32 {
		bits += rand.Intn(32 / align)
	}
	bits *= align
	if read+bits > max {
		bits = max - read
	}
	if bits > 32 {
		panic("too many bits")
	}
	return bits
}

func makeSource(size int) []uint8 {
	src := make([]uint8, size)
	for i := range src {
		src[i] = uint8(rand.Intn(0xFF))
	}
	return src[:]
}

func flushCheck(t *testing.T, w *Writer) {
	err := w.Flush()
	if err != nil {
		t.Fatal("unexpected error during flush", err)
	}
}

func compare(t *testing.T, src, dst []uint8) {
	if bytes.Equal(src, dst) {
		return
	}
	t.Log(hex.Dump(src))
	t.Log(hex.Dump(dst))
	t.Fatal("invalid output")
}

func testWrites(t *testing.T, align int) {
	var buf bytes.Buffer
	w := NewWriter(&buf)
	src := makeSource(512)
	max := len(src) * 8
	for read := 0; read < max; {
		bits := getNumBits(read, max, align)
		idx := read >> 3
		fill := read - idx*8
		if idx*8 > max-64 {
			rewind := max - 64
			fill += idx*8 - rewind
			idx = rewind >> 3
		}
		block := binary.BigEndian.Uint64(src[idx:])
		block >>= uint(64 - bits - fill)
		value := uint32(block & 0xFFFFFFFF)
		BigEndian.PutUint32(w, uint(bits), value)
		read += bits
	}
	flushCheck(t, w)
	compare(t, src, buf.Bytes())
}

func TestWrites(t *testing.T) {
	for i := 32; i > 0; i >>= 1 {
		testWrites(t, i)
	}
}

func TestLittleEndian(t *testing.T) {
	var buf bytes.Buffer
	w := NewWriter(&buf)
	LittleEndian.PutUint64(w, 64, 0x0123456789ABCDEF)
	w.Flush()
	compare(t, buf.Bytes(), []uint8{0xEF, 0xCD, 0xAB, 0x89, 0x67, 0x45, 0x23, 0x01})
}

func benchWrites(b *testing.B, align int) {
	b.StopTimer()
	var buf bytes.Buffer
	w := NewWriter(&buf)
	for i := 0; i < b.N; i++ {
		bits := uint(getNumBits(0, 1024, align))
		value := rand.Uint32()
		b.StartTimer()
		BigEndian.PutUint32(w, bits, value)
		b.StopTimer()
		buf.Reset()
	}
}

func BenchmarkWrites(b *testing.B) {
	benchWrites(b, 1)
}

func TestFlushOverflow(t *testing.T) {
	for i := 0; i < CacheSize*2; i++ {
		var buf bytes.Buffer
		w := NewWriterSize(&buf, CacheSize)
		for j := 0; j < i; j++ {
			BigEndian.PutUint32(w, 8, 0)
		}
		flushCheck(t, w)
	}
}

func TestSmallWriter(t *testing.T) {
	for i := CacheSize; i >= 0; i-- {
		var buf bytes.Buffer
		w := NewWriterSize(&buf, i)
		BigEndian.PutUint64(w, 64, 0)
		BigEndian.PutUint64(w, 64, 0)
		flushCheck(t, w)
	}
}
<|endoftext|>


<commit_before>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goncurses

// #include <stdlib.h>
// #include <ncurses.h>
import "C"

import (
	"errors"
	"unsafe"
)

type SlkFormat byte

const (
	SLK_323 SlkFormat = iota // 8 labels; 3-2-3 arrangement
	SLK_44	// 8 labels; 4-4 arrangement
	SLK_PC444	// 12 labels; 4-4-4
	SLK_PC444INDEX // 12 labels; 4-4-4 arrangement with index line
)

type SlkJustify byte
const (
	SLK_LEFT SlkJustify = iota
	SLK_CENTER
	SLK_RIGHT
)

// Initializes the soft-key labels with the given format; keys like the
// F1-F12 keys on most keyboards. After a call to SlkRefresh a bar at the
// bottom of the standard screen returned by Init will be displayed. This
// function MUST be called prior to Init()
func SlkInit(f SlkFormat) {
	C.slk_init(C.int(f))
}

// SlkSet sets the 'labnum' text to the supplied 'label'. Labels must not
// be greater than 8 characters
func SlkSet(labnum int, label string, just SlkJustify) error {
	cstr := C.CString(label)
	defer C.free(unsafe.Pointer(cstr))

	if C.slk_set(C.int(labnum), (*C.char)(cstr), C.int(just)) == C.ERR {
		return errors.New("Soft-keys not initialized")
	}
	return nil
}

// SlkRefresh behaves the same as Window.Refresh. Most applications would use
// SlkNoutRefresh because a Window.Refresh is likely to follow
func SlkRefresh() error {
	if C.slk_refresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkNoutFresh behaves like Window.NoutRefresh
func SlkNoutRefresh() error {
	if C.slk_noutrefresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkLabel returns the label for the given key
func SlkLabel(labnum int) string {
	return C.GoString(C.slk_label(C.int(labnum)))
}

// SlkClear removes the soft-key labels from the screen
func SlkClear() error {
	if C.slk_clear() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkRestore restores the soft-key labels to the screen after an SlkClear()
func SlkRestore() error {
	if C.slk_restore() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkTouch behaves just like Window.Touch
func SlkTouch() error {
	if C.slk_touch() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkColor sets the color pair for the soft-keys
func SlkColor(cp int16) error {
	if C.slk_color(C.short(cp)) == C.ERR {
		return errors.New("Invalid color pair or soft-keys not initialized.")
	}
	return nil
}

// SlkSetAttribute turns attributes on or off
//func SlkSetAttribute(attr Char, on bool) {}
<commit_msg>Gofmt slk<commit_after>// goncurses - ncurses library for Go.
// Copyright 2011 Rob Thornton. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goncurses

// #include <stdlib.h>
// #include <ncurses.h>
import "C"

import (
	"errors"
	"unsafe"
)

type SlkFormat byte

const (
	SLK_323        SlkFormat = iota // 8 labels; 3-2-3 arrangement
	SLK_44                          // 8 labels; 4-4 arrangement
	SLK_PC444                       // 12 labels; 4-4-4
	SLK_PC444INDEX                  // 12 labels; 4-4-4 arrangement with index line
)

type SlkJustify byte

const (
	SLK_LEFT SlkJustify = iota
	SLK_CENTER
	SLK_RIGHT
)

// Initializes the soft-key labels with the given format; keys like the
// F1-F12 keys on most keyboards. After a call to SlkRefresh a bar at the
// bottom of the standard screen returned by Init will be displayed. This
// function MUST be called prior to Init()
func SlkInit(f SlkFormat) {
	C.slk_init(C.int(f))
}

// SlkSet sets the 'labnum' text to the supplied 'label'. Labels must not
// be greater than 8 characters
func SlkSet(labnum int, label string, just SlkJustify) error {
	cstr := C.CString(label)
	defer C.free(unsafe.Pointer(cstr))

	if C.slk_set(C.int(labnum), (*C.char)(cstr), C.int(just)) == C.ERR {
		return errors.New("Soft-keys not initialized")
	}
	return nil
}

// SlkRefresh behaves the same as Window.Refresh. Most applications would use
// SlkNoutRefresh because a Window.Refresh is likely to follow
func SlkRefresh() error {
	if C.slk_refresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkNoutFresh behaves like Window.NoutRefresh
func SlkNoutRefresh() error {
	if C.slk_noutrefresh() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkLabel returns the label for the given key
func SlkLabel(labnum int) string {
	return C.GoString(C.slk_label(C.int(labnum)))
}

// SlkClear removes the soft-key labels from the screen
func SlkClear() error {
	if C.slk_clear() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkRestore restores the soft-key labels to the screen after an SlkClear()
func SlkRestore() error {
	if C.slk_restore() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkTouch behaves just like Window.Touch
func SlkTouch() error {
	if C.slk_touch() == C.ERR {
		return errors.New("Soft-keys not initialized.")
	}
	return nil
}

// SlkColor sets the color pair for the soft-keys
func SlkColor(cp int16) error {
	if C.slk_color(C.short(cp)) == C.ERR {
		return errors.New("Invalid color pair or soft-keys not initialized.")
	}
	return nil
}

// SlkSetAttribute turns attributes on or off
//func SlkSetAttribute(attr Char, on bool) {}
<|endoftext|>


<commit_before>package vxsv

import (
	"bufio"
	"io"
	"strings"
)

func ReadPSQLTable(reader io.Reader, count int64) (*TabularData, error) {
	scanner := bufio.NewScanner(reader)
	scanner.Scan()

	columnString := scanner.Text()
	columns := parseColumns(columnString)

	// Skip the horizontal line
	scanner.Scan()

	rows := [][]string{}

	var i int64
	for i = 0; i < count && scanner.Scan(); i++ {
		// This is the last line that's printed, e.g. (100 rows)
		if scanner.Text()[0] == '(' {
			break
		}

		rows = append(rows, parseRow(columns, scanner.Text()))
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return &TabularData{
		Columns: columns,
		Rows:    rows,
	}, nil
}

// Parses MySQL output format:
//
// +------+------+------+
// | colA | colB | colC |
// +------+------+------+
// | foo  | bar  | baz  |
// | foo2 | bar2 | baz2 |
// +------+------+------+
// 2 rows in set
func ReadMySQLTable(reader io.Reader, count int64) (*TabularData, error) {
	scanner := bufio.NewScanner(reader)

	// Skip leading horizontal line
	scanner.Scan()

	scanner.Scan()
	columnString := scanner.Text()
	columns := parseColumns(columnString[1 : len(columnString)-2])

	// Skip trailing horizontal line
	scanner.Scan()

	rows := [][]string{}

	var i int64
	for i = 0; i < count && scanner.Scan(); i++ {
		row := scanner.Text()

		// last line
		if row[0] == '+' {
			break
		}

		rows = append(rows, parseRow(columns, row[1:len(row)-2]))
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return &TabularData{
		Columns: columns,
		Rows:    rows,
	}, nil
}

func parseColumns(columnString string) []Column {
	split := strings.Split(columnString, " | ")

	columns := make([]Column, len(split))

	for i, col := range split {
		columns[i] = Column{
			Name:  strings.TrimSpace(col),
			Width: len(col),
		}
	}

	// Make sure we skip the leading space in the first column
	columns[0].Width--

	return columns
}

// TODO: doesn't handle multi-line rows
func parseRow(columns []Column, str string) []string {
	row := make([]string, len(columns))

	// Skip leading space
	offset := 1

	for i, col := range columns {
		// Make sure we don't over shoot the string length
		if offset+col.Width >= len(str) {
			row[i] = str[offset:len(str)]
		} else {
			row[i] = str[offset : offset+col.Width]
		}

		row[i] = strings.TrimSpace(row[i])

		offset += col.Width + 3
	}

	return row
}
<commit_msg>Document postgres format<commit_after>package vxsv

import (
	"bufio"
	"io"
	"strings"
)

// Parses Postgres output format:
//
//  colA | colB | colC
// ------+------+-----
//  foo  | bar  | baz
//  foo2 | bar2 | baz2
// (2 rows)
func ReadPSQLTable(reader io.Reader, count int64) (*TabularData, error) {
	scanner := bufio.NewScanner(reader)
	scanner.Scan()

	columnString := scanner.Text()
	columns := parseColumns(columnString)

	// Skip the horizontal line
	scanner.Scan()

	rows := [][]string{}

	var i int64
	for i = 0; i < count && scanner.Scan(); i++ {
		// This is the last line that's printed, e.g. (100 rows)
		if scanner.Text()[0] == '(' {
			break
		}

		rows = append(rows, parseRow(columns, scanner.Text()))
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return &TabularData{
		Columns: columns,
		Rows:    rows,
	}, nil
}

// Parses MySQL output format:
//
// +------+------+------+
// | colA | colB | colC |
// +------+------+------+
// | foo  | bar  | baz  |
// | foo2 | bar2 | baz2 |
// +------+------+------+
// 2 rows in set
func ReadMySQLTable(reader io.Reader, count int64) (*TabularData, error) {
	scanner := bufio.NewScanner(reader)

	// Skip leading horizontal line
	scanner.Scan()

	scanner.Scan()
	columnString := scanner.Text()
	columns := parseColumns(columnString[1 : len(columnString)-2])

	// Skip trailing horizontal line
	scanner.Scan()

	rows := [][]string{}

	var i int64
	for i = 0; i < count && scanner.Scan(); i++ {
		row := scanner.Text()

		// last line
		if row[0] == '+' {
			break
		}

		rows = append(rows, parseRow(columns, row[1:len(row)-2]))
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return &TabularData{
		Columns: columns,
		Rows:    rows,
	}, nil
}

func parseColumns(columnString string) []Column {
	split := strings.Split(columnString, " | ")

	columns := make([]Column, len(split))

	for i, col := range split {
		columns[i] = Column{
			Name:  strings.TrimSpace(col),
			Width: len(col),
		}
	}

	// Make sure we skip the leading space in the first column
	columns[0].Width--

	return columns
}

// TODO: doesn't handle multi-line rows
func parseRow(columns []Column, str string) []string {
	row := make([]string, len(columns))

	// Skip leading space
	offset := 1

	for i, col := range columns {
		// Make sure we don't over shoot the string length
		if offset+col.Width >= len(str) {
			row[i] = str[offset:len(str)]
		} else {
			row[i] = str[offset : offset+col.Width]
		}

		row[i] = strings.TrimSpace(row[i])

		offset += col.Width + 3
	}

	return row
}
<|endoftext|>