<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var (
	Version   string
	BuildTime string
	logger    *log.Logger
)

func debugf(format string, args ...interface{}) {
	if os.Getenv("DRD_DEBUG") == "1" {
		logger.Output(2, fmt.Sprintf(format, args...))
	}
}

func makeTicker(bpm int, step int) *time.Ticker {
	step = step / 4
	timing := (time.Minute / time.Duration(bpm)) / time.Duration(step)
	debugf("makeTicker(): timing: %v", timing)
	return time.NewTicker(timing)
}

func player(playQ chan part) {
	eventQueue := make(chan event)
	dacapo := make(chan bool)
	ticker := time.NewTicker(time.Millisecond)
	debugf("player(): starting player loop")
	go func() { dacapo <- true }()
	for {
		select {
		case e := <-eventQueue:
			go playChord(e)
			<-ticker.C
		case <-dacapo:
			debugf("player(): dacapo")
			currentPart := <-playQ
			ticker.Stop()
			ticker = makeTicker(currentPart.Bpm, currentPart.Step)
			fmt.Printf("> %s (%d/%d)\n", currentPart.Name, currentPart.Bpm, currentPart.Step)
			go func() {
				channels, notes := text2matrix(currentPart.Set, currentPart.Lanes)
				debugf("player(): %v", channels)
				debugf("player(): %v", notes)
				// sanity check before transposing
				// FIXME: should probably be done in the matrix lib
				err := notes.check()
				if err != nil {
					logger.Fatalf("part \"%s\" has wrong format: %v", currentPart.Name, err)
				}
				vmap := genVelocityMap(currentPart).transpose()
				cmap := channels.transpose()
				for i, c := range notes.transpose() {
					eventQueue <- event{
						Notes:      c,
						Velocities: vmap[i],
						Channels:   cmap[i],
					}
				}
				dacapo <- true
			}()
		}
	}
}

func getDrumsfile(drumsfile string) (map[string]part, map[string]seq) {
	drums := new(drums)
	drums.loadFromFile(drumsfile)
	sets := drums.getSets()
	parts := drums.getParts(sets)
	seqs := drums.getSeqs()
	numSets := len(sets)
	numParts := len(parts)
	numSeqs := len(seqs)
	fmt.Printf("%d sets, %d parts, %d seqs\n", numSets, numParts, numSeqs)
	debugf("getDrumsfile(): sets: %+v", sets)
	debugf("getDrumsfile(): parts: %+v", parts)
	debugf("getDrumsfile(): seqs: %+v", seqs)
	if numSets < 1 {
		logger.Fatalf("no sets found")
	}
	if numParts < 1 {
		logger.Fatalf("no parts found")
	}
	if numSeqs < 1 {
		logger.Fatalf("no seqs found")
	}
	if _, ok := seqs["start"]; !ok {
		logger.Fatalf("start sequence not found")
	}
	return parts, seqs
}

func feeder(drumsfile string, playQ chan part) {
	parts, seqs := getDrumsfile(drumsfile)
	for _, part := range seqs["precount"] {
		playQ <- parts[part]
	}
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGUSR1)
	debugf("installed signal handler")
	for {
		select {
		case sig := <-sigc:
			debugf("feeder(): got signal %v, re-reading drumsfile", sig)
			fmt.Println("re-reading input file")
			parts, seqs = getDrumsfile(drumsfile)
			debugf("feeder(): done re-reading drumsfile", sig)
		default:
			for _, partname := range seqs["start"] {
				debugf("feeder(): next: %v", partname)
				if part, ok := parts[partname]; !ok {
					logger.Printf("unknown part \"%s\"", partname)
					// avoid busy loop when all parts are unknown
					time.Sleep(time.Millisecond * 50)
				} else {
					playQ <- part
				}
			}
		}
	}
}

func checkErr(err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func main() {
	logger = log.New(os.Stderr, "", log.Lshortfile)
	fmt.Printf("droguedrums %s (built %s)\n", Version, BuildTime)

	var chosenPort int
	flag.IntVar(&chosenPort, "port", -1, "choose output port")
	flag.Parse()

	var drumsfile string
	if flag.NArg() > 0 {
		drumsfile = flag.Args()[0]
	}

	err := initMidi(chosenPort)
	checkErr(err)

	defer closeMidi()

	if drumsfile == "" {
		fmt.Println("no input file")
		os.Exit(1)
	}
	fmt.Println("-- player starting --")

	playQ := make(chan part)
	go player(playQ)
	feeder(drumsfile, playQ)
}
<commit_msg>show input file name on startup<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

var (
	Version   string
	BuildTime string
	logger    *log.Logger
)

func debugf(format string, args ...interface{}) {
	if os.Getenv("DRD_DEBUG") == "1" {
		logger.Output(2, fmt.Sprintf(format, args...))
	}
}

func makeTicker(bpm int, step int) *time.Ticker {
	step = step / 4
	timing := (time.Minute / time.Duration(bpm)) / time.Duration(step)
	debugf("makeTicker(): timing: %v", timing)
	return time.NewTicker(timing)
}

func player(playQ chan part) {
	eventQueue := make(chan event)
	dacapo := make(chan bool)
	ticker := time.NewTicker(time.Millisecond)
	debugf("player(): starting player loop")
	go func() { dacapo <- true }()
	for {
		select {
		case e := <-eventQueue:
			go playChord(e)
			<-ticker.C
		case <-dacapo:
			debugf("player(): dacapo")
			currentPart := <-playQ
			ticker.Stop()
			ticker = makeTicker(currentPart.Bpm, currentPart.Step)
			fmt.Printf("> %s (%d/%d)\n", currentPart.Name, currentPart.Bpm, currentPart.Step)
			go func() {
				channels, notes := text2matrix(currentPart.Set, currentPart.Lanes)
				debugf("player(): %v", channels)
				debugf("player(): %v", notes)
				// sanity check before transposing
				// FIXME: should probably be done in the matrix lib
				err := notes.check()
				if err != nil {
					logger.Fatalf("part \"%s\" has wrong format: %v", currentPart.Name, err)
				}
				vmap := genVelocityMap(currentPart).transpose()
				cmap := channels.transpose()
				for i, c := range notes.transpose() {
					eventQueue <- event{
						Notes:      c,
						Velocities: vmap[i],
						Channels:   cmap[i],
					}
				}
				dacapo <- true
			}()
		}
	}
}

func getDrumsfile(drumsfile string) (map[string]part, map[string]seq) {
	drums := new(drums)
	drums.loadFromFile(drumsfile)
	sets := drums.getSets()
	parts := drums.getParts(sets)
	seqs := drums.getSeqs()
	numSets := len(sets)
	numParts := len(parts)
	numSeqs := len(seqs)
	fmt.Printf("%d sets, %d parts, %d seqs\n", numSets, numParts, numSeqs)
	debugf("getDrumsfile(): sets: %+v", sets)
	debugf("getDrumsfile(): parts: %+v", parts)
	debugf("getDrumsfile(): seqs: %+v", seqs)
	if numSets < 1 {
		logger.Fatalf("no sets found")
	}
	if numParts < 1 {
		logger.Fatalf("no parts found")
	}
	if numSeqs < 1 {
		logger.Fatalf("no seqs found")
	}
	if _, ok := seqs["start"]; !ok {
		logger.Fatalf("start sequence not found")
	}
	return parts, seqs
}

func feeder(drumsfile string, playQ chan part) {
	parts, seqs := getDrumsfile(drumsfile)
	for _, part := range seqs["precount"] {
		playQ <- parts[part]
	}
	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGUSR1)
	debugf("installed signal handler")
	for {
		select {
		case sig := <-sigc:
			debugf("feeder(): got signal %v, re-reading drumsfile", sig)
			fmt.Println("re-reading input file")
			parts, seqs = getDrumsfile(drumsfile)
			debugf("feeder(): done re-reading drumsfile", sig)
		default:
			for _, partname := range seqs["start"] {
				debugf("feeder(): next: %v", partname)
				if part, ok := parts[partname]; !ok {
					logger.Printf("unknown part \"%s\"", partname)
					// avoid busy loop when all parts are unknown
					time.Sleep(time.Millisecond * 50)
				} else {
					playQ <- part
				}
			}
		}
	}
}

func checkErr(err error) {
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func main() {
	logger = log.New(os.Stderr, "", log.Lshortfile)
	fmt.Printf("droguedrums %s (built %s)\n", Version, BuildTime)

	var chosenPort int
	flag.IntVar(&chosenPort, "port", -1, "choose output port")
	flag.Parse()

	var drumsfile string
	if flag.NArg() > 0 {
		drumsfile = flag.Args()[0]
	}

	err := initMidi(chosenPort)
	checkErr(err)

	defer closeMidi()

	if drumsfile == "" {
		fmt.Println("no input file")
		os.Exit(1)
	} else {
		fmt.Printf("input file: %s\n", drumsfile)
	}
	fmt.Println("-- player starting --")

	playQ := make(chan part)
	go player(playQ)
	feeder(drumsfile, playQ)
}
<|endoftext|>


<commit_before>package disco

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

// Test_NewClient tests that a new disco client can be instantiated. Nothing more.
func Test_NewClient(t *testing.T) {
	c := New("https://discovery.rqlite.com")
	if c == nil {
		t.Fatal("failed to create new disco client")
	}

	if c.URL() != "https://discovery.rqlite.com" {
		t.Fatal("configured address of disco service is incorrect")
	}
}

// Test_ClientRegisterBadRequest tests how the client responds to a 400 from the Discovery Service.
func Test_ClientRegisterBadRequest(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusBadRequest)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 400 from server")
	}
}

// Test_ClientRegisterNotFound tests how the client responds to a 404 from the Discovery Service.
func Test_ClientRegisterNotFound(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusNotFound)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 404 from server")
	}
}

func Test_ClientRegisterForbidden(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusForbidden)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 403 from server")
	}
}

func Test_ClientRegisterRequestOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}

		if r.URL.String() != "/1234" {
			t.Fatalf("Request URL is wrong, got: %s", r.URL.String())
		}

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			t.Fatalf("failed to read request from client: %s", err.Error())
		}

		m := map[string]string{}
		if err := json.Unmarshal(b, &m); err != nil {
			t.Fatalf("failed to unmarshal request from client: %s", err.Error())
		}

		if m["addr"] != "http://127.0.0.1" {
			t.Fatalf("incorrect join address supplied by client: %s", m["addr"])
		}

		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://127.0.0.1"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://127.0.0.1")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 1 {
		t.Fatalf("failed to receive correct list of nodes, got %v", disco.Nodes)
	}
}

func Test_ClientRegisterRequestRedirectOK(t *testing.T) {
	ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}

		if r.URL.String() != "/1234" {
			t.Fatalf("Request URL is wrong, got: %s", r.URL.String())
		}

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			t.Fatalf("failed to read request from client: %s", err.Error())
		}

		m := map[string]string{}
		if err := json.Unmarshal(b, &m); err != nil {
			t.Fatalf("failed to unmarshal request from client: %s", err.Error())
		}

		if m["addr"] != "http://127.0.0.1" {
			t.Fatalf("incorrect join address supplied by client: %s", m["addr"])
		}

		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://127.0.0.1"]}`)
	}))
	defer ts1.Close()
	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, ts1.URL+"/1234", http.StatusMovedPermanently)
	}))

	c := New(ts2.URL)
	disco, err := c.Register("1234", "http://127.0.0.1")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 1 {
		t.Fatalf("failed to receive correct list of nodes, got %v", disco.Nodes)
	}
}

func Test_ClientRegisterFollowerOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://1.1.1.1", "http://2.2.2.2"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://2.2.2.2")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 2 {
		t.Fatalf("failed to receive non-empty list of nodes")
	}
	if disco.Nodes[0] != `http://1.1.1.1` {
		t.Fatalf("got incorrect node, got %v", disco.Nodes[0])
	}
}

func Test_ClientRegisterFollowerMultiOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://1.1.1.1", "http://2.2.2.2", "http://3.3.3.3"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://3.3.3.3")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 3 {
		t.Fatalf("failed to receive non-empty list of nodes")
	}
	if disco.Nodes[0] != `http://1.1.1.1` {
		t.Fatalf("got incorrect first node, got %v", disco.Nodes[0])
	}
	if disco.Nodes[1] != `http://2.2.2.2` {
		t.Fatalf("got incorrect second node, got %v", disco.Nodes[1])
	}
	if disco.Nodes[2] != `http://3.3.3.3` {
		t.Fatalf("got incorrect third node, got %v", disco.Nodes[1])
	}
}
<commit_msg>Update client_test.go<commit_after>package disco

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"
)

// Test_NewClient tests that a new disco client can be instantiated. Nothing more.
func Test_NewClient(t *testing.T) {
	c := New("https://discovery.rqlite.com")
	if c == nil {
		t.Fatal("failed to create new disco client")
	}

	if c.URL() != "https://discovery.rqlite.com" {
		t.Fatal("configured address of disco service is incorrect")
	}
}

// Test_ClientRegisterBadRequest tests how the client responds to a 400 from the Discovery Service.
func Test_ClientRegisterBadRequest(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusBadRequest)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 400 from server")
	}
}

// Test_ClientRegisterNotFound tests how the client responds to a 404 from the Discovery Service.
func Test_ClientRegisterNotFound(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusNotFound)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 404 from server")
	}
}

// Test_ClientRegisterForbidden tests how the client responds to a 403 from the Discovery Service.
func Test_ClientRegisterForbidden(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		w.WriteHeader(http.StatusForbidden)
	}))
	defer ts.Close()

	c := New(ts.URL)
	_, err := c.Register("1234", "http://127.0.0.1")
	if err == nil {
		t.Fatalf("failed to receive error on 403 from server")
	}
}

func Test_ClientRegisterRequestOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}

		if r.URL.String() != "/1234" {
			t.Fatalf("Request URL is wrong, got: %s", r.URL.String())
		}

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			t.Fatalf("failed to read request from client: %s", err.Error())
		}

		m := map[string]string{}
		if err := json.Unmarshal(b, &m); err != nil {
			t.Fatalf("failed to unmarshal request from client: %s", err.Error())
		}

		if m["addr"] != "http://127.0.0.1" {
			t.Fatalf("incorrect join address supplied by client: %s", m["addr"])
		}

		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://127.0.0.1"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://127.0.0.1")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 1 {
		t.Fatalf("failed to receive correct list of nodes, got %v", disco.Nodes)
	}
}

func Test_ClientRegisterRequestRedirectOK(t *testing.T) {
	ts1 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}

		if r.URL.String() != "/1234" {
			t.Fatalf("Request URL is wrong, got: %s", r.URL.String())
		}

		b, err := ioutil.ReadAll(r.Body)
		if err != nil {
			t.Fatalf("failed to read request from client: %s", err.Error())
		}

		m := map[string]string{}
		if err := json.Unmarshal(b, &m); err != nil {
			t.Fatalf("failed to unmarshal request from client: %s", err.Error())
		}

		if m["addr"] != "http://127.0.0.1" {
			t.Fatalf("incorrect join address supplied by client: %s", m["addr"])
		}

		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://127.0.0.1"]}`)
	}))
	defer ts1.Close()
	ts2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, ts1.URL+"/1234", http.StatusMovedPermanently)
	}))

	c := New(ts2.URL)
	disco, err := c.Register("1234", "http://127.0.0.1")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 1 {
		t.Fatalf("failed to receive correct list of nodes, got %v", disco.Nodes)
	}
}

func Test_ClientRegisterFollowerOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://1.1.1.1", "http://2.2.2.2"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://2.2.2.2")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 2 {
		t.Fatalf("failed to receive non-empty list of nodes")
	}
	if disco.Nodes[0] != `http://1.1.1.1` {
		t.Fatalf("got incorrect node, got %v", disco.Nodes[0])
	}
}

func Test_ClientRegisterFollowerMultiOK(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Fatalf("Client did not use POST")
		}
		fmt.Fprintln(w, `{"created_at": "2017-02-17 04:49:05.079125", "disco_id": "68d6c7cc-f4cc-11e6-a170-2e79ea0be7b1", "nodes": ["http://1.1.1.1", "http://2.2.2.2", "http://3.3.3.3"]}`)
	}))
	defer ts.Close()

	c := New(ts.URL)
	disco, err := c.Register("1234", "http://3.3.3.3")
	if err != nil {
		t.Fatalf("failed to register: %s", err.Error())
	}
	if len(disco.Nodes) != 3 {
		t.Fatalf("failed to receive non-empty list of nodes")
	}
	if disco.Nodes[0] != `http://1.1.1.1` {
		t.Fatalf("got incorrect first node, got %v", disco.Nodes[0])
	}
	if disco.Nodes[1] != `http://2.2.2.2` {
		t.Fatalf("got incorrect second node, got %v", disco.Nodes[1])
	}
	if disco.Nodes[2] != `http://3.3.3.3` {
		t.Fatalf("got incorrect third node, got %v", disco.Nodes[1])
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/omniscale/magnacarto/mml"

	"code.google.com/p/go.net/websocket"

	"github.com/omniscale/magnacarto"
	"github.com/omniscale/magnacarto/builder"
	mapnikBuilder "github.com/omniscale/magnacarto/builder/mapnik"
	"github.com/omniscale/magnacarto/builder/mapserver"
	"github.com/omniscale/magnacarto/config"
	"github.com/omniscale/magnacarto/maps"
	mssPkg "github.com/omniscale/magnacarto/mss"
	"github.com/omniscale/magnacarto/render"

	"github.com/omniscale/go-mapnik"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
)

type magnaserv struct {
	config       *config.Magnacarto
	builderCache *builder.Cache
	mapnikMaker  builder.MapMaker
}

func (s *magnaserv) styleParams(r *http.Request) (mml string, mss []string) {
	baseDir := s.config.StylesDir
	base := r.FormValue("base")
	if base != "" {
		baseDir = filepath.Join(baseDir, base)
	}

	mml = r.FormValue("mml")
	if mml != "" {
		mml = filepath.Join(baseDir, mml)
	}

	mssList := r.FormValue("mss")
	if mssList != "" {
		for _, f := range strings.Split(mssList, ",") {
			mss = append(mss, filepath.Join(baseDir, f))
		}
	}

	return mml, mss
}

func (s *magnaserv) render(w http.ResponseWriter, r *http.Request) {
	mapReq, err := maps.ParseMapRequest(r)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var maker builder.MapMaker
	renderer := mapReq.Query.Get("RENDERER")
	if renderer == "mapserver" {
		maker = mapserver.Maker
	} else {
		maker = s.mapnikMaker
	}

	styleFile := mapReq.Query.Get("FILE")
	if styleFile == "" {
		mml, mss := s.styleParams(r)
		if mml == "" {
			log.Println("missing mml param in request")
			http.Error(w, "missing mml param", http.StatusBadRequest)
			return
		}

		styleFile, err = s.builderCache.StyleFile(maker, mml, mss)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	var b []byte
	if renderer == "mapserver" {
		mapReq.Format = mapReq.Query.Get("FORMAT") // use requested format, not internal mapnik format
		b, err = render.MapServer(s.config.MapServer.DevBin, styleFile, renderReq(mapReq))
	} else {
		b, err = render.Mapnik(styleFile, renderReq(mapReq))

	}
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	w.Header().Add("Content-Type", "image/png")
	w.Header().Add("Content-Length", strconv.FormatUint(uint64(len(b)), 10))

	io.Copy(w, bytes.NewBuffer(b))
}

func (s *magnaserv) projects(w http.ResponseWriter, r *http.Request) {
	projects, err := findProjects(s.config.StylesDir)
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	w.Header().Add("Content-Type", "application/json")

	enc := json.NewEncoder(w)
	err = enc.Encode(struct {
		Projects []project `json:"projects"`
	}{Projects: projects})
	if err != nil {
		s.internalError(w, r, err)
		return
	}
}

func (s *magnaserv) mml(w http.ResponseWriter, r *http.Request) {
	baseName := mux.Vars(r)["base"]
	mmlName := mux.Vars(r)["mml"]

	fileName, err := filepath.Abs(filepath.Join(s.config.StylesDir, baseName, mmlName+".mml"))
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	if r.Method == "POST" {
		if err := writeCheckedMML(r.Body, fileName); err != nil {
			s.internalError(w, r, err)
			return
		}
		return
	}
	http.ServeFile(w, r, fileName)
}

func (s *magnaserv) mcp(w http.ResponseWriter, r *http.Request) {
	baseName := mux.Vars(r)["base"]
	mcpName := mux.Vars(r)["mcp"]

	fileName, err := filepath.Abs(filepath.Join(s.config.StylesDir, baseName, mcpName+".mcp"))
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	if r.Method == "POST" {
		if err := writeCheckedJSON(r.Body, fileName); err != nil {
			s.internalError(w, r, err)
			return
		}
		return
	}
	http.ServeFile(w, r, fileName)
}

// writeCheckedMML writes MML from io.ReadCloser to fileName.
// Checks if r is a valid MML before (over)writing file.
func writeCheckedMML(r io.ReadCloser, fileName string) error {
	return writeCheckedFile(r, fileName, func(r io.Reader) error {
		_, err := mml.Parse(r)
		return err
	})
	return nil
}

// writeCheckedMML writes JSON from io.ReadCloser to fileName.
// Checks if r is a valid JSON before (over)writing file.
func writeCheckedJSON(r io.ReadCloser, fileName string) error {
	return writeCheckedFile(r, fileName, func(r io.Reader) error {
		d := json.NewDecoder(r)
		tmp := map[string]interface{}{}
		return d.Decode(&tmp)
	})
	return nil
}

func (s *magnaserv) internalError(w http.ResponseWriter, r *http.Request, err error) {
	log.Print(err)
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("internal error"))
}

type fileChecker func(io.Reader) error

func writeCheckedFile(r io.ReadCloser, fileName string, checker fileChecker) error {
	tmpFile := fileName + ".tmp-" + strconv.FormatInt(int64(rand.Int31()), 16)
	f, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	defer r.Close()
	defer os.Remove(tmpFile) // make sure temp file always gets removed

	_, err = io.Copy(f, r)
	f.Close()
	if err != nil {
		return err
	}

	f, err = os.Open(tmpFile)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := checker(f); err != nil {
		return err
	}
	if err := os.Rename(tmpFile, fileName); err != nil {
		return err
	}
	return nil
}

func renderReq(r *maps.Request) render.Request {
	result := render.Request{}
	result.BBOX[0] = r.BBOX.MinX
	result.BBOX[1] = r.BBOX.MinY
	result.BBOX[2] = r.BBOX.MaxX
	result.BBOX[3] = r.BBOX.MaxY
	result.Width = r.Width
	result.Height = r.Height
	result.EPSGCode = r.EPSGCode
	result.Format = r.Format
	return result
}

func (s *magnaserv) changes(ws *websocket.Conn) {
	mml, mss := s.styleParams(ws.Request())
	if mml == "" {
		log.Println("missing mml param in request")
		ws.Close()
		return
	}

	var maker builder.MapMaker

	renderer := ws.Request().Form.Get("renderer")
	if renderer == "mapserver" {
		maker = mapserver.Maker
	} else {
		maker = s.mapnikMaker
	}

	done := make(chan struct{})
	updatec, errc := s.builderCache.Notify(maker, mml, mss, done)

	// read and discard anything from client, signal close on any error
	closeWs := make(chan struct{})
	go func() {
		readbuf := make([]byte, 0, 16)
		for {
			if _, err := ws.Read(readbuf); err != nil {
				if err != io.EOF {
					log.Println("ws read err: ", err)
				}
				closeWs <- struct{}{}
				return
			}
		}
	}()

	var lastMsgSent time.Time
	var lastMsg []byte
	for {
		select {
		case <-closeWs:
			done <- struct{}{}
			ws.Close()
			return
		case update := <-updatec:
			var msg []byte
			var err error
			if update.Err != nil {
				if parseErr, ok := update.Err.(*mssPkg.ParseError); ok {
					msg, err = json.Marshal(struct {
						FullError string `json:"full_error"`
						Error     string `json:"error"`
						Filename  string `json:"filename"`
						Line      int    `json:"line"`
						Column    int    `json:"column"`
					}{parseErr.Error(), parseErr.Err, parseErr.Filename, parseErr.Line, parseErr.Column})
				} else {
					msg, err = json.Marshal(struct {
						Error string `json:"error"`
					}{update.Err.Error()})
				}
			} else {
				msg, err = json.Marshal(struct {
					UpdatedAt  time.Time `json:"updated_at"`
					UpdatedMML bool      `json:"updated_mml"`
				}{update.Time, update.UpdatedMML})

			}
			if err != nil {
				log.Println("error encoding json", err)
				ws.Close()
				return
			}
			// debounce notifications
			if !lastMsgSent.Add(2*time.Second).After(time.Now()) || bytes.Compare(msg, lastMsg) != 0 {
				if _, err := ws.Write(msg); err != nil {
					done <- struct{}{}
					ws.Close()
					return
				}
				lastMsg = msg
				lastMsgSent = time.Now()
			}
		case err := <-errc:
			log.Println("error:", err)
			ws.Close()
			return
		}
	}
}

func main() {
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	var listenAddr = flag.String("listen", "localhost:7070", "listen address")
	var configFile = flag.String("config", "magnaserv.tml", "config")
	var version = flag.Bool("version", false, "print version and exit")

	flag.Parse()

	if *version {
		fmt.Println(magnacarto.Version)
		os.Exit(0)
	}

	conf, err := config.Load(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	locator := conf.Locator()

	builderCache := builder.NewCache(locator)
	if conf.OutDir != "" {
		if err := os.MkdirAll(conf.OutDir, 0755); err != nil && !os.IsExist(err) {
			log.Fatal("unable to create outdir: ", err)
		}
		builderCache.SetDestination(conf.OutDir)
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			builderCache.ClearAll()
			os.Exit(1)
		}
	}()

	r := mux.NewRouter()
	handler := magnaserv{config: conf, builderCache: builderCache}

	log.Println("using Mapnik", mapnik.Version.String)
	if mapnik.Version.Major == 2 {
		handler.mapnikMaker = mapnikBuilder.Maker2
	} else {
		handler.mapnikMaker = mapnikBuilder.Maker3
	}
	v1 := r.PathPrefix("/api/v1").Subrouter()
	v1.HandleFunc("/map", handler.render)
	v1.HandleFunc("/projects/{base}/{mml}.mml", handler.mml)
	v1.HandleFunc("/projects/{base}/{mcp}.mcp", handler.mcp)
	v1.HandleFunc("/projects", handler.projects)
	v1.Handle("/changes", websocket.Handler(handler.changes))

	r.Handle("/magnacarto/{path:.*}", http.StripPrefix("/magnacarto/", http.FileServer(http.Dir("app"))))

	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/magnacarto/", 302)
	})

	for _, fontDir := range conf.Mapnik.FontDirs {
		mapnik.RegisterFonts(fontDir)
	}
	log.Printf("listening on http://%s", *listenAddr)

	log.Fatal(http.ListenAndServe(*listenAddr, handlers.LoggingHandler(os.Stdout, r)))
}
<commit_msg>return empty JSON if .mcp does not exists but .mml<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/omniscale/magnacarto/mml"

	"code.google.com/p/go.net/websocket"

	"github.com/omniscale/magnacarto"
	"github.com/omniscale/magnacarto/builder"
	mapnikBuilder "github.com/omniscale/magnacarto/builder/mapnik"
	"github.com/omniscale/magnacarto/builder/mapserver"
	"github.com/omniscale/magnacarto/config"
	"github.com/omniscale/magnacarto/maps"
	mssPkg "github.com/omniscale/magnacarto/mss"
	"github.com/omniscale/magnacarto/render"

	"github.com/omniscale/go-mapnik"

	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
)

type magnaserv struct {
	config       *config.Magnacarto
	builderCache *builder.Cache
	mapnikMaker  builder.MapMaker
}

func (s *magnaserv) styleParams(r *http.Request) (mml string, mss []string) {
	baseDir := s.config.StylesDir
	base := r.FormValue("base")
	if base != "" {
		baseDir = filepath.Join(baseDir, base)
	}

	mml = r.FormValue("mml")
	if mml != "" {
		mml = filepath.Join(baseDir, mml)
	}

	mssList := r.FormValue("mss")
	if mssList != "" {
		for _, f := range strings.Split(mssList, ",") {
			mss = append(mss, filepath.Join(baseDir, f))
		}
	}

	return mml, mss
}

func (s *magnaserv) render(w http.ResponseWriter, r *http.Request) {
	mapReq, err := maps.ParseMapRequest(r)
	if err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var maker builder.MapMaker
	renderer := mapReq.Query.Get("RENDERER")
	if renderer == "mapserver" {
		maker = mapserver.Maker
	} else {
		maker = s.mapnikMaker
	}

	styleFile := mapReq.Query.Get("FILE")
	if styleFile == "" {
		mml, mss := s.styleParams(r)
		if mml == "" {
			log.Println("missing mml param in request")
			http.Error(w, "missing mml param", http.StatusBadRequest)
			return
		}

		styleFile, err = s.builderCache.StyleFile(maker, mml, mss)
		if err != nil {
			log.Println(err)
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
	}

	var b []byte
	if renderer == "mapserver" {
		mapReq.Format = mapReq.Query.Get("FORMAT") // use requested format, not internal mapnik format
		b, err = render.MapServer(s.config.MapServer.DevBin, styleFile, renderReq(mapReq))
	} else {
		b, err = render.Mapnik(styleFile, renderReq(mapReq))

	}
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	w.Header().Add("Content-Type", "image/png")
	w.Header().Add("Content-Length", strconv.FormatUint(uint64(len(b)), 10))

	io.Copy(w, bytes.NewBuffer(b))
}

func (s *magnaserv) projects(w http.ResponseWriter, r *http.Request) {
	projects, err := findProjects(s.config.StylesDir)
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	w.Header().Add("Content-Type", "application/json")

	enc := json.NewEncoder(w)
	err = enc.Encode(struct {
		Projects []project `json:"projects"`
	}{Projects: projects})
	if err != nil {
		s.internalError(w, r, err)
		return
	}
}

func (s *magnaserv) mml(w http.ResponseWriter, r *http.Request) {
	baseName := mux.Vars(r)["base"]
	mmlName := mux.Vars(r)["mml"]

	fileName, err := filepath.Abs(filepath.Join(s.config.StylesDir, baseName, mmlName+".mml"))
	if err != nil {
		s.internalError(w, r, err)
		return
	}

	if r.Method == "POST" {
		if err := writeCheckedMML(r.Body, fileName); err != nil {
			s.internalError(w, r, err)
			return
		}
		return
	}
	http.ServeFile(w, r, fileName)
}

func (s *magnaserv) mcp(w http.ResponseWriter, r *http.Request) {
	baseName := mux.Vars(r)["base"]
	mcpName := mux.Vars(r)["mcp"]

	projDir, err := filepath.Abs(filepath.Join(s.config.StylesDir, baseName))
	if err != nil {
		s.internalError(w, r, err)
		return
	}
	mcpFile := filepath.Join(projDir, mcpName+".mcp")

	if r.Method == "POST" {
		if err := writeCheckedJSON(r.Body, mcpFile); err != nil {
			s.internalError(w, r, err)
			return
		}
		return
	}

	// return mcp if exists
	if _, err := os.Stat(mcpFile); err == nil {
		http.ServeFile(w, r, mcpFile)
	} else {
		mmlFile := filepath.Join(projDir, mcpName+".mml")
		// return empty JSON if mml exists
		if _, err := os.Stat(mmlFile); err == nil {
			w.Header().Add("Content-type", "application/json")
			w.Write([]byte("{}\n"))
		} else { // otherwise 404
			http.NotFound(w, r)
		}
	}
}

// writeCheckedMML writes MML from io.ReadCloser to fileName.
// Checks if r is a valid MML before (over)writing file.
func writeCheckedMML(r io.ReadCloser, fileName string) error {
	return writeCheckedFile(r, fileName, func(r io.Reader) error {
		_, err := mml.Parse(r)
		return err
	})
	return nil
}

// writeCheckedMML writes JSON from io.ReadCloser to fileName.
// Checks if r is a valid JSON before (over)writing file.
func writeCheckedJSON(r io.ReadCloser, fileName string) error {
	return writeCheckedFile(r, fileName, func(r io.Reader) error {
		d := json.NewDecoder(r)
		tmp := map[string]interface{}{}
		return d.Decode(&tmp)
	})
	return nil
}

func (s *magnaserv) internalError(w http.ResponseWriter, r *http.Request, err error) {
	log.Print(err)
	w.WriteHeader(http.StatusInternalServerError)
	w.Write([]byte("internal error"))
}

type fileChecker func(io.Reader) error

func writeCheckedFile(r io.ReadCloser, fileName string, checker fileChecker) error {
	tmpFile := fileName + ".tmp-" + strconv.FormatInt(int64(rand.Int31()), 16)
	f, err := os.Create(tmpFile)
	if err != nil {
		return err
	}
	defer r.Close()
	defer os.Remove(tmpFile) // make sure temp file always gets removed

	_, err = io.Copy(f, r)
	f.Close()
	if err != nil {
		return err
	}

	f, err = os.Open(tmpFile)
	if err != nil {
		return err
	}
	defer f.Close()
	if err := checker(f); err != nil {
		return err
	}
	if err := os.Rename(tmpFile, fileName); err != nil {
		return err
	}
	return nil
}

func renderReq(r *maps.Request) render.Request {
	result := render.Request{}
	result.BBOX[0] = r.BBOX.MinX
	result.BBOX[1] = r.BBOX.MinY
	result.BBOX[2] = r.BBOX.MaxX
	result.BBOX[3] = r.BBOX.MaxY
	result.Width = r.Width
	result.Height = r.Height
	result.EPSGCode = r.EPSGCode
	result.Format = r.Format
	return result
}

func (s *magnaserv) changes(ws *websocket.Conn) {
	mml, mss := s.styleParams(ws.Request())
	if mml == "" {
		log.Println("missing mml param in request")
		ws.Close()
		return
	}

	var maker builder.MapMaker

	renderer := ws.Request().Form.Get("renderer")
	if renderer == "mapserver" {
		maker = mapserver.Maker
	} else {
		maker = s.mapnikMaker
	}

	done := make(chan struct{})
	updatec, errc := s.builderCache.Notify(maker, mml, mss, done)

	// read and discard anything from client, signal close on any error
	closeWs := make(chan struct{})
	go func() {
		readbuf := make([]byte, 0, 16)
		for {
			if _, err := ws.Read(readbuf); err != nil {
				if err != io.EOF {
					log.Println("ws read err: ", err)
				}
				closeWs <- struct{}{}
				return
			}
		}
	}()

	var lastMsgSent time.Time
	var lastMsg []byte
	for {
		select {
		case <-closeWs:
			done <- struct{}{}
			ws.Close()
			return
		case update := <-updatec:
			var msg []byte
			var err error
			if update.Err != nil {
				if parseErr, ok := update.Err.(*mssPkg.ParseError); ok {
					msg, err = json.Marshal(struct {
						FullError string `json:"full_error"`
						Error     string `json:"error"`
						Filename  string `json:"filename"`
						Line      int    `json:"line"`
						Column    int    `json:"column"`
					}{parseErr.Error(), parseErr.Err, parseErr.Filename, parseErr.Line, parseErr.Column})
				} else {
					msg, err = json.Marshal(struct {
						Error string `json:"error"`
					}{update.Err.Error()})
				}
			} else {
				msg, err = json.Marshal(struct {
					UpdatedAt  time.Time `json:"updated_at"`
					UpdatedMML bool      `json:"updated_mml"`
				}{update.Time, update.UpdatedMML})

			}
			if err != nil {
				log.Println("error encoding json", err)
				ws.Close()
				return
			}
			// debounce notifications
			if !lastMsgSent.Add(2*time.Second).After(time.Now()) || bytes.Compare(msg, lastMsg) != 0 {
				if _, err := ws.Write(msg); err != nil {
					done <- struct{}{}
					ws.Close()
					return
				}
				lastMsg = msg
				lastMsgSent = time.Now()
			}
		case err := <-errc:
			log.Println("error:", err)
			ws.Close()
			return
		}
	}
}

func main() {
	if os.Getenv("GOMAXPROCS") == "" {
		runtime.GOMAXPROCS(runtime.NumCPU())
	}

	var listenAddr = flag.String("listen", "localhost:7070", "listen address")
	var configFile = flag.String("config", "magnaserv.tml", "config")
	var version = flag.Bool("version", false, "print version and exit")

	flag.Parse()

	if *version {
		fmt.Println(magnacarto.Version)
		os.Exit(0)
	}

	conf, err := config.Load(*configFile)
	if err != nil {
		log.Fatal(err)
	}

	locator := conf.Locator()

	builderCache := builder.NewCache(locator)
	if conf.OutDir != "" {
		if err := os.MkdirAll(conf.OutDir, 0755); err != nil && !os.IsExist(err) {
			log.Fatal("unable to create outdir: ", err)
		}
		builderCache.SetDestination(conf.OutDir)
	}

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	go func() {
		for _ = range c {
			builderCache.ClearAll()
			os.Exit(1)
		}
	}()

	r := mux.NewRouter()
	handler := magnaserv{config: conf, builderCache: builderCache}

	log.Println("using Mapnik", mapnik.Version.String)
	if mapnik.Version.Major == 2 {
		handler.mapnikMaker = mapnikBuilder.Maker2
	} else {
		handler.mapnikMaker = mapnikBuilder.Maker3
	}
	v1 := r.PathPrefix("/api/v1").Subrouter()
	v1.HandleFunc("/map", handler.render)
	v1.HandleFunc("/projects/{base}/{mml}.mml", handler.mml)
	v1.HandleFunc("/projects/{base}/{mcp}.mcp", handler.mcp)
	v1.HandleFunc("/projects", handler.projects)
	v1.Handle("/changes", websocket.Handler(handler.changes))

	r.Handle("/magnacarto/{path:.*}", http.StripPrefix("/magnacarto/", http.FileServer(http.Dir("app"))))

	r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		http.Redirect(w, r, "/magnacarto/", 302)
	})

	for _, fontDir := range conf.Mapnik.FontDirs {
		mapnik.RegisterFonts(fontDir)
	}
	log.Printf("listening on http://%s", *listenAddr)

	log.Fatal(http.ListenAndServe(*listenAddr, handlers.LoggingHandler(os.Stdout, r)))
}
<|endoftext|>


<commit_before>package proxy

import (
	"errors"
	"math/rand"
	"sync"
)

var (
	// ErrEmptyBackendList is used when the list of beckends is empty
	ErrEmptyBackendList = errors.New("can not elect backend, Backends empty")
	// ErrZeroWeight is used when there a zero value weight was given
	ErrZeroWeight = errors.New("invalid backend, weight 0 given")
	// ErrCannotElectBackend is used a backend cannot be elected
	ErrCannotElectBackend = errors.New("cant elect backend")
)

type (
	// Balancer holds the load balancer methods for many different algorithms
	Balancer interface {
		Elect(hosts []*Target) (*Target, error)
	}

	// RoundrobinBalancer balancer
	RoundrobinBalancer struct {
		current int // current backend position
		mu      sync.RWMutex
	}

	// WeightBalancer balancer
	WeightBalancer struct{}
)

// NewRoundrobinBalancer creates a new instance of Roundrobin
func NewRoundrobinBalancer() *RoundrobinBalancer {
	return &RoundrobinBalancer{}
}

// Elect backend using roundrobin strategy
func (b *RoundrobinBalancer) Elect(hosts []*Target) (*Target, error) {
	if len(hosts) == 0 {
		return nil, ErrEmptyBackendList
	}

	if b.current >= len(hosts) {
		b.current = 0
	}

	host := hosts[b.current]

	b.mu.Lock()
	defer b.mu.Unlock()
	b.current++

	return host, nil
}

// NewWeightBalancer creates a new instance of Roundrobin
func NewWeightBalancer() *WeightBalancer {
	return &WeightBalancer{}
}

// Elect backend using roundrobin strategy
func (b *WeightBalancer) Elect(hosts []*Target) (*Target, error) {
	if len(hosts) == 0 {
		return nil, ErrEmptyBackendList
	}

	totalWeight := 0
	for _, host := range hosts {
		if host.Weight <= 0 {
			return nil, ErrZeroWeight
		}
		totalWeight += host.Weight
	}

	r := rand.Intn(totalWeight)
	pos := 0

	for _, host := range hosts {
		pos += host.Weight
		if r >= pos {
			continue
		}
		return host, nil
	}

	return nil, ErrCannotElectBackend
}
<commit_msg>Support for hosts with 0 weight<commit_after>package proxy

import (
	"errors"
	"math/rand"
	"sync"
)

var (
	// ErrEmptyBackendList is used when the list of beckends is empty
	ErrEmptyBackendList = errors.New("can not elect backend, Backends empty")
	// ErrZeroWeight is used when there a zero value weight was given
	ErrZeroWeight = errors.New("invalid backend, weight 0 given")
	// ErrCannotElectBackend is used a backend cannot be elected
	ErrCannotElectBackend = errors.New("cant elect backend")
)

type (
	// Balancer holds the load balancer methods for many different algorithms
	Balancer interface {
		Elect(hosts []*Target) (*Target, error)
	}

	// RoundrobinBalancer balancer
	RoundrobinBalancer struct {
		current int // current backend position
		mu      sync.RWMutex
	}

	// WeightBalancer balancer
	WeightBalancer struct{}
)

// NewRoundrobinBalancer creates a new instance of Roundrobin
func NewRoundrobinBalancer() *RoundrobinBalancer {
	return &RoundrobinBalancer{}
}

// Elect backend using roundrobin strategy
func (b *RoundrobinBalancer) Elect(hosts []*Target) (*Target, error) {
	if len(hosts) == 0 {
		return nil, ErrEmptyBackendList
	}

	if b.current >= len(hosts) {
		b.current = 0
	}

	host := hosts[b.current]

	b.mu.Lock()
	defer b.mu.Unlock()
	b.current++

	return host, nil
}

// NewWeightBalancer creates a new instance of Roundrobin
func NewWeightBalancer() *WeightBalancer {
	return &WeightBalancer{}
}

// Elect backend using roundrobin strategy
func (b *WeightBalancer) Elect(hosts []*Target) (*Target, error) {
	if len(hosts) == 0 {
		return nil, ErrEmptyBackendList
	}

	totalWeight := 0
	for _, host := range hosts {
		totalWeight += host.Weight
	}

	if totalWeight <= 0 {
		return nil, ErrZeroWeight
	}

	r := rand.Intn(totalWeight)
	pos := 0

	for _, host := range hosts {
		pos += host.Weight
		if r >= pos {
			continue
		}
		return host, nil
	}

	return nil, ErrCannotElectBackend
}
<|endoftext|>


<commit_before>// Package git contains various commands that shell out to git
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package git

import (
	"fmt"
	"io"
	"strings"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/rubyist/tracerx"
)

// FilterProcessScanner provides a scanner-like interface capable of
// initializing the filter process with the Git parent, and scanning for
// requests across the protocol.
//
// Reading a request (and errors) is as follows:
//
//     s := NewFilterProcessScanner(os.Stdin, os.Stderr)
//     for s.Scan() {
//             req := s.Request()
//     	       // ...
//     }
//
//     if err := s.Err(); err != nil {
//             // ...
//     }
type FilterProcessScanner struct {
	// pl is the *pktline instance used to read and write packets back and
	// forth between Git.
	pl *pktline

	// req is a temporary variable used to hold the value accessible by the
	// `Request()` function. It is cleared at the beginning of each `Scan()`
	// invocation, and written to at the end of each `Scan()` invocation.
	req *Request
	// err is a temporary variable used to hold the value accessible by the
	// `Request()` function. It is cleared at the beginning of each `Scan()`
	// invocation, and written to at the end of each `Scan()` invocation.
	err error
}

// NewFilterProcessScanner constructs a new instance of the
// `*FilterProcessScanner` type which reads packets from the `io.Reader` "r",
// and writes packets to the `io.Writer`, "w".
//
// Both reader and writers SHOULD NOT be `*git.PacketReader` or
// `*git.PacketWriter`s, they will be transparently treated as such. In other
// words, it is safe (and recommended) to pass `os.Stdin` and `os.Stdout`
// directly.
func NewFilterProcessScanner(r io.Reader, w io.Writer) *FilterProcessScanner {
	return &FilterProcessScanner{
		pl: newPktline(r, w),
	}
}

// Init initializes the filter and ACKs back and forth between the Git LFS
// subprocess and the Git parent process that each is a git-filter-server and
// client respectively.
//
// If either side wrote an invalid sequence of data, or did not meet
// expectations, an error will be returned. If the filter type is not supported,
// an error will be returned. If the pkt-line welcome message was invalid, an
// error will be returned.
//
// If there was an error reading or writing any of the packets below, an error
// will be returned.
func (o *FilterProcessScanner) Init() error {
	tracerx.Printf("Initialize filter-process")
	reqVer := "version=2"

	initMsg, err := o.pl.readPacketText()
	if err != nil {
		return errors.Wrap(err, "reading filter-process initialization")
	}
	if initMsg != "git-filter-client" {
		return fmt.Errorf("invalid filter-process pkt-line welcome message: %s", initMsg)
	}

	supVers, err := o.pl.readPacketList()
	if err != nil {
		return errors.Wrap(err, "reading filter-process versions")
	}
	if !isStringInSlice(supVers, reqVer) {
		return fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqVer, supVers)
	}

	err = o.pl.writePacketList([]string{"git-filter-server", reqVer})
	if err != nil {
		return errors.Wrap(err, "writing filter-process initialization failed")
	}
	return nil
}

// NegotiateCapabilities executes the process of negotiating capabilities
// between the filter client and server. If we don't support any of the
// capabilities given to LFS by the parent, an error will be returned. If there
// was an error reading or writing capabilities between the two, an error will
// be returned.
func (o *FilterProcessScanner) NegotiateCapabilities() ([]string, error) {
	reqCaps := []string{"capability=clean", "capability=smudge"}

	supCaps, err := o.pl.readPacketList()
	if err != nil {
		return nil, fmt.Errorf("reading filter-process capabilities failed with %s", err)
	}
	for _, reqCap := range reqCaps {
		if !isStringInSlice(supCaps, reqCap) {
			return nil, fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqCap, supCaps)
		}
	}

	err = o.pl.writePacketList(reqCaps)
	if err != nil {
		return nil, fmt.Errorf("writing filter-process capabilities failed with %s", err)
	}

	return supCaps, nil
}

// Request represents a single command sent to LFS from the parent Git process.
type Request struct {
	// Header maps header strings to values, and is encoded as the first
	// part of the packet.
	Header map[string]string
	// Payload represents the body of the packet, and contains the contents
	// of the file in the index.
	Payload io.Reader
}

// Scan scans for the next request, or error and returns whether or not the scan
// was successful, indicating the presence of a valid request. If the Scan
// failed, there was either an error reading the next request (and the results
// of calling `Err()` should be inspected), or the pipe was closed and no more
// requests are present.
//
// Closing the pipe is Git's way to communicate that no more files will be
// filtered. Git expects that the LFS process exits after this event.
func (o *FilterProcessScanner) Scan() bool {
	o.req, o.err = nil, nil

	req, err := o.readRequest()
	if err != nil {
		o.err = err
		return false
	}

	o.req = req
	return true
}

// Request returns the request read from a call to Scan(). It is available only
// after a call to `Scan()` has completed, and is re-initialized to nil at the
// beginning of the subsequent `Scan()` call.
func (o *FilterProcessScanner) Request() *Request { return o.req }

// Err returns any error encountered from the last call to Scan(). It is available only
// after a call to `Scan()` has completed, and is re-initialized to nil at the
// beginning of the subsequent `Scan()` call.
func (o *FilterProcessScanner) Err() error { return o.err }

// readRequest reads the headers of a request and yields an `io.Reader` which
// will read the body of the request. Since the body is _not_ offset, one
// request should be read in its entirety before consuming the next request.
func (o *FilterProcessScanner) readRequest() (*Request, error) {
	tracerx.Printf("Read filter-process request.")

	requestList, err := o.pl.readPacketList()
	if err != nil {
		return nil, err
	}

	req := &Request{
		Header:  make(map[string]string),
		Payload: &pktlineReader{pl: o.pl},
	}

	for _, pair := range requestList {
		v := strings.SplitN(pair, "=", 2)
		req.Header[v[0]] = v[1]
	}

	return req, nil
}

func (o *FilterProcessScanner) WriteStatus(status string) error {
	return o.pl.writePacketList([]string{"status=" + status})
}

// isStringInSlice returns whether a given string "what" is contained in a
// slice, "s".
//
// isStringInSlice is copied from "github.com/xeipuuv/gojsonschema/utils.go"
func isStringInSlice(s []string, what string) bool {
	for i := range s {
		if s[i] == what {
			return true
		}
	}
	return false
}
<commit_msg>git: remove 'parent' terminology from NegotiateCapabilities()<commit_after>// Package git contains various commands that shell out to git
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package git

import (
	"fmt"
	"io"
	"strings"

	"github.com/git-lfs/git-lfs/errors"
	"github.com/rubyist/tracerx"
)

// FilterProcessScanner provides a scanner-like interface capable of
// initializing the filter process with the Git parent, and scanning for
// requests across the protocol.
//
// Reading a request (and errors) is as follows:
//
//     s := NewFilterProcessScanner(os.Stdin, os.Stderr)
//     for s.Scan() {
//             req := s.Request()
//     	       // ...
//     }
//
//     if err := s.Err(); err != nil {
//             // ...
//     }
type FilterProcessScanner struct {
	// pl is the *pktline instance used to read and write packets back and
	// forth between Git.
	pl *pktline

	// req is a temporary variable used to hold the value accessible by the
	// `Request()` function. It is cleared at the beginning of each `Scan()`
	// invocation, and written to at the end of each `Scan()` invocation.
	req *Request
	// err is a temporary variable used to hold the value accessible by the
	// `Request()` function. It is cleared at the beginning of each `Scan()`
	// invocation, and written to at the end of each `Scan()` invocation.
	err error
}

// NewFilterProcessScanner constructs a new instance of the
// `*FilterProcessScanner` type which reads packets from the `io.Reader` "r",
// and writes packets to the `io.Writer`, "w".
//
// Both reader and writers SHOULD NOT be `*git.PacketReader` or
// `*git.PacketWriter`s, they will be transparently treated as such. In other
// words, it is safe (and recommended) to pass `os.Stdin` and `os.Stdout`
// directly.
func NewFilterProcessScanner(r io.Reader, w io.Writer) *FilterProcessScanner {
	return &FilterProcessScanner{
		pl: newPktline(r, w),
	}
}

// Init initializes the filter and ACKs back and forth between the Git LFS
// subprocess and the Git parent process that each is a git-filter-server and
// client respectively.
//
// If either side wrote an invalid sequence of data, or did not meet
// expectations, an error will be returned. If the filter type is not supported,
// an error will be returned. If the pkt-line welcome message was invalid, an
// error will be returned.
//
// If there was an error reading or writing any of the packets below, an error
// will be returned.
func (o *FilterProcessScanner) Init() error {
	tracerx.Printf("Initialize filter-process")
	reqVer := "version=2"

	initMsg, err := o.pl.readPacketText()
	if err != nil {
		return errors.Wrap(err, "reading filter-process initialization")
	}
	if initMsg != "git-filter-client" {
		return fmt.Errorf("invalid filter-process pkt-line welcome message: %s", initMsg)
	}

	supVers, err := o.pl.readPacketList()
	if err != nil {
		return errors.Wrap(err, "reading filter-process versions")
	}
	if !isStringInSlice(supVers, reqVer) {
		return fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqVer, supVers)
	}

	err = o.pl.writePacketList([]string{"git-filter-server", reqVer})
	if err != nil {
		return errors.Wrap(err, "writing filter-process initialization failed")
	}
	return nil
}

// NegotiateCapabilities executes the process of negotiating capabilities
// between the filter client and server. If we don't support any of the
// capabilities given to LFS by Git, an error will be returned. If there was an
// error reading or writing capabilities between the two, an error will be
// returned.
func (o *FilterProcessScanner) NegotiateCapabilities() ([]string, error) {
	reqCaps := []string{"capability=clean", "capability=smudge"}

	supCaps, err := o.pl.readPacketList()
	if err != nil {
		return nil, fmt.Errorf("reading filter-process capabilities failed with %s", err)
	}
	for _, reqCap := range reqCaps {
		if !isStringInSlice(supCaps, reqCap) {
			return nil, fmt.Errorf("filter '%s' not supported (your Git supports: %s)", reqCap, supCaps)
		}
	}

	err = o.pl.writePacketList(reqCaps)
	if err != nil {
		return nil, fmt.Errorf("writing filter-process capabilities failed with %s", err)
	}

	return supCaps, nil
}

// Request represents a single command sent to LFS from the parent Git process.
type Request struct {
	// Header maps header strings to values, and is encoded as the first
	// part of the packet.
	Header map[string]string
	// Payload represents the body of the packet, and contains the contents
	// of the file in the index.
	Payload io.Reader
}

// Scan scans for the next request, or error and returns whether or not the scan
// was successful, indicating the presence of a valid request. If the Scan
// failed, there was either an error reading the next request (and the results
// of calling `Err()` should be inspected), or the pipe was closed and no more
// requests are present.
//
// Closing the pipe is Git's way to communicate that no more files will be
// filtered. Git expects that the LFS process exits after this event.
func (o *FilterProcessScanner) Scan() bool {
	o.req, o.err = nil, nil

	req, err := o.readRequest()
	if err != nil {
		o.err = err
		return false
	}

	o.req = req
	return true
}

// Request returns the request read from a call to Scan(). It is available only
// after a call to `Scan()` has completed, and is re-initialized to nil at the
// beginning of the subsequent `Scan()` call.
func (o *FilterProcessScanner) Request() *Request { return o.req }

// Err returns any error encountered from the last call to Scan(). It is available only
// after a call to `Scan()` has completed, and is re-initialized to nil at the
// beginning of the subsequent `Scan()` call.
func (o *FilterProcessScanner) Err() error { return o.err }

// readRequest reads the headers of a request and yields an `io.Reader` which
// will read the body of the request. Since the body is _not_ offset, one
// request should be read in its entirety before consuming the next request.
func (o *FilterProcessScanner) readRequest() (*Request, error) {
	tracerx.Printf("Read filter-process request.")

	requestList, err := o.pl.readPacketList()
	if err != nil {
		return nil, err
	}

	req := &Request{
		Header:  make(map[string]string),
		Payload: &pktlineReader{pl: o.pl},
	}

	for _, pair := range requestList {
		v := strings.SplitN(pair, "=", 2)
		req.Header[v[0]] = v[1]
	}

	return req, nil
}

func (o *FilterProcessScanner) WriteStatus(status string) error {
	return o.pl.writePacketList([]string{"status=" + status})
}

// isStringInSlice returns whether a given string "what" is contained in a
// slice, "s".
//
// isStringInSlice is copied from "github.com/xeipuuv/gojsonschema/utils.go"
func isStringInSlice(s []string, what string) bool {
	for i := range s {
		if s[i] == what {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"os"
	"regexp"
	"time"

	"github.com/grafana/metrictank/conf"
	"github.com/grafana/metrictank/idx/cassandra"
	"github.com/grafana/metrictank/idx/memory"
	"github.com/grafana/metrictank/logger"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

func init() {
	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
	log.SetLevel(log.InfoLevel)
}

func perror(err error) {
	if err != nil {
		log.Fatal(err.Error())
	}
}

type counters struct {
	total      int
	active     int
	deprecated int
}

func (c *counters) PrintCounters() {
	fmt.Println(fmt.Sprintf("Total analyzed defs: %d", c.total))
	fmt.Println(fmt.Sprintf("Active defs:         %d", c.active))
	fmt.Println(fmt.Sprintf("Deprecated defs:     %d", c.deprecated))
}

func main() {
	var noDryRun, verbose bool
	var partitionFrom, partitionTo int
	var indexRulesFile string
	globalFlags := flag.NewFlagSet("global config flags", flag.ExitOnError)
	globalFlags.BoolVar(&noDryRun, "no-dry-run", false, "do not only plan and print what to do, but also execute it")
	globalFlags.BoolVar(&verbose, "verbose", false, "print every metric name that gets archived")
	globalFlags.IntVar(&partitionFrom, "partition-from", 0, "the partition to start at")
	globalFlags.IntVar(&partitionTo, "partition-to", -1, "prune all partitions up to this one (exclusive). If unset, only the partition defined with \"--partition-from\" gets pruned")
	globalFlags.StringVar(&indexRulesFile, "index-rules-file", "/etc/metrictank/index-rules.conf", "name of file which defines the max-stale times")
	cassFlags := cassandra.ConfigSetup()

	flag.Usage = func() {
		fmt.Println("mt-index-prune")
		fmt.Println()
		fmt.Println("Retrieves a metrictank index and moves all deprecated entries into an archive table")
		fmt.Println()
		fmt.Printf("Usage:\n\n")
		fmt.Printf("  mt-index-prune [global config flags] <idxtype> [idx config flags]\n\n")
		fmt.Printf("global config flags:\n\n")
		globalFlags.PrintDefaults()
		fmt.Println()
		fmt.Printf("idxtype: only 'cass' supported for now\n\n")
		fmt.Printf("cass config flags:\n\n")
		cassFlags.PrintDefaults()
		fmt.Println()
		fmt.Println()
		fmt.Println("EXAMPLES:")
		fmt.Println("mt-index-prune --verbose --partition-count 128 cass -hosts cassandra:9042")
	}

	if len(os.Args) == 2 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
		flag.Usage()
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		flag.Usage()
		os.Exit(-1)
	}

	var cassI int
	for i, v := range os.Args {
		if v == "cass" {
			cassI = i
		}
	}

	if cassI == 0 {
		log.Println("only indextype 'cass' supported")
		flag.Usage()
		os.Exit(1)
	}
	globalFlags.Parse(os.Args[1:cassI])

	indexRules, err := conf.ReadIndexRules(indexRulesFile)
	if os.IsNotExist(err) {
		log.Fatalf("Index-rules.conf file %s does not exist; exiting", indexRulesFile)
		os.Exit(1)
	}
	now := time.Now()
	cutoffs := indexRules.Cutoffs(now)

	cassFlags.Parse(os.Args[cassI+1:])
	cassandra.CliConfig.Enabled = true
	cassIdx := cassandra.New(cassandra.CliConfig)
	err = cassIdx.InitBare()
	perror(err)

	// we don't want to filter any metric definitions during the loading
	// so MaxStale is set to 0
	memory.IndexRules = conf.IndexRules{
		Rules: nil,
		Default: conf.IndexRule{
			Name:     "default",
			Pattern:  regexp.MustCompile(""),
			MaxStale: 0,
		},
	}

	defCounters := counters{}
	defs := make([]schema.MetricDefinition, 0)
	deprecatedDefs := make([]schema.MetricDefinition, 0)
	for partition := partitionFrom; (partitionTo == -1 && partition == partitionFrom) || (partitionTo > 0 && partition < partitionTo); partition++ {
		defsByNameWithTags := make(map[string][]schema.MetricDefinition)
		defs = cassIdx.LoadPartitions([]int32{int32(partition)}, defs, now)
		defCounters.total += len(defs)
		for _, def := range defs {
			name := def.NameWithTags()
			defsByNameWithTags[name] = append(defsByNameWithTags[name], def)
		}

		for name, defs := range defsByNameWithTags {
			// find the latest LastUpdate ts
			latest := int64(0)
			for _, def := range defs {
				if def.LastUpdate > latest {
					latest = def.LastUpdate
				}
			}

			irId, _ := indexRules.Match(name)
			if latest < cutoffs[irId] {
				for _, def := range defs {
					deprecatedDefs = append(deprecatedDefs, def)
				}
				defCounters.deprecated += len(defs)

				if verbose {
					fmt.Println(fmt.Sprintf("Metric is deprecated: %s", name))
				}
			} else {
				defCounters.active += len(defs)

				if verbose {
					fmt.Println(fmt.Sprintf("Metric is active: %s", name))
				}
			}
		}

		if noDryRun {
			err = cassIdx.ArchiveDefs(deprecatedDefs)
			if err != nil {
				perror(fmt.Errorf("Failed to archive defs: %s", err.Error()))
			}
		}

		defs = defs[:0]
		deprecatedDefs = deprecatedDefs[:0]
	}

	defCounters.PrintCounters()
}
<commit_msg>change fatal error to warning<commit_after>package main

import (
	"flag"
	"fmt"
	"os"
	"regexp"
	"time"

	"github.com/grafana/metrictank/conf"
	"github.com/grafana/metrictank/idx/cassandra"
	"github.com/grafana/metrictank/idx/memory"
	"github.com/grafana/metrictank/logger"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

func init() {
	formatter := &logger.TextFormatter{}
	formatter.TimestampFormat = "2006-01-02 15:04:05.000"
	log.SetFormatter(formatter)
	log.SetLevel(log.InfoLevel)
}

func perror(err error) {
	if err != nil {
		log.Fatal(err.Error())
	}
}

type counters struct {
	total      int
	active     int
	deprecated int
}

func (c *counters) PrintCounters() {
	fmt.Println(fmt.Sprintf("Total analyzed defs: %d", c.total))
	fmt.Println(fmt.Sprintf("Active defs:         %d", c.active))
	fmt.Println(fmt.Sprintf("Deprecated defs:     %d", c.deprecated))
}

func main() {
	var noDryRun, verbose bool
	var partitionFrom, partitionTo int
	var indexRulesFile string
	globalFlags := flag.NewFlagSet("global config flags", flag.ExitOnError)
	globalFlags.BoolVar(&noDryRun, "no-dry-run", false, "do not only plan and print what to do, but also execute it")
	globalFlags.BoolVar(&verbose, "verbose", false, "print every metric name that gets archived")
	globalFlags.IntVar(&partitionFrom, "partition-from", 0, "the partition to start at")
	globalFlags.IntVar(&partitionTo, "partition-to", -1, "prune all partitions up to this one (exclusive). If unset, only the partition defined with \"--partition-from\" gets pruned")
	globalFlags.StringVar(&indexRulesFile, "index-rules-file", "/etc/metrictank/index-rules.conf", "name of file which defines the max-stale times")
	cassFlags := cassandra.ConfigSetup()

	flag.Usage = func() {
		fmt.Println("mt-index-prune")
		fmt.Println()
		fmt.Println("Retrieves a metrictank index and moves all deprecated entries into an archive table")
		fmt.Println()
		fmt.Printf("Usage:\n\n")
		fmt.Printf("  mt-index-prune [global config flags] <idxtype> [idx config flags]\n\n")
		fmt.Printf("global config flags:\n\n")
		globalFlags.PrintDefaults()
		fmt.Println()
		fmt.Printf("idxtype: only 'cass' supported for now\n\n")
		fmt.Printf("cass config flags:\n\n")
		cassFlags.PrintDefaults()
		fmt.Println()
		fmt.Println()
		fmt.Println("EXAMPLES:")
		fmt.Println("mt-index-prune --verbose --partition-count 128 cass -hosts cassandra:9042")
	}

	if len(os.Args) == 2 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
		flag.Usage()
		os.Exit(0)
	}

	if len(os.Args) < 2 {
		flag.Usage()
		os.Exit(-1)
	}

	var cassI int
	for i, v := range os.Args {
		if v == "cass" {
			cassI = i
		}
	}

	if cassI == 0 {
		log.Println("only indextype 'cass' supported")
		flag.Usage()
		os.Exit(1)
	}
	globalFlags.Parse(os.Args[1:cassI])

	indexRules, err := conf.ReadIndexRules(indexRulesFile)
	if os.IsNotExist(err) {
		log.Fatalf("Index-rules.conf file %s does not exist; exiting", indexRulesFile)
		os.Exit(1)
	}
	now := time.Now()
	cutoffs := indexRules.Cutoffs(now)

	cassFlags.Parse(os.Args[cassI+1:])
	cassandra.CliConfig.Enabled = true
	cassIdx := cassandra.New(cassandra.CliConfig)
	err = cassIdx.InitBare()
	perror(err)

	// we don't want to filter any metric definitions during the loading
	// so MaxStale is set to 0
	memory.IndexRules = conf.IndexRules{
		Rules: nil,
		Default: conf.IndexRule{
			Name:     "default",
			Pattern:  regexp.MustCompile(""),
			MaxStale: 0,
		},
	}

	defCounters := counters{}
	defs := make([]schema.MetricDefinition, 0)
	deprecatedDefs := make([]schema.MetricDefinition, 0)
	for partition := partitionFrom; (partitionTo == -1 && partition == partitionFrom) || (partitionTo > 0 && partition < partitionTo); partition++ {
		defsByNameWithTags := make(map[string][]schema.MetricDefinition)
		defs = cassIdx.LoadPartitions([]int32{int32(partition)}, defs, now)
		defCounters.total += len(defs)
		for _, def := range defs {
			name := def.NameWithTags()
			defsByNameWithTags[name] = append(defsByNameWithTags[name], def)
		}

		for name, defs := range defsByNameWithTags {
			// find the latest LastUpdate ts
			latest := int64(0)
			for _, def := range defs {
				if def.LastUpdate > latest {
					latest = def.LastUpdate
				}
			}

			irId, _ := indexRules.Match(name)
			if latest < cutoffs[irId] {
				for _, def := range defs {
					deprecatedDefs = append(deprecatedDefs, def)
				}
				defCounters.deprecated += len(defs)

				if verbose {
					fmt.Println(fmt.Sprintf("Metric is deprecated: %s", name))
				}
			} else {
				defCounters.active += len(defs)

				if verbose {
					fmt.Println(fmt.Sprintf("Metric is active: %s", name))
				}
			}
		}

		if noDryRun {
			err = cassIdx.ArchiveDefs(deprecatedDefs)
			if err != nil {
				log.Warnf("Failed to archive defs: %s", err.Error())
			}
		}

		defs = defs[:0]
		deprecatedDefs = deprecatedDefs[:0]
	}

	defCounters.PrintCounters()
}
<|endoftext|>


<commit_before>package rake
/*
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import (
	"regexp"
	"strconv"
	"strings"
	"sort"
	"io/ioutil"
	"fmt"
	"os"
)

var sentenceDelimiters map[string]bool = map[string]bool {
	"[": true, "]": true, "\n": true, ".": true, "!": true, "?": true, ",": true, ";": true,
	":": true, "\t": true, "-": true, "\"": true, "(": true, ")": true, "'": true, "\u2019": true,
	"\u2013": true,
}
var wordSep *regexp.Regexp = regexp.MustCompile("\\s+")
var wordSplitter *regexp.Regexp = regexp.MustCompile("[^a-zA-Z0-9_\\+\\-/]")

func SplitSentences(text string) []string {
	indices := []int{0}
	for i := 0; i < len(text); i++ {
		s := text[i:i + 1]
		_, match := sentenceDelimiters[s]
		if match {
			indices = append(indices, i)
		}
	}
	indices = append(indices, len(text))
	result := make([]string, 0, len(indices) + 1)
	for i, index := range indices {
		j := i + 1
		if j < len(indices) {
			index2 := indices[j]
			if index2 - index > 1 {
				result = append(result, text[index + 1:index2])
			}
		}
	}
	return result
	//return sentenceDelimiters.Split(text, -1)
}

func IsAcceptable(phrase string, minCharLength int, maxWordsLength int) bool {
	// a phrase must have a min length in characters
	if len(phrase) < minCharLength {
		return false
	}

	// a phrase must have a max number of words
	words := wordSep.Split(phrase, -1)
	if len(words) > maxWordsLength {
		return false
	}
	digits := 0
	alpha := 0
	for _, c := range phrase {
		if c > '0' && c < '9' {
			digits++
		} else if (c > 'a' && c < 'z') || (c > 'A' && c < 'Z') {
			alpha++
		}
	}
	// a phrase must have at least one alpha character
	if alpha == 0 {
		return false
	}
	// a phrase must have more alpha than digits characters
	if digits > alpha {
		return false
	}
	return true
}

func findStopwordIndices(words []string, stopwords map[string]bool) []int {
	result := []int{}
	for i, word := range words {
		if stopwords[word] {
			result = append(result, i)
		}
	}
	return result
}

func GenerateCandidateKeywords(sentenceList []string, stopwords map[string]bool, minCharLength int, maxWordsLength int) []string {
	phraseList := []string{}
	for _, s := range sentenceList {
		words := strings.Split(s, " ")
		stopwordIndices := findStopwordIndices(words, stopwords)

		if len(stopwordIndices) > 0 {
			if stopwordIndices[0] != 0 {
				phraseWords := words[0:stopwordIndices[0]]
				phrase := strings.Join(phraseWords, " ")
				if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
					phraseList = append(phraseList, phrase)
				}
			}
			for i, index := range stopwordIndices {
				j := i + 1
				if j < len(stopwordIndices) {
					index2 := stopwordIndices[j]
					if index2 - index == 1 {
						continue
					}
					phraseWords := words[index + 1:index2]
					phrase := strings.Join(phraseWords, " ")
					if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
						phraseList = append(phraseList, phrase)
					}
				}
			}
			if stopwordIndices[len(stopwordIndices) - 1] != len(words) - 1 {
				index := stopwordIndices[len(stopwordIndices) - 1]
				phraseWords := words[index + 1:]
				phrase := strings.Join(phraseWords, " ")
				if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
					phraseList = append(phraseList, phrase)
				}
			}
		} else {
			phrase := strings.Join(words, " ")
			if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
				phraseList = append(phraseList, phrase)
			}
		}
	}
	return phraseList
}

func IsNumber(s string) bool {
	if strings.Index(s, ".") != -1 {
		_, err := strconv.ParseFloat(s, 32)
		return err == nil
	} else {
		_, err := strconv.ParseInt(s, 10, 32)
		return err == nil
	}
}

func SeparateWords(text string, minWordReturnSize int) []string {
	//    Utility function to return a list of all words that are have a length
	//    greater than a specified number of characters.
	//    @param text The text that must be split in to words.
	//    @param min_word_return_size The minimum no of characters
	//           a word must have to be included.
	words := []string{}
	for _, singleWord := range wordSplitter.Split(text, -1) {
		currentWord := strings.ToLower(strings.TrimSpace(singleWord))
		// leave numbers in phrase, but don't count as words, since they tend to invalidate scores of their phrases
		if len(currentWord) > minWordReturnSize && currentWord != "" && !IsNumber(currentWord) {
			words = append(words, currentWord)
		}
	}
	return words
}

func CalculateWordScores(phraseList []string) map[string]float64 {
	wordFrequency := map[string]int{}
	wordDegree := map[string]int{}
	for _, phrase := range phraseList {
		wordList := SeparateWords(phrase, 0)
		wordListLength := len(wordList)
		wordListDegree := wordListLength - 1
		// if word_list_degree > 3: word_list_degree = 3 #exp.
		for _, word := range wordList {
			wordFrequency[word]++
			wordDegree[word] += wordListDegree
		}
	}
	for item := range wordFrequency {
		wordDegree[item] = wordDegree[item] + wordFrequency[item]
	}
	// Calculate Word scores = deg(w)/frew(w)
	wordScore := map[string]float64{}
	for item := range wordFrequency {
		wordScore[item] = float64(wordDegree[item]) / float64(wordFrequency[item])
	}
	return wordScore
}

func StringCount(stringList []string) map[string]int {
	stringCount := map[string]int{}
	for _, item := range stringList {
		stringCount[item]++
	}
	return stringCount
}

func GenerateCandidateKeywordScores(phraseList []string, wordScore map[string]float64, minKeywordFrequency int) (map[string]float64, map[string]int) {
	keywordCandidates := map[string]float64{}
	phraseCounts := StringCount(phraseList)
	for _, phrase := range phraseList {
		if minKeywordFrequency > 1 {
			if phraseCounts[phrase] < minKeywordFrequency {
				continue
			}
		}
		wordList := SeparateWords(phrase, 0)
		var candiateScore float64 = 0
		for _, word := range wordList {
			candiateScore += wordScore[word]
		}
		keywordCandidates[phrase] = candiateScore
	}

	return keywordCandidates, phraseCounts
}

func MapToKeywordScores(keywordScoreMap map[string]float64, keywordCountMap map[string]int) []KeywordScore {
	keywordScores := []KeywordScore{}
	for key, value := range keywordScoreMap {
		keywordScore := KeywordScore{
			Keyword: key,
			Score: value,
		}
		count, _ := keywordCountMap[key]
		keywordScore.Count = count
		keywordScores = append(keywordScores, keywordScore)
	}
	return keywordScores
}

func loadStopWords(stopWordFile string) []string {
	stopWords := []string{}
	contents, err := ioutil.ReadFile(stopWordFile)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	lines := strings.Split(string(contents), "\n")
	for _, line := range(lines) {
		line = strings.ToLower(strings.TrimSpace(line))
		if strings.Index(line, "#") != 0 {
			// in case more than one per line
			for _, word := range strings.Split(line, " ") {
				stopWords = append(stopWords, word)
			}

		}
	}
	return stopWords
}

func BuildStopWordMap(stopWordFilePath string) map[string]bool {
	stopWordList := loadStopWords(stopWordFilePath)
	stopWordMap := make(map[string]bool, len(stopWordList))
	for _, word := range stopWordList {
		stopWordMap[word] = true
	}
	return stopWordMap
}

// ByScore implements sort.Interface for []KeywordScore based on
// the Score field. Sorts in reverse order.
type ByScore []KeywordScore

func (a ByScore) Len() int           { return len(a) }
func (a ByScore) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByScore) Less(i, j int) bool { return a[i].Score > a[j].Score }

type Rake struct {
	stopWordsPath       string
	stopWords           map[string]bool
	minCharLength       int
	maxWordsLength      int
	minKeywordFrequency int
}

type KeywordScore struct {
	Keyword string
	Score   float64
	Count   int
}

func NewRake(stopWordsPath string, minCharLength int, maxWordsLength int, minKeywordFrequency int) *Rake {
	return &Rake{
		stopWordsPath: stopWordsPath,
		stopWords: BuildStopWordMap(stopWordsPath),
		minCharLength: minCharLength,
		maxWordsLength: maxWordsLength,
		minKeywordFrequency: minKeywordFrequency,
	}
}

func (rake *Rake) Run(text string) []KeywordScore {
	sentenceList := SplitSentences(text)
	phraseList := GenerateCandidateKeywords(sentenceList, rake.stopWords, rake.minCharLength, rake.maxWordsLength)
	wordScores := CalculateWordScores(phraseList)
	keywordCandidates, keywordCounts := GenerateCandidateKeywordScores(phraseList, wordScores, rake.minKeywordFrequency)
	keywordScores := MapToKeywordScores(keywordCandidates, keywordCounts)
	sort.Sort(ByScore(keywordScores))
	return keywordScores
}
<commit_msg>Support unicode letters<commit_after>package rake

/*
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

import (
	"fmt"
	"io/ioutil"
	"os"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"unicode"
)

var sentenceDelimiters map[string]bool = map[string]bool{
	"[": true, "]": true, "\n": true, ".": true, "!": true, "?": true, ",": true, ";": true,
	":": true, "\t": true, "-": true, "\"": true, "(": true, ")": true, "'": true, "\u2019": true,
	"\u2013": true,
}
var wordSep *regexp.Regexp = regexp.MustCompile("\\s+")
var wordSplitter *regexp.Regexp = regexp.MustCompile("[^\\p{L}0-9\\+\\-/]")

func SplitSentences(text string) []string {
	indices := []int{0}
	for i := 0; i < len(text); i++ {
		s := text[i : i+1]
		_, match := sentenceDelimiters[s]
		if match {
			indices = append(indices, i)
		}
	}
	indices = append(indices, len(text))
	result := make([]string, 0, len(indices)+1)
	for i, index := range indices {
		j := i + 1
		if j < len(indices) {
			index2 := indices[j]
			if index2-index > 1 {
				result = append(result, text[index+1:index2])
			}
		}
	}
	return result
	//return sentenceDelimiters.Split(text, -1)
}

func IsAcceptable(phrase string, minCharLength int, maxWordsLength int) bool {
	// a phrase must have a min length in characters
	if len(phrase) < minCharLength {
		return false
	}

	// a phrase must have a max number of words
	words := wordSep.Split(phrase, -1)
	if len(words) > maxWordsLength {
		return false
	}
	digits := 0
	alpha := 0
	for _, c := range phrase {
		if unicode.IsDigit(c) {
			digits++
		} else if unicode.IsLetter(c) {
			alpha++
		}
	}
	// a phrase must have at least one alpha character
	if alpha == 0 {
		return false
	}
	// a phrase must have more alpha than digits characters
	if digits > alpha {
		return false
	}
	return true
}

func findStopwordIndices(words []string, stopwords map[string]bool) []int {
	result := []int{}
	for i, word := range words {
		if stopwords[word] {
			result = append(result, i)
		}
	}
	return result
}

func GenerateCandidateKeywords(sentenceList []string, stopwords map[string]bool, minCharLength int, maxWordsLength int) []string {
	phraseList := []string{}
	for _, s := range sentenceList {
		words := strings.Split(s, " ")
		stopwordIndices := findStopwordIndices(words, stopwords)

		if len(stopwordIndices) > 0 {
			if stopwordIndices[0] != 0 {
				phraseWords := words[0:stopwordIndices[0]]
				phrase := strings.Join(phraseWords, " ")
				if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
					phraseList = append(phraseList, phrase)
				}
			}
			for i, index := range stopwordIndices {
				j := i + 1
				if j < len(stopwordIndices) {
					index2 := stopwordIndices[j]
					if index2-index == 1 {
						continue
					}
					phraseWords := words[index+1 : index2]
					phrase := strings.Join(phraseWords, " ")
					if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
						phraseList = append(phraseList, phrase)
					}
				}
			}
			if stopwordIndices[len(stopwordIndices)-1] != len(words)-1 {
				index := stopwordIndices[len(stopwordIndices)-1]
				phraseWords := words[index+1:]
				phrase := strings.Join(phraseWords, " ")
				if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
					phraseList = append(phraseList, phrase)
				}
			}
		} else {
			phrase := strings.Join(words, " ")
			if phrase != "" && IsAcceptable(phrase, minCharLength, maxWordsLength) {
				phraseList = append(phraseList, phrase)
			}
		}
	}
	return phraseList
}

func IsNumber(s string) bool {
	if strings.Index(s, ".") != -1 {
		_, err := strconv.ParseFloat(s, 32)
		return err == nil
	} else {
		_, err := strconv.ParseInt(s, 10, 32)
		return err == nil
	}
}

func SeparateWords(text string, minWordReturnSize int) []string {
	//    Utility function to return a list of all words that are have a length
	//    greater than a specified number of characters.
	//    @param text The text that must be split in to words.
	//    @param min_word_return_size The minimum no of characters
	//           a word must have to be included.
	words := []string{}
	for _, singleWord := range wordSplitter.Split(text, -1) {
		currentWord := strings.ToLower(strings.TrimSpace(singleWord))
		// leave numbers in phrase, but don't count as words, since they tend to invalidate scores of their phrases
		if len(currentWord) > minWordReturnSize && currentWord != "" && !IsNumber(currentWord) {
			words = append(words, currentWord)
		}
	}
	return words
}

func CalculateWordScores(phraseList []string) map[string]float64 {
	wordFrequency := map[string]int{}
	wordDegree := map[string]int{}
	for _, phrase := range phraseList {
		wordList := SeparateWords(phrase, 0)
		wordListLength := len(wordList)
		wordListDegree := wordListLength - 1
		// if word_list_degree > 3: word_list_degree = 3 #exp.
		for _, word := range wordList {
			wordFrequency[word]++
			wordDegree[word] += wordListDegree
		}
	}
	for item := range wordFrequency {
		wordDegree[item] = wordDegree[item] + wordFrequency[item]
	}
	// Calculate Word scores = deg(w)/frew(w)
	wordScore := map[string]float64{}
	for item := range wordFrequency {
		wordScore[item] = float64(wordDegree[item]) / float64(wordFrequency[item])
	}
	return wordScore
}

func StringCount(stringList []string) map[string]int {
	stringCount := map[string]int{}
	for _, item := range stringList {
		stringCount[item]++
	}
	return stringCount
}

func GenerateCandidateKeywordScores(phraseList []string, wordScore map[string]float64, minKeywordFrequency int) (map[string]float64, map[string]int) {
	keywordCandidates := map[string]float64{}
	phraseCounts := StringCount(phraseList)
	for _, phrase := range phraseList {
		if minKeywordFrequency > 1 {
			if phraseCounts[phrase] < minKeywordFrequency {
				continue
			}
		}
		wordList := SeparateWords(phrase, 0)
		var candiateScore float64 = 0
		for _, word := range wordList {
			candiateScore += wordScore[word]
		}
		keywordCandidates[phrase] = candiateScore
	}

	return keywordCandidates, phraseCounts
}

func MapToKeywordScores(keywordScoreMap map[string]float64, keywordCountMap map[string]int) []KeywordScore {
	keywordScores := []KeywordScore{}
	for key, value := range keywordScoreMap {
		keywordScore := KeywordScore{
			Keyword: key,
			Score:   value,
		}
		count, _ := keywordCountMap[key]
		keywordScore.Count = count
		keywordScores = append(keywordScores, keywordScore)
	}
	return keywordScores
}

func loadStopWords(stopWordFile string) []string {
	stopWords := []string{}
	contents, err := ioutil.ReadFile(stopWordFile)
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	lines := strings.Split(string(contents), "\n")
	for _, line := range lines {
		line = strings.ToLower(strings.TrimSpace(line))
		if strings.Index(line, "#") != 0 {
			// in case more than one per line
			for _, word := range strings.Split(line, " ") {
				stopWords = append(stopWords, word)
			}

		}
	}
	return stopWords
}

func BuildStopWordMap(stopWordFilePath string) map[string]bool {
	stopWordList := loadStopWords(stopWordFilePath)
	stopWordMap := make(map[string]bool, len(stopWordList))
	for _, word := range stopWordList {
		stopWordMap[word] = true
	}
	return stopWordMap
}

// ByScore implements sort.Interface for []KeywordScore based on
// the Score field. Sorts in reverse order.
type ByScore []KeywordScore

func (a ByScore) Len() int           { return len(a) }
func (a ByScore) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByScore) Less(i, j int) bool { return a[i].Score > a[j].Score }

type Rake struct {
	stopWordsPath       string
	stopWords           map[string]bool
	minCharLength       int
	maxWordsLength      int
	minKeywordFrequency int
}

type KeywordScore struct {
	Keyword string
	Score   float64
	Count   int
}

func NewRake(stopWordsPath string, minCharLength int, maxWordsLength int, minKeywordFrequency int) *Rake {
	return &Rake{
		stopWordsPath:       stopWordsPath,
		stopWords:           BuildStopWordMap(stopWordsPath),
		minCharLength:       minCharLength,
		maxWordsLength:      maxWordsLength,
		minKeywordFrequency: minKeywordFrequency,
	}
}

func (rake *Rake) Run(text string) []KeywordScore {
	sentenceList := SplitSentences(text)
	phraseList := GenerateCandidateKeywords(sentenceList, rake.stopWords, rake.minCharLength, rake.maxWordsLength)
	wordScores := CalculateWordScores(phraseList)
	keywordCandidates, keywordCounts := GenerateCandidateKeywordScores(phraseList, wordScores, rake.minKeywordFrequency)
	keywordScores := MapToKeywordScores(keywordCandidates, keywordCounts)
	sort.Sort(ByScore(keywordScores))
	return keywordScores
}
<|endoftext|>


<commit_before>package multibar

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/sethgrid/curse"
)

type progressFunc func(progress int)

type BarContainer struct {
	Bars []*ProgressBar
}

type ProgressBar struct {
	Width           int
	Total           int
	LeftEnd         byte
	RightEnd        byte
	Fill            byte
	Head            byte
	Empty           byte
	ShowPercent     bool
	ShowTimeElapsed bool
	StartTime       time.Time
	Line            int
	Prepend         string
	progressChan    chan int
}

func New() (*BarContainer, error) {
	return &BarContainer{}, nil
}

func (b *BarContainer) Listen() {
	cases := make([]reflect.SelectCase, len(b.Bars))
	for i, bar := range b.Bars {
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(bar.progressChan)}
	}

	remaining := len(cases)
	for remaining > 0 {
		chosen, value, ok := reflect.Select(cases)
		if !ok {
			// The chosen channel has been closed, so zero out the channel to disable the case
			cases[chosen].Chan = reflect.ValueOf(nil)
			remaining -= 1
			continue
		}

		b.Bars[chosen].Update(int(value.Int()))
	}
	fmt.Println()
}

func (b *BarContainer) MakeBar(total int, prepend string) progressFunc {
	// can swallow err because sensible defaults are returned
	width, _, _ := curse.GetScreenDimensions()
	ch := make(chan int)
	bar := &ProgressBar{
		Width:           (width - len(prepend)) * 3 / 5,
		Total:           total,
		Prepend:         prepend,
		LeftEnd:         '[',
		RightEnd:        ']',
		Fill:            '=',
		Head:            '>',
		Empty:           '-',
		ShowPercent:     true,
		ShowTimeElapsed: true,
		StartTime:       time.Now(),
		progressChan:    ch,
	}

	b.Bars = append(b.Bars, bar)
	_, line, _ := curse.GetCursorPosition()
	bar.Line = line
	bar.Update(0)
	fmt.Println()
	return func(progress int) { bar.progressChan <- progress }
}

func (p *ProgressBar) AddPrepend(str string) {
	width, _, _ := curse.GetScreenDimensions()
	p.Prepend = str
	p.Width = (width - len(str)) * 3 / 5
}

func (p *ProgressBar) Update(progress int) {
	bar := make([]string, p.Width)

	// avoid division by zero errors on non-properly constructed progressbars
	if p.Width == 0 {
		p.Width = 1
	}
	if p.Total == 0 {
		p.Total = 1
	}
	justGotToFirstEmptySpace := true
	for i, _ := range bar {
		if float32(progress)/float32(p.Total) > float32(i)/float32(p.Width) {
			bar[i] = string(p.Fill)
		} else {
			bar[i] = string(p.Empty)
			if justGotToFirstEmptySpace {
				bar[i] = string(p.Head)
				justGotToFirstEmptySpace = false
			}
		}
	}

	percent := ""
	if p.ShowPercent {
		asInt := int(100 * (float32(progress) / float32(p.Total)))
		padding := ""
		if asInt < 10 {
			padding = "  "
		} else if asInt < 99 {
			padding = " "
		}
		percent = padding + strconv.Itoa(asInt) + "% "
	}

	timeElapsed := ""
	if p.ShowTimeElapsed {
		timeElapsed = " " + prettyTime(time.Since(p.StartTime))
	}
	currentRow, currentLine, _ := curse.GetCursorPosition()
	c := &curse.Cursor{}
	c.Move(1, p.Line)
	c.EraseCurrentLine()
	fmt.Printf("\r%s %s%c%s%c%s", p.Prepend, percent, p.LeftEnd, strings.Join(bar, ""), p.RightEnd, timeElapsed)
	c.Move(currentRow, currentLine)
}

func prettyTime(t time.Duration) string {
	re, err := regexp.Compile(`(\d+).(\d+)(\w+)`)
	if err != nil {
		return err.Error()
	}
	parts := re.FindSubmatch([]byte(t.String()))

	return string(parts[1]) + string(parts[3])
}
<commit_msg>made the progress bars terminate at the same line length<commit_after>package multibar

import (
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/sethgrid/curse"
)

type progressFunc func(progress int)

type BarContainer struct {
	Bars []*ProgressBar
}

type ProgressBar struct {
	Width           int
	Total           int
	LeftEnd         byte
	RightEnd        byte
	Fill            byte
	Head            byte
	Empty           byte
	ShowPercent     bool
	ShowTimeElapsed bool
	StartTime       time.Time
	Line            int
	Prepend         string
	progressChan    chan int
}

func New() (*BarContainer, error) {
	return &BarContainer{}, nil
}

func (b *BarContainer) Listen() {
	cases := make([]reflect.SelectCase, len(b.Bars))
	for i, bar := range b.Bars {
		cases[i] = reflect.SelectCase{Dir: reflect.SelectRecv, Chan: reflect.ValueOf(bar.progressChan)}
	}

	remaining := len(cases)
	for remaining > 0 {
		chosen, value, ok := reflect.Select(cases)
		if !ok {
			// The chosen channel has been closed, so zero out the channel to disable the case
			cases[chosen].Chan = reflect.ValueOf(nil)
			remaining -= 1
			continue
		}

		b.Bars[chosen].Update(int(value.Int()))
	}
	fmt.Println()
}

func (b *BarContainer) MakeBar(total int, prepend string) progressFunc {
	// can swallow err because sensible defaults are returned
	width, _, _ := curse.GetScreenDimensions()
	ch := make(chan int)
	bar := &ProgressBar{
		Width:           width - len(prepend) - 20,
		Total:           total,
		Prepend:         prepend,
		LeftEnd:         '[',
		RightEnd:        ']',
		Fill:            '=',
		Head:            '>',
		Empty:           '-',
		ShowPercent:     true,
		ShowTimeElapsed: true,
		StartTime:       time.Now(),
		progressChan:    ch,
	}

	b.Bars = append(b.Bars, bar)
	_, line, _ := curse.GetCursorPosition()
	bar.Line = line
	bar.Update(0)
	fmt.Println()

	return func(progress int) { bar.progressChan <- progress }
}

func (p *ProgressBar) AddPrepend(str string) {
	width, _, _ := curse.GetScreenDimensions()
	p.Prepend = str
	p.Width = (width - len(str)) * 3 / 5
}

func (p *ProgressBar) Update(progress int) {
	bar := make([]string, p.Width)

	// avoid division by zero errors on non-properly constructed progressbars
	if p.Width == 0 {
		p.Width = 1
	}
	if p.Total == 0 {
		p.Total = 1
	}
	justGotToFirstEmptySpace := true
	for i, _ := range bar {
		if float32(progress)/float32(p.Total) > float32(i)/float32(p.Width) {
			bar[i] = string(p.Fill)
		} else {
			bar[i] = string(p.Empty)
			if justGotToFirstEmptySpace {
				bar[i] = string(p.Head)
				justGotToFirstEmptySpace = false
			}
		}
	}

	percent := ""
	if p.ShowPercent {
		asInt := int(100 * (float32(progress) / float32(p.Total)))
		padding := ""
		if asInt < 10 {
			padding = "  "
		} else if asInt < 99 {
			padding = " "
		}
		percent = padding + strconv.Itoa(asInt) + "% "
	}

	timeElapsed := ""
	if p.ShowTimeElapsed {
		timeElapsed = " " + prettyTime(time.Since(p.StartTime))
	}
	currentRow, currentLine, _ := curse.GetCursorPosition()
	c := &curse.Cursor{}
	c.Move(1, p.Line)
	c.EraseCurrentLine()
	fmt.Printf("\r%s %s%c%s%c%s", p.Prepend, percent, p.LeftEnd, strings.Join(bar, ""), p.RightEnd, timeElapsed)
	c.Move(currentRow, currentLine)
}

func prettyTime(t time.Duration) string {
	re, err := regexp.Compile(`(\d+).(\d+)(\w+)`)
	if err != nil {
		return err.Error()
	}
	parts := re.FindSubmatch([]byte(t.String()))

	return string(parts[1]) + string(parts[3])
}
<|endoftext|>