<commit_before>package cmd

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/exercism/cli/config"
	"github.com/exercism/cli/workspace"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestSubmitWithoutToken(t *testing.T) {
	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: viper.New(),
		DefaultBaseURL:  "http://example.com",
	}

	err := runSubmit(cfg, flags, []string{})
	assert.Regexp(t, "Welcome to Exercism", err.Error())
}

func TestSubmitWithoutWorkspace(t *testing.T) {
	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)

	v := viper.New()
	v.Set("token", "abc123")

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err := runSubmit(cfg, flags, []string{})
	assert.Regexp(t, "run configure", err.Error())
}

func TestSubmitNonExistentFile(t *testing.T) {
	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)

	tmpDir, err := ioutil.TempDir("", "submit-no-such-file")
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-1.txt"), []byte("This is file 1"), os.FileMode(0755))
	assert.NoError(t, err)

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-2.txt"), []byte("This is file 2"), os.FileMode(0755))
	assert.NoError(t, err)

	err = runSubmit(cfg, flags, []string{filepath.Join(tmpDir, "file-1.txt"), "no-such-file.txt", filepath.Join(tmpDir, "file-2.txt")})
	assert.Regexp(t, "no such file", err.Error())
}

func TestSubmitFilesAndDir(t *testing.T) {
	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)

	tmpDir, err := ioutil.TempDir("", "submit-no-such-file")
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-1.txt"), []byte("This is file 1"), os.FileMode(0755))
	assert.NoError(t, err)

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-2.txt"), []byte("This is file 2"), os.FileMode(0755))
	assert.NoError(t, err)

	err = runSubmit(cfg, flags, []string{filepath.Join(tmpDir, "file-1.txt"), tmpDir, filepath.Join(tmpDir, "file-2.txt")})
	assert.Regexp(t, "is a directory", err.Error())
}

func TestSubmitFiles(t *testing.T) {
	oldOut := Out
	oldErr := Err
	Out = ioutil.Discard
	Err = ioutil.Discard
	defer func() {
		Out = oldOut
		Err = oldErr
	}()
	// The fake endpoint will populate this when it receives the call from the command.
	submittedFiles := map[string]string{}
	ts := fakeSubmitServer(t, submittedFiles)
	defer ts.Close()

	tmpDir, err := ioutil.TempDir("", "submit-files")
	assert.NoError(t, err)

	dir := filepath.Join(tmpDir, "bogus-track", "bogus-exercise")
	os.MkdirAll(filepath.Join(dir, "subdir"), os.FileMode(0755))

	type file struct {
		relativePath string
		contents     string
	}

	file1 := file{
		relativePath: "file-1.txt",
		contents:     "This is file 1.",
	}
	file2 := file{
		relativePath: filepath.Join("subdir", "file-2.txt"),
		contents:     "This is file 2.",
	}
	// We don't filter *.md files if you explicitly pass the file path.
	file3 := file{
		relativePath: "README.md",
		contents:     "The readme.",
	}

	filenames := make([]string, 0, 3)
	for _, file := range []file{file1, file2, file3} {
		path := filepath.Join(dir, file.relativePath)
		filenames = append(filenames, path)
		err := ioutil.WriteFile(path, []byte(file.contents), os.FileMode(0755))
		assert.NoError(t, err)
	}

	writeFakeSolution(t, dir, "bogus-track", "bogus-exercise")

	flags := pflag.NewFlagSet("fake", pflag.PanicOnError)
	setupSubmitFlags(flags)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)
	v.Set("apibaseurl", ts.URL)

	cliCfg := &config.CLIConfig{
		Config: config.New(tmpDir, "cli"),
		Tracks: config.Tracks{},
	}
	cliCfg.Tracks["bogus-track"] = config.NewTrack("bogus-track")
	err = cliCfg.Write()
	assert.NoError(t, err)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		Dir:             tmpDir,
		UserViperConfig: v,
		CLIConfig:       cliCfg,
	}

	err = runSubmit(cfg, flags, filenames)
	assert.NoError(t, err)

	assert.Equal(t, 3, len(submittedFiles))

	for _, file := range []file{file1, file2, file3} {
		path := string(os.PathSeparator) + file.relativePath
		assert.Equal(t, file.contents, submittedFiles[path])
	}
}

func TestSubmitFilesFromDifferentSolutions(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "dir-1-submit")
	assert.NoError(t, err)

	dir1 := filepath.Join(tmpDir, "bogus-track", "bogus-exercise-1")
	os.MkdirAll(dir1, os.FileMode(0755))
	writeFakeSolution(t, dir1, "bogus-track", "bogus-exercise-1")

	dir2 := filepath.Join(tmpDir, "bogus-track", "bogus-exercise-2")
	os.MkdirAll(dir2, os.FileMode(0755))
	writeFakeSolution(t, dir2, "bogus-track", "bogus-exercise-2")

	file1 := filepath.Join(dir1, "file-1.txt")
	err = ioutil.WriteFile(file1, []byte("This is file 1."), os.FileMode(0755))
	assert.NoError(t, err)

	file2 := filepath.Join(dir2, "file-2.txt")
	err = ioutil.WriteFile(file2, []byte("This is file 2."), os.FileMode(0755))
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cliCfg := &config.CLIConfig{
		Config: config.New(tmpDir, "cli"),
		Tracks: config.Tracks{},
	}
	cliCfg.Tracks["bogus-track"] = config.NewTrack("bogus-track")
	err = cliCfg.Write()
	assert.NoError(t, err)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		Dir:             tmpDir,
		UserViperConfig: v,
		CLIConfig:       cliCfg,
	}

	err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{file1, file2})
	assert.Error(t, err)
	assert.Regexp(t, "more than one solution", err.Error())
}

func fakeSubmitServer(t *testing.T, submittedFiles map[string]string) *httptest.Server {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseMultipartForm(2 << 10)
		if err != nil {
			t.Fatal(err)
		}
		mf := r.MultipartForm

		files := mf.File["files[]"]
		for _, fileHeader := range files {
			file, err := fileHeader.Open()
			if err != nil {
				t.Fatal(err)
			}
			defer file.Close()
			body, err := ioutil.ReadAll(file)
			if err != nil {
				t.Fatal(err)
			}
			submittedFiles[fileHeader.Filename] = string(body)
		}
	})
	return httptest.NewServer(handler)
}

func writeFakeSolution(t *testing.T, dir, trackID, exerciseSlug string) {
	solution := &workspace.Solution{
		ID:          "bogus-solution-uuid",
		Track:       trackID,
		Exercise:    exerciseSlug,
		URL:         "http://example.com/bogus-url",
		IsRequester: true,
	}
	err := solution.Write(dir)
	assert.NoError(t, err)
}
<commit_msg>Inline flagsets in submit tests<commit_after>package cmd

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"testing"

	"github.com/exercism/cli/config"
	"github.com/exercism/cli/workspace"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
)

func TestSubmitWithoutToken(t *testing.T) {
	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: viper.New(),
		DefaultBaseURL:  "http://example.com",
	}

	err := runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	assert.Regexp(t, "Welcome to Exercism", err.Error())
}

func TestSubmitWithoutWorkspace(t *testing.T) {
	v := viper.New()
	v.Set("token", "abc123")

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err := runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{})
	assert.Regexp(t, "run configure", err.Error())
}

func TestSubmitNonExistentFile(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "submit-no-such-file")
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-1.txt"), []byte("This is file 1"), os.FileMode(0755))
	assert.NoError(t, err)

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-2.txt"), []byte("This is file 2"), os.FileMode(0755))
	assert.NoError(t, err)
	files := []string{
		filepath.Join(tmpDir, "file-1.txt"),
		"no-such-file.txt",
		filepath.Join(tmpDir, "file-2.txt"),
	}
	err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), files)
	assert.Regexp(t, "no such file", err.Error())
}

func TestSubmitFilesAndDir(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "submit-no-such-file")
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		UserViperConfig: v,
		DefaultBaseURL:  "http://example.com",
	}

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-1.txt"), []byte("This is file 1"), os.FileMode(0755))
	assert.NoError(t, err)

	err = ioutil.WriteFile(filepath.Join(tmpDir, "file-2.txt"), []byte("This is file 2"), os.FileMode(0755))
	assert.NoError(t, err)
	files := []string{
		filepath.Join(tmpDir, "file-1.txt"),
		tmpDir,
		filepath.Join(tmpDir, "file-2.txt"),
	}
	err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), files)
	assert.Regexp(t, "is a directory", err.Error())
}

func TestSubmitFiles(t *testing.T) {
	oldOut := Out
	oldErr := Err
	Out = ioutil.Discard
	Err = ioutil.Discard
	defer func() {
		Out = oldOut
		Err = oldErr
	}()
	// The fake endpoint will populate this when it receives the call from the command.
	submittedFiles := map[string]string{}
	ts := fakeSubmitServer(t, submittedFiles)
	defer ts.Close()

	tmpDir, err := ioutil.TempDir("", "submit-files")
	assert.NoError(t, err)

	dir := filepath.Join(tmpDir, "bogus-track", "bogus-exercise")
	os.MkdirAll(filepath.Join(dir, "subdir"), os.FileMode(0755))
	writeFakeSolution(t, dir, "bogus-track", "bogus-exercise")

	file1 := filepath.Join(dir, "file-1.txt")
	err = ioutil.WriteFile(file1, []byte("This is file 1."), os.FileMode(0755))
	assert.NoError(t, err)

	file2 := filepath.Join(dir, "subdir", "file-2.txt")
	err = ioutil.WriteFile(file2, []byte("This is file 2."), os.FileMode(0755))
	assert.NoError(t, err)

	// We don't filter *.md files if you explicitly pass the file path.
	readme := filepath.Join(dir, "README.md")
	err = ioutil.WriteFile(readme, []byte("This is the readme."), os.FileMode(0755))
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)
	v.Set("apibaseurl", ts.URL)

	cliCfg := &config.CLIConfig{
		Config: config.New(tmpDir, "cli"),
		Tracks: config.Tracks{},
	}
	cliCfg.Tracks["bogus-track"] = config.NewTrack("bogus-track")
	err = cliCfg.Write()
	assert.NoError(t, err)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		Dir:             tmpDir,
		UserViperConfig: v,
		CLIConfig:       cliCfg,
	}

	files := []string{
		file1, file2, readme,
	}
	err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), files)
	assert.NoError(t, err)

	assert.Equal(t, 3, len(submittedFiles))

	assert.Equal(t, "This is file 1.", submittedFiles[string(os.PathSeparator)+"file-1.txt"])
	assert.Equal(t, "This is file 2.", submittedFiles[string(os.PathSeparator)+filepath.Join("subdir", "file-2.txt")])
	assert.Equal(t, "This is the readme.", submittedFiles[string(os.PathSeparator)+"README.md"])
}

func TestSubmitFilesFromDifferentSolutions(t *testing.T) {
	tmpDir, err := ioutil.TempDir("", "dir-1-submit")
	assert.NoError(t, err)

	dir1 := filepath.Join(tmpDir, "bogus-track", "bogus-exercise-1")
	os.MkdirAll(dir1, os.FileMode(0755))
	writeFakeSolution(t, dir1, "bogus-track", "bogus-exercise-1")

	dir2 := filepath.Join(tmpDir, "bogus-track", "bogus-exercise-2")
	os.MkdirAll(dir2, os.FileMode(0755))
	writeFakeSolution(t, dir2, "bogus-track", "bogus-exercise-2")

	file1 := filepath.Join(dir1, "file-1.txt")
	err = ioutil.WriteFile(file1, []byte("This is file 1."), os.FileMode(0755))
	assert.NoError(t, err)

	file2 := filepath.Join(dir2, "file-2.txt")
	err = ioutil.WriteFile(file2, []byte("This is file 2."), os.FileMode(0755))
	assert.NoError(t, err)

	v := viper.New()
	v.Set("token", "abc123")
	v.Set("workspace", tmpDir)

	cliCfg := &config.CLIConfig{
		Config: config.New(tmpDir, "cli"),
		Tracks: config.Tracks{},
	}
	cliCfg.Tracks["bogus-track"] = config.NewTrack("bogus-track")
	err = cliCfg.Write()
	assert.NoError(t, err)

	cfg := config.Configuration{
		Persister:       config.InMemoryPersister{},
		Dir:             tmpDir,
		UserViperConfig: v,
		CLIConfig:       cliCfg,
	}

	err = runSubmit(cfg, pflag.NewFlagSet("fake", pflag.PanicOnError), []string{file1, file2})
	assert.Error(t, err)
	assert.Regexp(t, "more than one solution", err.Error())
}

func fakeSubmitServer(t *testing.T, submittedFiles map[string]string) *httptest.Server {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseMultipartForm(2 << 10)
		if err != nil {
			t.Fatal(err)
		}
		mf := r.MultipartForm

		files := mf.File["files[]"]
		for _, fileHeader := range files {
			file, err := fileHeader.Open()
			if err != nil {
				t.Fatal(err)
			}
			defer file.Close()
			body, err := ioutil.ReadAll(file)
			if err != nil {
				t.Fatal(err)
			}
			submittedFiles[fileHeader.Filename] = string(body)
		}
	})
	return httptest.NewServer(handler)
}

func writeFakeSolution(t *testing.T, dir, trackID, exerciseSlug string) {
	solution := &workspace.Solution{
		ID:          "bogus-solution-uuid",
		Track:       trackID,
		Exercise:    exerciseSlug,
		URL:         "http://example.com/bogus-url",
		IsRequester: true,
	}
	err := solution.Write(dir)
	assert.NoError(t, err)
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"sync"

	"golang.org/x/net/websocket"

	"github.com/dhowden/httpauth"
	"github.com/dhowden/itl"

	"github.com/dhowden/tchaik/index"
	"github.com/dhowden/tchaik/store"
	"github.com/dhowden/tchaik/store/cmdflag"
)

var debug bool
var itlXML, tchLib string

var listenAddr string
var certFile, keyFile string

var auth bool

func init() {
	flag.BoolVar(&debug, "debug", false, "print debugging information")

	flag.StringVar(&listenAddr, "listen", "localhost:8080", "bind address to http listen")
	flag.StringVar(&certFile, "tls-cert", "", "path to a certificate file, must also specify -tls-key")
	flag.StringVar(&keyFile, "tls-key", "", "path to a certificate key file, must also specify -tls-cert")

	flag.StringVar(&itlXML, "itlXML", "", "path to iTunes Library XML file")
	flag.StringVar(&tchLib, "lib", "", "path to Tchaik library file")

	flag.BoolVar(&auth, "auth", false, "use basic HTTP authentication")
}

var creds = httpauth.Creds(map[string]string{
	"user": "password",
})

func readLibrary() (index.Library, error) {
	if itlXML == "" && tchLib == "" {
		return nil, fmt.Errorf("must specify one library file (-itlXML or -lib)")
	}

	if itlXML != "" && tchLib != "" {
		return nil, fmt.Errorf("must only specify one library file (-itlXML or -lib)")
	}

	var l index.Library
	if itlXML != "" {
		f, err := os.Open(itlXML)
		if err != nil {
			return nil, fmt.Errorf("could not open iTunes library file: %v", err)
		}

		fmt.Printf("Parsing %v...", itlXML)
		it, err := itl.ReadFromXML(f)
		if err != nil {
			return nil, fmt.Errorf("error parsing iTunes library file: %v", err)
		}
		f.Close()
		fmt.Println("done.")

		fmt.Printf("Building Tchaik Library...")
		l = index.Convert(index.NewITunesLibrary(&it), "TrackID")
		fmt.Println("done.")
		return l, nil
	}

	f, err := os.Open(tchLib)
	if err != nil {
		return nil, fmt.Errorf("could not open Tchaik library file: %v", err)
	}

	fmt.Printf("Parsing %v...", tchLib)
	l, err = index.ReadFrom(f)
	if err != nil {
		return nil, fmt.Errorf("error parsing Tchaik library file: %v\n", err)
	}
	fmt.Println("done.")
	return l, nil
}

func buildRootCollection(l index.Library) index.Collection {
	root := index.Collect(l, index.ByAttr(index.StringAttr("Album")))
	index.SortKeysByGroupName(root)
	return root
}

func buildSearchIndex(c index.Collection) index.Searcher {
	wi := index.BuildWordIndex(c, []string{"Composer", "Artist", "Album", "Name"})
	return index.FlatSearcher{
		Searcher: index.WordsIntersectSearcher(index.BuildPrefixExpandSearcher(wi, wi, 10)),
	}
}

func main() {
	flag.Parse()
	l, err := readLibrary()
	if err != nil {
		fmt.Printf("error: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Building root collection...")
	root := buildRootCollection(l)
	fmt.Println("done.")

	fmt.Printf("Building search index...")
	searcher := buildSearchIndex(root)
	fmt.Println("done.")

	mediaFileSystem, artworkFileSystem, err := cmdflag.Stores()
	if err != nil {
		fmt.Println("error setting up stores:", err)
		os.Exit(1)
	}

	if debug {
		mediaFileSystem = store.LogFileSystem{
			Name:       "Media",
			FileSystem: mediaFileSystem,
		}
		artworkFileSystem = store.LogFileSystem{
			Name:       "Artwork",
			FileSystem: artworkFileSystem,
		}
	}

	mediaFileSystem = &libraryFileSystem{mediaFileSystem, l}
	artworkFileSystem = &libraryFileSystem{artworkFileSystem, l}

	libAPI := LibraryAPI{
		Library:  l,
		root:     root,
		searcher: searcher,
	}

	m := buildMainHandler(libAPI, mediaFileSystem, artworkFileSystem)

	if certFile != "" && keyFile != "" {
		fmt.Printf("Web server is running on https://%v\n", listenAddr)
		fmt.Println("Quit the server with CTRL-C.")

		log.Fatal(http.ListenAndServeTLS(listenAddr, certFile, keyFile, m))
	}

	fmt.Printf("Web server is running on http://%v\n", listenAddr)
	fmt.Println("Quit the server with CTRL-C.")

	log.Fatal(http.ListenAndServe(listenAddr, m))
}

func buildMainHandler(l LibraryAPI, mediaFileSystem, artworkFileSystem http.FileSystem) http.Handler {
	var c httpauth.Checker = httpauth.None{}
	if auth {
		c = creds
	}

	w := httpauth.NewServeMux(c, http.NewServeMux())
	w.HandleFunc("/", rootHandler)
	w.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("ui/static"))))
	w.Handle("/track/", http.StripPrefix("/track/", http.FileServer(mediaFileSystem)))
	w.Handle("/artwork/", http.StripPrefix("/artwork/", http.FileServer(artworkFileSystem)))
	w.Handle("/socket", websocket.Handler(socketHandler(l)))
	return w
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett")
	http.ServeFile(w, r, "ui/tchaik.html")
}

// Websocket handling
type socket struct {
	io.ReadWriter
	done chan struct{}
}

func (s *socket) Close() {
	select {
	case <-s.done:
		return
	default:
	}
	close(s.done)
}

type Command struct {
	Action string
	Input  string
	Path   []string
}

const (
	FetchAction  string = "FETCH"
	SearchAction string = "SEARCH"
)

func socketHandler(l LibraryAPI) func(ws *websocket.Conn) {
	return func(ws *websocket.Conn) {
		s := socket{ws, make(chan struct{})}
		out, in := make(chan interface{}), make(chan *Command)
		errCh := make(chan error)

		wg := &sync.WaitGroup{}
		wg.Add(2)

		// Encode messages from process and encode to the client
		enc := json.NewEncoder(s)
		go func() {
			defer wg.Done()
			defer s.Close()

			for {
				select {
				case x, ok := <-out:
					if !ok {
						return
					}

					if debug {
						b, err := json.MarshalIndent(x, "", "  ")
						if err != nil {
							log.Fatal(err)
						}
						fmt.Println(string(b))
					}

					if err := enc.Encode(x); err != nil {
						errCh <- fmt.Errorf("encode: %v", err)
						return
					}
				case <-s.done:
					return
				}
			}
		}()

		// Decode messages from the client and send them on the in channel
		go func() {
			defer wg.Done()
			defer s.Close()

			dec := json.NewDecoder(s)
			for {
				c := &Command{}
				if err := dec.Decode(c); err != nil {
					if err == io.EOF && debug {
						fmt.Println("websocket closed")
						return
					}
					errCh <- err
					return
				}
				in <- c
			}
		}()

		go func() {
			for x := range in {
				if debug {
					fmt.Printf("command received: %#v\n", x)
				}
				switch x.Action {
				case FetchAction:
					handleCollectionList(l, x, out)
				case SearchAction:
					handleSearch(l, x, out)
				default:
					fmt.Printf("unknown command: %v", x.Action)
				}
			}
		}()

		go func() {
			wg.Wait()

			close(in)
			close(out)
			close(errCh)
		}()

		go func() {
			for err := range errCh {
				if err != nil {
					fmt.Printf("websocket handler: %v\n", err)
				}
			}
		}()

		select {}
	}
}

func handleCollectionList(l LibraryAPI, x *Command, out chan<- interface{}) {
	if len(x.Path) < 1 {
		fmt.Printf("invalid path: %v\n", x.Path)
		return
	}

	g, err := l.Fetch(l.root, x.Path[1:])
	if err != nil {
		fmt.Printf("error in Fetch: %v (path: %#v)", err, x.Path[1:])
		return
	}

	o := struct {
		Action string
		Data   interface{}
	}{
		x.Action,
		struct {
			Path []string
			Item group
		}{
			x.Path,
			g,
		},
	}
	out <- o
}

func handleSearch(l LibraryAPI, x *Command, out chan<- interface{}) {
	paths := l.searcher.Search(x.Input)
	o := struct {
		Action string
		Data   interface{}
	}{
		Action: x.Action,
		Data:   paths,
	}
	out <- o
}
<commit_msg>Use websocket.JSON for sending/receiving instead of own wrapper<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"

	"golang.org/x/net/websocket"

	"github.com/dhowden/httpauth"
	"github.com/dhowden/itl"

	"github.com/dhowden/tchaik/index"
	"github.com/dhowden/tchaik/store"
	"github.com/dhowden/tchaik/store/cmdflag"
)

var debug bool
var itlXML, tchLib string

var listenAddr string
var certFile, keyFile string

var auth bool

func init() {
	flag.BoolVar(&debug, "debug", false, "print debugging information")

	flag.StringVar(&listenAddr, "listen", "localhost:8080", "bind address to http listen")
	flag.StringVar(&certFile, "tls-cert", "", "path to a certificate file, must also specify -tls-key")
	flag.StringVar(&keyFile, "tls-key", "", "path to a certificate key file, must also specify -tls-cert")

	flag.StringVar(&itlXML, "itlXML", "", "path to iTunes Library XML file")
	flag.StringVar(&tchLib, "lib", "", "path to Tchaik library file")

	flag.BoolVar(&auth, "auth", false, "use basic HTTP authentication")
}

var creds = httpauth.Creds(map[string]string{
	"user": "password",
})

func readLibrary() (index.Library, error) {
	if itlXML == "" && tchLib == "" {
		return nil, fmt.Errorf("must specify one library file (-itlXML or -lib)")
	}

	if itlXML != "" && tchLib != "" {
		return nil, fmt.Errorf("must only specify one library file (-itlXML or -lib)")
	}

	var l index.Library
	if itlXML != "" {
		f, err := os.Open(itlXML)
		if err != nil {
			return nil, fmt.Errorf("could not open iTunes library file: %v", err)
		}

		fmt.Printf("Parsing %v...", itlXML)
		it, err := itl.ReadFromXML(f)
		if err != nil {
			return nil, fmt.Errorf("error parsing iTunes library file: %v", err)
		}
		f.Close()
		fmt.Println("done.")

		fmt.Printf("Building Tchaik Library...")
		l = index.Convert(index.NewITunesLibrary(&it), "TrackID")
		fmt.Println("done.")
		return l, nil
	}

	f, err := os.Open(tchLib)
	if err != nil {
		return nil, fmt.Errorf("could not open Tchaik library file: %v", err)
	}

	fmt.Printf("Parsing %v...", tchLib)
	l, err = index.ReadFrom(f)
	if err != nil {
		return nil, fmt.Errorf("error parsing Tchaik library file: %v\n", err)
	}
	fmt.Println("done.")
	return l, nil
}

func buildRootCollection(l index.Library) index.Collection {
	root := index.Collect(l, index.ByAttr(index.StringAttr("Album")))
	index.SortKeysByGroupName(root)
	return root
}

func buildSearchIndex(c index.Collection) index.Searcher {
	wi := index.BuildWordIndex(c, []string{"Composer", "Artist", "Album", "Name"})
	return index.FlatSearcher{
		Searcher: index.WordsIntersectSearcher(index.BuildPrefixExpandSearcher(wi, wi, 10)),
	}
}

func main() {
	flag.Parse()
	l, err := readLibrary()
	if err != nil {
		fmt.Printf("error: %v\n", err)
		os.Exit(1)
	}

	fmt.Printf("Building root collection...")
	root := buildRootCollection(l)
	fmt.Println("done.")

	fmt.Printf("Building search index...")
	searcher := buildSearchIndex(root)
	fmt.Println("done.")

	mediaFileSystem, artworkFileSystem, err := cmdflag.Stores()
	if err != nil {
		fmt.Println("error setting up stores:", err)
		os.Exit(1)
	}

	if debug {
		mediaFileSystem = store.LogFileSystem{
			Name:       "Media",
			FileSystem: mediaFileSystem,
		}
		artworkFileSystem = store.LogFileSystem{
			Name:       "Artwork",
			FileSystem: artworkFileSystem,
		}
	}

	mediaFileSystem = &libraryFileSystem{mediaFileSystem, l}
	artworkFileSystem = &libraryFileSystem{artworkFileSystem, l}

	libAPI := LibraryAPI{
		Library:  l,
		root:     root,
		searcher: searcher,
	}

	m := buildMainHandler(libAPI, mediaFileSystem, artworkFileSystem)

	if certFile != "" && keyFile != "" {
		fmt.Printf("Web server is running on https://%v\n", listenAddr)
		fmt.Println("Quit the server with CTRL-C.")

		log.Fatal(http.ListenAndServeTLS(listenAddr, certFile, keyFile, m))
	}

	fmt.Printf("Web server is running on http://%v\n", listenAddr)
	fmt.Println("Quit the server with CTRL-C.")

	log.Fatal(http.ListenAndServe(listenAddr, m))
}

func buildMainHandler(l LibraryAPI, mediaFileSystem, artworkFileSystem http.FileSystem) http.Handler {
	var c httpauth.Checker = httpauth.None{}
	if auth {
		c = creds
	}

	w := httpauth.NewServeMux(c, http.NewServeMux())
	w.HandleFunc("/", rootHandler)
	w.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("ui/static"))))
	w.Handle("/track/", http.StripPrefix("/track/", http.FileServer(mediaFileSystem)))
	w.Handle("/artwork/", http.StripPrefix("/artwork/", http.FileServer(artworkFileSystem)))
	w.Handle("/socket", websocket.Handler(socketHandler(l)))
	return w
}

func rootHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-Clacks-Overhead", "GNU Terry Pratchett")
	http.ServeFile(w, r, "ui/tchaik.html")
}

type Command struct {
	Action string
	Input  string
	Path   []string
}

const (
	FetchAction  string = "FETCH"
	SearchAction string = "SEARCH"
)

func socketHandler(l LibraryAPI) func(ws *websocket.Conn) {
	return func(ws *websocket.Conn) {
		defer ws.Close()

		var err error
		for {
			var c Command
			err = websocket.JSON.Receive(ws, &c)
			if err != nil {
				if err != io.EOF {
					err = fmt.Errorf("receive: %v", err)
				}
				break
			}

			var resp interface{}
			switch c.Action {
			case FetchAction:
				resp, err = handleCollectionList(l, c)
			case SearchAction:
				resp = handleSearch(l, c)
			default:
				err = fmt.Errorf("unknown action: %v", c.Action)
			}

			if err != nil {
				break
			}

			err = websocket.JSON.Send(ws, resp)
			if err != nil {
				if err != io.EOF {
					err = fmt.Errorf("send: %v", err)
				}
				break
			}
		}

		if err != nil && err != io.EOF {
			fmt.Printf("socket error: %v", err)
		}
	}
}

func handleCollectionList(l LibraryAPI, c Command) (interface{}, error) {
	if len(c.Path) < 1 {
		return nil, fmt.Errorf("invalid path: %v\n", c.Path)
	}

	g, err := l.Fetch(l.root, c.Path[1:])
	if err != nil {
		return nil, fmt.Errorf("error in Fetch: %v (path: %#v)", err, c.Path[1:])
	}

	return struct {
		Action string
		Data   interface{}
	}{
		c.Action,
		struct {
			Path []string
			Item group
		}{
			c.Path,
			g,
		},
	}, nil
}

func handleSearch(l LibraryAPI, c Command) interface{} {
	return struct {
		Action string
		Data   interface{}
	}{
		Action: c.Action,
		Data:   l.searcher.Search(c.Input),
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"sync"

	"github.com/thomersch/grandine/lib/cugdf"
	"github.com/thomersch/grandine/lib/mvt"
	"github.com/thomersch/grandine/lib/spatial"
	"github.com/thomersch/grandine/lib/tile"
)

type zmLvl []int

func (zm *zmLvl) String() string {
	return fmt.Sprintf("%d", *zm)
}

func (zm *zmLvl) Set(value string) error {
	v, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("%s (only integer values are allowed)", value)
	}
	*zm = append(*zm, v)
	return nil
}

var zoomlevels zmLvl

func main() {
	source := flag.String("src", "geo.geojson", "file to read from, supported formats: geojson, cugdf")
	target := flag.String("target", "tiles", "path where the tiles will be written")
	defaultLayer := flag.Bool("default-layer", true, "if no layer name is specified in the feature, whether it will be put into a default layer")
	workersNumber := flag.Int("workers", runtime.GOMAXPROCS(0), "number of workers")
	flag.Var(&zoomlevels, "zoom", "one or more zoom level of which the tiles will be rendered")
	flag.Parse()

	if len(zoomlevels) == 0 {
		log.Fatal("no zoom levels specified")
	}

	f, err := os.Open(*source)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	err = os.MkdirAll(*target, 0777)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("parsing input...")
	fc := spatial.FeatureCollection{}

	if strings.HasSuffix(strings.ToLower(*source), "geojson") {
		if err := json.NewDecoder(f).Decode(&fc); err != nil {
			log.Fatal(err)
		}
	} else {
		fc.Features, err = cugdf.Unmarshal(f)
		if err != nil {
			log.Fatal(err)
		}
	}

	if len(fc.Features) == 0 {
		log.Fatal("no features in input file")
	}

	log.Printf("read %d features", len(fc.Features))

	var bboxPts []spatial.Point
	for _, feat := range fc.Features {
		bb := feat.Geometry.BBox()
		bboxPts = append(bboxPts, bb.SW, bb.NE)
	}

	bbox := spatial.Line(bboxPts).BBox()
	log.Println("filtering features...")

	// TODO: consider using rtree
	features := spatial.Filter(fc.Features, bbox)
	if len(features) == 0 {
		log.Println("no features to be processed, exiting.")
		os.Exit(2)
	}
	log.Printf("%d features to be processed", len(features))

	var tc []tile.ID
	for _, zoomlevel := range zoomlevels {
		tc = append(tc, tile.Coverage(bbox, zoomlevel)...)
	}
	log.Printf("attempting to generate %d tiles", len(tc))

	dtw := diskTileWriter{basedir: *target}
	dlm := defaultLayerMapper{defaultLayer: *defaultLayer}

	var (
		wg sync.WaitGroup
		ws = workerSlices(tc, *workersNumber)
	)
	for wrk := 0; wrk < len(ws); wrk++ {
		wg.Add(1)
		go func(i int) {
			generateTiles(ws[i], features, &dtw, &dlm)
			wg.Done()
		}(wrk)
	}
	wg.Wait()
}

func workerSlices(tiles []tile.ID, wrkNum int) [][]tile.ID {
	var r [][]tile.ID
	if len(tiles) <= wrkNum {
		for t := 0; t < len(tiles); t++ {
			r = append(r, []tile.ID{tiles[t]})
		}
		return r
	}
	for wrkr := 0; wrkr < wrkNum; wrkr++ {
		start := (len(tiles) / wrkNum) * wrkr
		end := (len(tiles) / wrkNum) * (wrkr + 1)
		if wrkr == wrkNum-1 {
			end = len(tiles)
		}
		r = append(r, tiles[start:end])
	}
	return r
}

type diskTileWriter struct {
	basedir string
}

func (tw *diskTileWriter) WriteTile(tID tile.ID, buf []byte) error {
	err := os.MkdirAll(filepath.Join(tw.basedir, strconv.Itoa(tID.Z), strconv.Itoa(tID.X)), 0777)
	if err != nil {
		return err
	}
	tf, err := os.Create(filepath.Join(tw.basedir, strconv.Itoa(tID.Z), strconv.Itoa(tID.X), strconv.Itoa(tID.Y)+".mvt"))
	if err != nil {
		return err
	}
	defer tf.Close()
	_, err = tf.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

type defaultLayerMapper struct {
	defaultLayer bool
}

func (dlm *defaultLayerMapper) LayerName(props map[string]interface{}) string {
	if layerName, ok := props["@layer"]; ok {
		return layerName.(string)
	}
	if dlm.defaultLayer {
		return "default"
	}
	return ""
}

type layerMapper interface {
	LayerName(map[string]interface{}) string
}

type tileWriter interface {
	WriteTile(tile.ID, []byte) error
}

func generateTiles(tIDs []tile.ID, features []spatial.Feature, tw tileWriter, lm layerMapper) {
	for _, tID := range tIDs {
		log.Printf("Generating %s", tID)
		var layers = map[string][]spatial.Feature{}
		tileClipBBox := tID.BBox()
		for _, feat := range spatial.Filter(features, tileClipBBox) {
			for _, geom := range feat.Geometry.ClipToBBox(tileClipBBox) {
				feat.Geometry = geom
				ln := lm.LayerName(feat.Props)
				if len(ln) != 0 {
					if _, ok := layers[ln]; !ok {
						layers[ln] = []spatial.Feature{feat}
					} else {
						layers[ln] = append(layers[ln], feat)
					}
				}
			}
		}
		if !anyFeatures(layers) {
			continue
		}
		buf, err := mvt.EncodeTile(layers, tID)
		if err != nil {
			log.Fatal(err)
		}

		err = tw.WriteTile(tID, buf)
		if err != nil {
			log.Fatal(err)
		}
	}
}

func anyFeatures(layers map[string][]spatial.Feature) bool {
	for _, ly := range layers {
		if len(ly) > 0 {
			return true
		}
	}
	return false
}
<commit_msg>cmd/tiler: cpu profiling<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"runtime/pprof"
	"strconv"
	"strings"
	"sync"

	"github.com/thomersch/grandine/lib/cugdf"
	"github.com/thomersch/grandine/lib/mvt"
	"github.com/thomersch/grandine/lib/spatial"
	"github.com/thomersch/grandine/lib/tile"
)

type zmLvl []int

func (zm *zmLvl) String() string {
	return fmt.Sprintf("%d", *zm)
}

func (zm *zmLvl) Set(value string) error {
	v, err := strconv.Atoi(value)
	if err != nil {
		return fmt.Errorf("%s (only integer values are allowed)", value)
	}
	*zm = append(*zm, v)
	return nil
}

var zoomlevels zmLvl

func main() {
	source := flag.String("src", "geo.geojson", "file to read from, supported formats: geojson, cugdf")
	target := flag.String("target", "tiles", "path where the tiles will be written")
	defaultLayer := flag.Bool("default-layer", true, "if no layer name is specified in the feature, whether it will be put into a default layer")
	workersNumber := flag.Int("workers", runtime.GOMAXPROCS(0), "number of workers")
	cpuProfile := flag.String("cpuprof", "", "writes CPU profiling data into a file")
	flag.Var(&zoomlevels, "zoom", "one or more zoom level of which the tiles will be rendered")
	flag.Parse()

	if len(zoomlevels) == 0 {
		log.Fatal("no zoom levels specified")
	}

	if len(*cpuProfile) != 0 {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	f, err := os.Open(*source)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()

	err = os.MkdirAll(*target, 0777)
	if err != nil {
		log.Fatal(err)
	}

	log.Println("parsing input...")
	fc := spatial.FeatureCollection{}

	if strings.HasSuffix(strings.ToLower(*source), "geojson") {
		if err := json.NewDecoder(f).Decode(&fc); err != nil {
			log.Fatal(err)
		}
	} else {
		fc.Features, err = cugdf.Unmarshal(f)
		if err != nil {
			log.Fatal(err)
		}
	}

	if len(fc.Features) == 0 {
		log.Fatal("no features in input file")
	}

	log.Printf("read %d features", len(fc.Features))

	var bboxPts []spatial.Point
	for _, feat := range fc.Features {
		bb := feat.Geometry.BBox()
		bboxPts = append(bboxPts, bb.SW, bb.NE)
	}

	bbox := spatial.Line(bboxPts).BBox()
	log.Println("filtering features...")

	// TODO: consider using rtree
	features := spatial.Filter(fc.Features, bbox)
	if len(features) == 0 {
		log.Println("no features to be processed, exiting.")
		os.Exit(2)
	}
	log.Printf("%d features to be processed", len(features))

	var tc []tile.ID
	for _, zoomlevel := range zoomlevels {
		tc = append(tc, tile.Coverage(bbox, zoomlevel)...)
	}
	log.Printf("attempting to generate %d tiles", len(tc))

	dtw := diskTileWriter{basedir: *target}
	dlm := defaultLayerMapper{defaultLayer: *defaultLayer}

	var (
		wg sync.WaitGroup
		ws = workerSlices(tc, *workersNumber)
	)
	for wrk := 0; wrk < len(ws); wrk++ {
		wg.Add(1)
		go func(i int) {
			generateTiles(ws[i], features, &dtw, &dlm)
			wg.Done()
		}(wrk)
	}
	wg.Wait()
}

func workerSlices(tiles []tile.ID, wrkNum int) [][]tile.ID {
	var r [][]tile.ID
	if len(tiles) <= wrkNum {
		for t := 0; t < len(tiles); t++ {
			r = append(r, []tile.ID{tiles[t]})
		}
		return r
	}
	for wrkr := 0; wrkr < wrkNum; wrkr++ {
		start := (len(tiles) / wrkNum) * wrkr
		end := (len(tiles) / wrkNum) * (wrkr + 1)
		if wrkr == wrkNum-1 {
			end = len(tiles)
		}
		r = append(r, tiles[start:end])
	}
	return r
}

type diskTileWriter struct {
	basedir string
}

func (tw *diskTileWriter) WriteTile(tID tile.ID, buf []byte) error {
	err := os.MkdirAll(filepath.Join(tw.basedir, strconv.Itoa(tID.Z), strconv.Itoa(tID.X)), 0777)
	if err != nil {
		return err
	}
	tf, err := os.Create(filepath.Join(tw.basedir, strconv.Itoa(tID.Z), strconv.Itoa(tID.X), strconv.Itoa(tID.Y)+".mvt"))
	if err != nil {
		return err
	}
	defer tf.Close()
	_, err = tf.Write(buf)
	if err != nil {
		return err
	}
	return nil
}

type defaultLayerMapper struct {
	defaultLayer bool
}

func (dlm *defaultLayerMapper) LayerName(props map[string]interface{}) string {
	if layerName, ok := props["@layer"]; ok {
		return layerName.(string)
	}
	if dlm.defaultLayer {
		return "default"
	}
	return ""
}

type layerMapper interface {
	LayerName(map[string]interface{}) string
}

type tileWriter interface {
	WriteTile(tile.ID, []byte) error
}

func generateTiles(tIDs []tile.ID, features []spatial.Feature, tw tileWriter, lm layerMapper) {
	for _, tID := range tIDs {
		log.Printf("Generating %s", tID)
		var layers = map[string][]spatial.Feature{}
		tileClipBBox := tID.BBox()
		for _, feat := range spatial.Filter(features, tileClipBBox) {
			for _, geom := range feat.Geometry.ClipToBBox(tileClipBBox) {
				feat.Geometry = geom
				ln := lm.LayerName(feat.Props)
				if len(ln) != 0 {
					if _, ok := layers[ln]; !ok {
						layers[ln] = []spatial.Feature{feat}
					} else {
						layers[ln] = append(layers[ln], feat)
					}
				}
			}
		}
		if !anyFeatures(layers) {
			continue
		}
		buf, err := mvt.EncodeTile(layers, tID)
		if err != nil {
			log.Fatal(err)
		}

		err = tw.WriteTile(tID, buf)
		if err != nil {
			log.Fatal(err)
		}
	}
}

func anyFeatures(layers map[string][]spatial.Feature) bool {
	for _, ly := range layers {
		if len(ly) > 0 {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"os"

	"path/filepath"

	cf_debug_server "code.cloudfoundry.org/debugserver"
	cf_lager "github.com/cloudfoundry-incubator/cf-lager"
	"github.com/cloudfoundry-incubator/volman/volhttp"
	"github.com/cloudfoundry-incubator/volman/vollocal"
	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
)

var atAddress = flag.String(
	"listenAddr",
	"0.0.0.0:8750",
	"host:port to serve volume management functions",
)

var driverPaths = flag.String(
	"volmanDriverPaths",
	"",
	"Path to the directory where drivers can be discovered.  Multiple paths may be specified using the OS-specific path separator; e.g. /path/to/somewhere:/path/to/somewhere-else",
)

func init() {
	// no command line parsing can happen here in go 1.6
}

func main() {
	parseCommandLine()
	withLogger, logTap := logger()
	defer withLogger.Info("ends")

	servers := createVolmanServer(withLogger, *atAddress, *driverPaths)

	if dbgAddr := cf_debug_server.DebugAddress(flag.CommandLine); dbgAddr != "" {
		servers = append(grouper.Members{
			{"debug-server", cf_debug_server.Runner(dbgAddr, logTap)},
		}, servers...)
	}
	process := ifrit.Invoke(processRunnerFor(servers))
	withLogger.Info("started")
	untilTerminated(withLogger, process)
}

func exitOnFailure(logger lager.Logger, err error) {
	if err != nil {
		logger.Error("Fatal err.. aborting", err)
		panic(err.Error())
	}
}

func untilTerminated(logger lager.Logger, process ifrit.Process) {
	err := <-process.Wait()
	exitOnFailure(logger, err)
}

func processRunnerFor(servers grouper.Members) ifrit.Runner {
	return sigmon.New(grouper.NewOrdered(os.Interrupt, servers))
}

func createVolmanServer(logger lager.Logger, atAddress string, driverPaths string) grouper.Members {
	if driverPaths == "" {
		panic("'-volmanDriverPaths' must be provided")
	}

	cfg := vollocal.NewDriverConfig()
	cfg.DriverPaths = filepath.SplitList(driverPaths)
	client, runner := vollocal.NewServer(logger, cfg)

	handler, err := volhttp.NewHandler(logger, client)
	exitOnFailure(logger, err)

	return grouper.Members{
		{"driver-syncer", runner},
		{"http-server", http_server.New(atAddress, handler)},
	}
}

func logger() (lager.Logger, *lager.ReconfigurableSink) {

	logger, reconfigurableSink := cf_lager.New("volman")
	return logger, reconfigurableSink
}

func parseCommandLine() {
	cf_lager.AddFlags(flag.CommandLine)
	cf_debug_server.AddFlags(flag.CommandLine)
	flag.Parse()
}
<commit_msg>Update and rename cf-lager -> cflager<commit_after>package main

import (
	"flag"
	"os"

	"path/filepath"

	cf_lager "code.cloudfoundry.org/cflager"
	cf_debug_server "code.cloudfoundry.org/debugserver"
	"github.com/cloudfoundry-incubator/volman/volhttp"
	"github.com/cloudfoundry-incubator/volman/vollocal"
	"github.com/pivotal-golang/lager"
	"github.com/tedsuo/ifrit"
	"github.com/tedsuo/ifrit/grouper"
	"github.com/tedsuo/ifrit/http_server"
	"github.com/tedsuo/ifrit/sigmon"
)

var atAddress = flag.String(
	"listenAddr",
	"0.0.0.0:8750",
	"host:port to serve volume management functions",
)

var driverPaths = flag.String(
	"volmanDriverPaths",
	"",
	"Path to the directory where drivers can be discovered.  Multiple paths may be specified using the OS-specific path separator; e.g. /path/to/somewhere:/path/to/somewhere-else",
)

func init() {
	// no command line parsing can happen here in go 1.6
}

func main() {
	parseCommandLine()
	withLogger, logTap := logger()
	defer withLogger.Info("ends")

	servers := createVolmanServer(withLogger, *atAddress, *driverPaths)

	if dbgAddr := cf_debug_server.DebugAddress(flag.CommandLine); dbgAddr != "" {
		servers = append(grouper.Members{
			{"debug-server", cf_debug_server.Runner(dbgAddr, logTap)},
		}, servers...)
	}
	process := ifrit.Invoke(processRunnerFor(servers))
	withLogger.Info("started")
	untilTerminated(withLogger, process)
}

func exitOnFailure(logger lager.Logger, err error) {
	if err != nil {
		logger.Error("Fatal err.. aborting", err)
		panic(err.Error())
	}
}

func untilTerminated(logger lager.Logger, process ifrit.Process) {
	err := <-process.Wait()
	exitOnFailure(logger, err)
}

func processRunnerFor(servers grouper.Members) ifrit.Runner {
	return sigmon.New(grouper.NewOrdered(os.Interrupt, servers))
}

func createVolmanServer(logger lager.Logger, atAddress string, driverPaths string) grouper.Members {
	if driverPaths == "" {
		panic("'-volmanDriverPaths' must be provided")
	}

	cfg := vollocal.NewDriverConfig()
	cfg.DriverPaths = filepath.SplitList(driverPaths)
	client, runner := vollocal.NewServer(logger, cfg)

	handler, err := volhttp.NewHandler(logger, client)
	exitOnFailure(logger, err)

	return grouper.Members{
		{"driver-syncer", runner},
		{"http-server", http_server.New(atAddress, handler)},
	}
}

func logger() (lager.Logger, *lager.ReconfigurableSink) {

	logger, reconfigurableSink := cf_lager.New("volman")
	return logger, reconfigurableSink
}

func parseCommandLine() {
	cf_lager.AddFlags(flag.CommandLine)
	cf_debug_server.AddFlags(flag.CommandLine)
	flag.Parse()
}
<|endoftext|>


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

//go:build !plan9
// +build !plan9

// id displays the user id, group id, and groups of the calling process.
//
// Synopsis:
//      id [-gGnu]
//
// Description:
//      id displays the uid, gid and groups of the calling process
//
// Options:
//	-g, --group     print only the effective group ID
//	-G, --groups    print all group IDs
//	-n, --name      print a name instead of a number, for -ugG
//	-u, --user      print only the effective user ID
//	-r, --user      print real ID instead of effective ID
package main

import (
	"flag"
	"fmt"
	"log"
	"strconv"
	"strings"
	"syscall"
)

var (
	GroupFile  = "/etc/group"
	PasswdFile = "/etc/passwd"

	flags struct {
		group  bool
		groups bool
		name   bool
		user   bool
		real   bool
	}
)

func correctFlags(flags ...bool) bool {
	n := 0
	for _, v := range flags {
		if v {
			n++
		}
	}
	return !(n > 1)
}

func init() {
	flag.BoolVar(&flags.group, "g", false, "print only the effective group ID")
	flag.BoolVar(&flags.groups, "G", false, "print all group IDs")
	flag.BoolVar(&flags.name, "n", false, "print a name instead of a number, for -ugG")
	flag.BoolVar(&flags.user, "u", false, "print only the effective user ID")
	flag.BoolVar(&flags.real, "r", false, "print real ID instead of effective ID")
}

type User struct {
	name   string
	uid    int
	gid    int
	groups map[int]string
}

func (u *User) UID() int {
	return u.uid
}

func (u *User) GID() int {
	return u.gid
}

func (u *User) Name() string {
	return u.name
}

func (u *User) Groups() map[int]string {
	return u.groups
}

func (u *User) GIDName() string {
	val := u.Groups()[u.UID()]
	return val
}

// NewUser is a factory method for the User type.
func NewUser(username string, users *Users, groups *Groups) (*User, error) {
	var groupsNumbers []int

	u := &User{groups: make(map[int]string)}
	if len(username) == 0 { // no username provided, get current
		if flags.real {
			u.uid = syscall.Geteuid()
			u.gid = syscall.Getegid()
		} else {
			u.uid = syscall.Getuid()
			u.gid = syscall.Getgid()
		}
		groupsNumbers, _ = syscall.Getgroups()
		if v, err := users.GetUser(u.uid); err == nil {
			u.name = v
		} else {
			u.name = strconv.Itoa(u.uid)
		}
	} else {
		if v, err := users.GetUID(username); err == nil { // user is username
			u.name = username
			u.uid = v
		} else {
			if uid, err := strconv.Atoi(username); err == nil { // user is valid int
				if v, err := users.GetUser(uid); err == nil { // user is valid uid
					u.name = v
					u.uid = uid
				}
			} else {
				return nil, fmt.Errorf("id: no such user or uid: %s", username)
			}
		}
		u.gid, _ = users.GetGID(u.uid)
		groupsNumbers = append([]int{u.gid}, groups.UserGetGIDs(u.name)...)
		// FIXME: not yet implemented group listing lookups
	}

	for _, groupNum := range groupsNumbers {
		if groupName, err := groups.GetGroup(groupNum); err == nil {
			u.groups[groupNum] = groupName
		} else {
			u.groups[groupNum] = strconv.Itoa(groupNum)
		}
	}

	return u, nil
}

// IDCommand runs the "id" with the current user's information.
func IDCommand(u User) {
	if !flags.groups {
		if flags.user {
			if flags.name {
				fmt.Println(u.Name())
				return
			}
			fmt.Println(u.UID())
			return
		} else if flags.group {
			if flags.name {
				fmt.Println(u.GIDName())
				return
			}
			fmt.Println(u.GID())
			return
		}

		fmt.Printf("uid=%d(%s) ", u.UID(), u.Name())
		fmt.Printf("gid=%d(%s) ", u.GID(), u.GIDName())
	}

	if !flags.groups {
		fmt.Print("groups=")
	}

	var groupOutput []string

	for gid, name := range u.Groups() {
		if !flags.groups {
			groupOutput = append(groupOutput, fmt.Sprintf("%d(%s)", gid, name))
		} else {
			if flags.name {
				groupOutput = append(groupOutput, fmt.Sprintf("%s ", name))
			} else {
				groupOutput = append(groupOutput, fmt.Sprintf("%d ", gid))
			}
		}
	}

	sep := ","
	if flags.groups {
		sep = ""
	}

	fmt.Println(strings.Join(groupOutput, sep))
}

func main() {
	flag.Parse()
	if !correctFlags(flags.groups, flags.group, flags.user) {
		log.Fatalf("id: cannot print \"only\" of more than one choice")
	}
	if flags.name && !(flags.groups || flags.group || flags.user) {
		log.Fatalf("id: cannot print only names in default format")
	}
	if len(flag.Arg(0)) != 0 && flags.real {
		log.Fatalf("id: cannot print only names or real IDs in default format")
	}

	users, err := NewUsers(PasswdFile)
	if err != nil {
		log.Printf("id: unable to read %s: %v", PasswdFile, err)
	}
	groups, err := NewGroups(GroupFile)
	if err != nil {
		log.Printf("id: unable to read %s: %v", PasswdFile, err)
	}

	user, err := NewUser(flag.Arg(0), users, groups)
	if err != nil {
		log.Fatalf("id: %s", err)
	}

	IDCommand(*user)
}
<commit_msg>cmds/core/id: remove logging of password file name This makes codeql happy.<commit_after>// Copyright 2017-2018 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build !plan9
// +build !plan9

// id displays the user id, group id, and groups of the calling process.
//
// Synopsis:
//      id [-gGnu]
//
// Description:
//      id displays the uid, gid and groups of the calling process
//
// Options:
//	-g, --group     print only the effective group ID
//	-G, --groups    print all group IDs
//	-n, --name      print a name instead of a number, for -ugG
//	-u, --user      print only the effective user ID
//	-r, --user      print real ID instead of effective ID
package main

import (
	"flag"
	"fmt"
	"log"
	"strconv"
	"strings"
	"syscall"
)

var (
	GroupFile  = "/etc/group"
	PasswdFile = "/etc/passwd"

	flags struct {
		group  bool
		groups bool
		name   bool
		user   bool
		real   bool
	}
)

func correctFlags(flags ...bool) bool {
	n := 0
	for _, v := range flags {
		if v {
			n++
		}
	}
	return !(n > 1)
}

func init() {
	flag.BoolVar(&flags.group, "g", false, "print only the effective group ID")
	flag.BoolVar(&flags.groups, "G", false, "print all group IDs")
	flag.BoolVar(&flags.name, "n", false, "print a name instead of a number, for -ugG")
	flag.BoolVar(&flags.user, "u", false, "print only the effective user ID")
	flag.BoolVar(&flags.real, "r", false, "print real ID instead of effective ID")
}

type User struct {
	name   string
	uid    int
	gid    int
	groups map[int]string
}

func (u *User) UID() int {
	return u.uid
}

func (u *User) GID() int {
	return u.gid
}

func (u *User) Name() string {
	return u.name
}

func (u *User) Groups() map[int]string {
	return u.groups
}

func (u *User) GIDName() string {
	val := u.Groups()[u.UID()]
	return val
}

// NewUser is a factory method for the User type.
func NewUser(username string, users *Users, groups *Groups) (*User, error) {
	var groupsNumbers []int

	u := &User{groups: make(map[int]string)}
	if len(username) == 0 { // no username provided, get current
		if flags.real {
			u.uid = syscall.Geteuid()
			u.gid = syscall.Getegid()
		} else {
			u.uid = syscall.Getuid()
			u.gid = syscall.Getgid()
		}
		groupsNumbers, _ = syscall.Getgroups()
		if v, err := users.GetUser(u.uid); err == nil {
			u.name = v
		} else {
			u.name = strconv.Itoa(u.uid)
		}
	} else {
		if v, err := users.GetUID(username); err == nil { // user is username
			u.name = username
			u.uid = v
		} else {
			if uid, err := strconv.Atoi(username); err == nil { // user is valid int
				if v, err := users.GetUser(uid); err == nil { // user is valid uid
					u.name = v
					u.uid = uid
				}
			} else {
				return nil, fmt.Errorf("id: no such user or uid: %s", username)
			}
		}
		u.gid, _ = users.GetGID(u.uid)
		groupsNumbers = append([]int{u.gid}, groups.UserGetGIDs(u.name)...)
		// FIXME: not yet implemented group listing lookups
	}

	for _, groupNum := range groupsNumbers {
		if groupName, err := groups.GetGroup(groupNum); err == nil {
			u.groups[groupNum] = groupName
		} else {
			u.groups[groupNum] = strconv.Itoa(groupNum)
		}
	}

	return u, nil
}

// IDCommand runs the "id" with the current user's information.
func IDCommand(u User) {
	if !flags.groups {
		if flags.user {
			if flags.name {
				fmt.Println(u.Name())
				return
			}
			fmt.Println(u.UID())
			return
		} else if flags.group {
			if flags.name {
				fmt.Println(u.GIDName())
				return
			}
			fmt.Println(u.GID())
			return
		}

		fmt.Printf("uid=%d(%s) ", u.UID(), u.Name())
		fmt.Printf("gid=%d(%s) ", u.GID(), u.GIDName())
	}

	if !flags.groups {
		fmt.Print("groups=")
	}

	var groupOutput []string

	for gid, name := range u.Groups() {
		if !flags.groups {
			groupOutput = append(groupOutput, fmt.Sprintf("%d(%s)", gid, name))
		} else {
			if flags.name {
				groupOutput = append(groupOutput, fmt.Sprintf("%s ", name))
			} else {
				groupOutput = append(groupOutput, fmt.Sprintf("%d ", gid))
			}
		}
	}

	sep := ","
	if flags.groups {
		sep = ""
	}

	fmt.Println(strings.Join(groupOutput, sep))
}

func main() {
	flag.Parse()
	if !correctFlags(flags.groups, flags.group, flags.user) {
		log.Fatalf("id: cannot print \"only\" of more than one choice")
	}
	if flags.name && !(flags.groups || flags.group || flags.user) {
		log.Fatalf("id: cannot print only names in default format")
	}
	if len(flag.Arg(0)) != 0 && flags.real {
		log.Fatalf("id: cannot print only names or real IDs in default format")
	}

	users, err := NewUsers(PasswdFile)
	if err != nil {
		log.Printf("id: %v", err)
	}
	groups, err := NewGroups(GroupFile)
	if err != nil {
		log.Printf("id: %v", err)
	}

	user, err := NewUser(flag.Arg(0), users, groups)
	if err != nil {
		log.Fatalf("id: %s", err)
	}

	IDCommand(*user)
}
<|endoftext|>


<commit_before>package location

import (
	"fmt"
	log "github.com/mailgun/gotools-log"
	timetools "github.com/mailgun/gotools-time"
	. "github.com/mailgun/vulcan/callback"
	. "github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/failover"
	"github.com/mailgun/vulcan/headers"
	. "github.com/mailgun/vulcan/limit"
	. "github.com/mailgun/vulcan/loadbalance"
	"github.com/mailgun/vulcan/netutils"
	. "github.com/mailgun/vulcan/request"
	"net"
	"net/http"
	"os"
	"strings"
	"time"
)

type SleepFn func(time.Duration)

// Location with built in failover and load balancing support
type HttpLocation struct {
	transport *http.Transport
	settings  HttpLocationSettings
}

type HttpLocationSettings struct {
	Timeouts struct {
		Read time.Duration // Socket read timeout (before we receive the first reply header)
		Dial time.Duration // Socket connect timeout
	}
	ShouldFailover failover.Predicate // Predicate that defines when requests are allowed to failover
	LoadBalancer   LoadBalancer       // Load balancing algorithm
	Limiter        Limiter            // Rate limiting algorithm
	// Before callback executed before request gets routed to the endpoint
	// and can intervene during the request lifetime
	Before Before
	// Callback executed after proxy received response from the endpoint
	After After
	// Used to set forwarding headers
	Hostname string
	// In this case appends new forward info to the existing header
	TrustForwardHeader bool
	// Option to override sleep function (useful for testing purposes)
	SleepFn SleepFn
	// Time provider (useful for testing purposes)
	TimeProvider timetools.TimeProvider
}

func NewHttpLocation(s HttpLocationSettings) (*HttpLocation, error) {
	s, err := parseSettings(s)
	if err != nil {
		return nil, err
	}
	return &HttpLocation{
		transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				return net.DialTimeout(network, addr, s.Timeouts.Dial)
			},
			ResponseHeaderTimeout: s.Timeouts.Read,
		},
		settings: s,
	}, nil
}

// Round trips the request to one of the endpoints, returns the streamed
// request body length in bytes and the endpoint reply.
func (l *HttpLocation) RoundTrip(req Request) (*http.Response, error) {
	for {
		_, err := req.GetBody().Seek(0, 0)
		if err != nil {
			return nil, err
		}

		endpoint, err := l.settings.LoadBalancer.NextEndpoint(req)
		if err != nil {
			log.Errorf("Load Balancer failure: %s", err)
			return nil, err
		}

		// Rewrites the request: adds headers, changes urls
		newRequest := l.rewriteRequest(req.GetHttpRequest(), endpoint)
		log.Infof("Proxy to endpoint: %s", endpoint)

		if l.settings.Limiter != nil {
			delay, err := l.settings.Limiter.Limit(req)
			if err != nil {
				log.Errorf("Limiter rejects request: %s", err)
				return nil, err
			}
			if delay > 0 {
				log.Infof("Limiter delays request by %s", delay)
				l.settings.SleepFn(delay)
			}
		}

		// In case if error is not nil, we allow load balancer to choose the next endpoint
		// e.g. to do request failover. Nil error means that we got proxied the request successfully.
		response, err := l.proxyToEndpoint(req, newRequest)
		if err == nil {
			return response, err
		}
	}
	log.Errorf("All endpoints failed!")
	return nil, fmt.Errorf("All endpoints failed")
}

// Proxy the request to the given endpoint, in case if endpoint is down
// or failover code sequence has been recorded as the reply, return the error.
// Failover sequence - is a special response code from the endpoint that indicates
// that endpoint is shutting down and is not willing to accept new requests.
func (l *HttpLocation) proxyToEndpoint(req Request, httpReq *http.Request) (*http.Response, error) {

	before := []Before{l.settings.Before, l.settings.LoadBalancer, l.settings.Limiter}
	for _, cb := range before {
		if cb != nil {
			response, err := cb.Before(req)
			// In case if error is not nil, return this error to the client
			// and interrupt the callback chain
			if err != nil {
				log.Errorf("Callback says error: %s", err)
				return nil, err
			}
			// If response is present that means that callback wants to proxy
			// this response to the client
			if response != nil {
				return response, nil
			}
		}
	}

	// Forward the reuest and mirror the response
	res, err := l.transport.RoundTrip(httpReq)
	if err != nil {
		return nil, err
	}

	// This gives a chance for callbacks to change the response
	after := []After{l.settings.After, l.settings.LoadBalancer, l.settings.Limiter}
	for _, cb := range after {
		if cb != nil {
			err := cb.After(req, res, err)
			if err != nil {
				log.Errorf("After returned error and intercepts the response: %s", err)
				return nil, err
			}
		}
	}
	return res, nil
}

// This function alters the original request - adds/removes headers, removes hop headers, changes the request path.
func (l *HttpLocation) rewriteRequest(req *http.Request, endpoint Endpoint) *http.Request {
	outReq := new(http.Request)
	*outReq = *req // includes shallow copies of maps, but we handle this below

	outReq.URL.Scheme = endpoint.GetUrl().Scheme
	outReq.URL.Host = endpoint.GetUrl().Host
	outReq.URL.RawQuery = req.URL.RawQuery

	outReq.Proto = "HTTP/1.1"
	outReq.ProtoMajor = 1
	outReq.ProtoMinor = 1
	outReq.Close = false

	log.Infof("Proxying request to: %v", outReq)

	outReq.Header = make(http.Header)
	netutils.CopyHeaders(outReq.Header, req.Header)

	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		if l.settings.TrustForwardHeader {
			if prior, ok := outReq.Header[headers.XForwardedFor]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
		}
		outReq.Header.Set(headers.XForwardedFor, clientIP)
	}
	if req.TLS != nil {
		outReq.Header.Set(headers.XForwardedProto, "https")
	} else {
		outReq.Header.Set(headers.XForwardedProto, "http")
	}
	if req.Host != "" {
		outReq.Header.Set(headers.XForwardedHost, req.Host)
	}
	outReq.Header.Set(headers.XForwardedServer, l.settings.Hostname)

	// Remove hop-by-hop headers to the backend.  Especially
	// important is "Connection" because we want a persistent
	// connection, regardless of what the client sent to us.
	netutils.RemoveHeaders(headers.HopHeaders, outReq.Header)
	return outReq
}

// Standard dial and read timeouts, can be overriden when supplying location
const (
	DefaultHttpReadTimeout = time.Duration(10) * time.Second
	DefaultHttpDialTimeout = time.Duration(10) * time.Second
)

func parseSettings(s HttpLocationSettings) (HttpLocationSettings, error) {
	if s.Timeouts.Read <= time.Duration(0) {
		s.Timeouts.Read = DefaultHttpReadTimeout
	}
	if s.Timeouts.Dial <= time.Duration(0) {
		s.Timeouts.Dial = DefaultHttpDialTimeout
	}
	if s.LoadBalancer == nil {
		return s, fmt.Errorf("Provide load balancer")
	}
	if s.SleepFn == nil {
		s.SleepFn = time.Sleep
	}
	if s.Hostname == "" {
		h, err := os.Hostname()
		if err != nil {
			s.Hostname = h
		}
	}
	if s.TimeProvider == nil {
		s.TimeProvider = &timetools.RealTime{}
	}
	if s.ShouldFailover == nil {
		// Failover on erros for 2 times maximum on GET requests only.
		s.ShouldFailover = failover.And(failover.MaxAttempts(2), failover.OnErrors, failover.OnGets)
	}
	return s, nil
}
<commit_msg>Fixes<commit_after>package location

import (
	"fmt"
	log "github.com/mailgun/gotools-log"
	timetools "github.com/mailgun/gotools-time"
	. "github.com/mailgun/vulcan/callback"
	. "github.com/mailgun/vulcan/endpoint"
	"github.com/mailgun/vulcan/failover"
	"github.com/mailgun/vulcan/headers"
	. "github.com/mailgun/vulcan/limit"
	. "github.com/mailgun/vulcan/loadbalance"
	"github.com/mailgun/vulcan/netutils"
	. "github.com/mailgun/vulcan/request"
	"net"
	"net/http"
	"os"
	"strings"
	"time"
)

type SleepFn func(time.Duration)

// Location with built in failover and load balancing support
type HttpLocation struct {
	transport *http.Transport
	settings  HttpLocationSettings
}

type HttpLocationSettings struct {
	Timeouts struct {
		Read time.Duration // Socket read timeout (before we receive the first reply header)
		Dial time.Duration // Socket connect timeout
	}
	ShouldFailover failover.Predicate // Predicate that defines when requests are allowed to failover
	LoadBalancer   LoadBalancer       // Load balancing algorithm
	Limiter        Limiter            // Rate limiting algorithm
	// Before callback executed before request gets routed to the endpoint
	// and can intervene during the request lifetime
	Before Before
	// Callback executed after proxy received response from the endpoint
	After After
	// Used to set forwarding headers
	Hostname string
	// In this case appends new forward info to the existing header
	TrustForwardHeader bool
	// Option to override sleep function (useful for testing purposes)
	SleepFn SleepFn
	// Time provider (useful for testing purposes)
	TimeProvider timetools.TimeProvider
}

func NewHttpLocation(s HttpLocationSettings) (*HttpLocation, error) {
	s, err := parseSettings(s)
	if err != nil {
		return nil, err
	}
	return &HttpLocation{
		transport: &http.Transport{
			Dial: func(network, addr string) (net.Conn, error) {
				return net.DialTimeout(network, addr, s.Timeouts.Dial)
			},
			ResponseHeaderTimeout: s.Timeouts.Read,
		},
		settings: s,
	}, nil
}

// Round trips the request to one of the endpoints, returns the streamed
// request body length in bytes and the endpoint reply.
func (l *HttpLocation) RoundTrip(req Request) (*http.Response, error) {
	for {
		_, err := req.GetBody().Seek(0, 0)
		if err != nil {
			return nil, err
		}

		endpoint, err := l.settings.LoadBalancer.NextEndpoint(req)
		if err != nil {
			log.Errorf("Load Balancer failure: %s", err)
			return nil, err
		}

		// Rewrites the request: adds headers, changes urls
		newRequest := l.rewriteRequest(req.GetHttpRequest(), endpoint)
		log.Infof("Proxy to endpoint: %s", endpoint)

		if l.settings.Limiter != nil {
			delay, err := l.settings.Limiter.Limit(req)
			if err != nil {
				log.Errorf("Limiter rejects request: %s", err)
				return nil, err
			}
			if delay > 0 {
				log.Infof("Limiter delays request by %s", delay)
				l.settings.SleepFn(delay)
			}
		}

		// In case if error is not nil, we allow load balancer to choose the next endpoint
		// e.g. to do request failover. Nil error means that we got proxied the request successfully.
		response, err := l.proxyToEndpoint(endpoint, req, newRequest)
		if err == nil {
			return response, err
		}
	}
	log.Errorf("All endpoints failed!")
	return nil, fmt.Errorf("All endpoints failed")
}

// Proxy the request to the given endpoint, in case if endpoint is down
// or failover code sequence has been recorded as the reply, return the error.
// Failover sequence - is a special response code from the endpoint that indicates
// that endpoint is shutting down and is not willing to accept new requests.
func (l *HttpLocation) proxyToEndpoint(endpoint Endpoint, req Request, httpReq *http.Request) (*http.Response, error) {

	before := []Before{l.settings.Before, l.settings.LoadBalancer, l.settings.Limiter}
	for _, cb := range before {
		if cb != nil {
			response, err := cb.Before(req)
			// In case if error is not nil, return this error to the client
			// and interrupt the callback chain
			if err != nil {
				log.Errorf("Callback says error: %s", err)
				return nil, err
			}
			// If response is present that means that callback wants to proxy
			// this response to the client
			if response != nil {
				return response, nil
			}
		}
	}

	// Forward the reuest and mirror the response
	start := l.settings.TimeProvider.UtcNow()
	res, err := l.transport.RoundTrip(httpReq)
	diff := l.settings.TimeProvider.UtcNow().Sub(start)

	// Record attempt
	req.AddAttempt(&BaseAttempt{Endpoint: endpoint, Duration: diff, Response: res, Error: err})
	// Return the error in case if there's no response
	if err != nil {
		return nil, err
	}

	// This gives a chance for callbacks to change the response
	after := []After{l.settings.After, l.settings.LoadBalancer, l.settings.Limiter}
	for _, cb := range after {
		if cb != nil {
			err := cb.After(req)
			if err != nil {
				log.Errorf("After returned error and intercepts the response: %s", err)
				return nil, err
			}
		}
	}
	return res, nil
}

// This function alters the original request - adds/removes headers, removes hop headers, changes the request path.
func (l *HttpLocation) rewriteRequest(req *http.Request, endpoint Endpoint) *http.Request {
	outReq := new(http.Request)
	*outReq = *req // includes shallow copies of maps, but we handle this below

	outReq.URL.Scheme = endpoint.GetUrl().Scheme
	outReq.URL.Host = endpoint.GetUrl().Host
	outReq.URL.RawQuery = req.URL.RawQuery

	outReq.Proto = "HTTP/1.1"
	outReq.ProtoMajor = 1
	outReq.ProtoMinor = 1
	outReq.Close = false

	log.Infof("Proxying request to: %v", outReq)

	outReq.Header = make(http.Header)
	netutils.CopyHeaders(outReq.Header, req.Header)

	if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
		if l.settings.TrustForwardHeader {
			if prior, ok := outReq.Header[headers.XForwardedFor]; ok {
				clientIP = strings.Join(prior, ", ") + ", " + clientIP
			}
		}
		outReq.Header.Set(headers.XForwardedFor, clientIP)
	}
	if req.TLS != nil {
		outReq.Header.Set(headers.XForwardedProto, "https")
	} else {
		outReq.Header.Set(headers.XForwardedProto, "http")
	}
	if req.Host != "" {
		outReq.Header.Set(headers.XForwardedHost, req.Host)
	}
	outReq.Header.Set(headers.XForwardedServer, l.settings.Hostname)

	// Remove hop-by-hop headers to the backend.  Especially
	// important is "Connection" because we want a persistent
	// connection, regardless of what the client sent to us.
	netutils.RemoveHeaders(headers.HopHeaders, outReq.Header)
	return outReq
}

// Standard dial and read timeouts, can be overriden when supplying location
const (
	DefaultHttpReadTimeout = time.Duration(10) * time.Second
	DefaultHttpDialTimeout = time.Duration(10) * time.Second
)

func parseSettings(s HttpLocationSettings) (HttpLocationSettings, error) {
	if s.Timeouts.Read <= time.Duration(0) {
		s.Timeouts.Read = DefaultHttpReadTimeout
	}
	if s.Timeouts.Dial <= time.Duration(0) {
		s.Timeouts.Dial = DefaultHttpDialTimeout
	}
	if s.LoadBalancer == nil {
		return s, fmt.Errorf("Provide load balancer")
	}
	if s.SleepFn == nil {
		s.SleepFn = time.Sleep
	}
	if s.Hostname == "" {
		h, err := os.Hostname()
		if err != nil {
			s.Hostname = h
		}
	}
	if s.TimeProvider == nil {
		s.TimeProvider = &timetools.RealTime{}
	}
	if s.ShouldFailover == nil {
		// Failover on erros for 2 times maximum on GET requests only.
		s.ShouldFailover = failover.And(failover.MaxAttempts(2), failover.OnErrors, failover.OnGets)
	}
	return s, nil
}
<|endoftext|>


<commit_before>package gore

import (
	"errors"
	"math"
	"regexp"
	"strings"
	"sync"
	"time"
)

// Sentinel is a special Redis process that monitors other Redis instances,
// does fail-over, notifies client status of all monitored instances.
type Sentinel struct {
	servers   []string
	conn      *Conn
	subConn   *Conn // A dedicated connection for pubsub
	subs      *Subscriptions
	mutex     *sync.Mutex
	state     int
	instances map[string]*instance
}

// NewSentinel returns new Sentinel
func NewSentinel() *Sentinel {
	return &Sentinel{
		mutex:     &sync.Mutex{},
		state:     connStateNotConnected,
		instances: make(map[string]*instance),
	}
}

// AddServer adds new sentinel servers. Only one sentinel server is active
// at any time. If this server fails, gore will connect to other sentinel
// servers immediately.
//
// AddServer can be called at anytime, to add new server on the fly.
// In production environment, you should always have at least 3 sentinel
// servers up and running.
func (s *Sentinel) AddServer(addresses ...string) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.servers = append(s.servers, addresses...)
}

// Dial connects to one sentinel server in the list. If it fails to connect,
// it moves to the next on the list. If all servers cannot be connected,
// Init return error.
func (s *Sentinel) Dial() (err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.state != connStateNotConnected {
		return nil
	}
	return s.connect()
}

// Close gracefully closes the sentinel and all monitored connections
func (s *Sentinel) Close() {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.close()
}

// Ping pings all sentinel server, and returns error if it does not
// receive pong. This method can be called before Dial()
func (s *Sentinel) Ping() error {
	if len(s.servers) == 0 {
		return ErrNotConnected
	}
	conns := []*Conn{}
	defer func() {
		for _, conn := range conns {
			conn.Close()
		}
	}()
	for _, addr := range s.servers {
		conn, err := Dial(addr)
		if err != nil {
			return err
		}
		conns = append(conns, conn)
	}
	cmd := NewCommand("PING")
	for _, conn := range conns {
		_, err := cmd.Run(conn)
		if err != nil {
			return err
		}
	}
	return nil
}

// AddInstance registers a Redis instance with all sentinels server.
// This method can be called before Dial(). The quorum number is
// determined by the number of sentinel servers. If this method returns
// error, this may leave the sentinels data not synchronized.
func (s *Sentinel) AddInstance(name, host, port string) (err error) {
	if len(s.servers) == 0 {
		return ErrNotConnected
	}
	conns := []*Conn{}
	defer func() {
		for _, conn := range conns {
			conn.Close()
		}
	}()
	for _, addr := range s.servers {
		conn, err := Dial(addr)
		if err != nil {
			return err
		}
		conns = append(conns, conn)
	}
	quorum := math.Ceil(float64(len(s.servers)) / 2)
	cmd := NewCommand("SENTINEL", "MONITOR", name, host, port, quorum)
	for _, conn := range conns {
		rep, err := cmd.Run(conn)
		if err != nil {
			return err
		}
		if !rep.IsOk() {
			errMessage, _ := rep.Error()
			return errors.New(errMessage)
		}
	}
	return nil
}

// RemoveInstance removes a Redis instance from all sentinels server.
// Like AddInstance, this method can be called before Dial()
func (s *Sentinel) RemoveInstance(name string) error {
	if len(s.servers) == 0 {
		return ErrNotConnected
	}
	conns := []*Conn{}
	defer func() {
		for _, conn := range conns {
			conn.Close()
		}
	}()
	for _, addr := range s.servers {
		conn, err := Dial(addr)
		if err != nil {
			return err
		}
		conns = append(conns, conn)
	}
	cmd := NewCommand("SENTINEL", "REMOVE", name)
	for _, conn := range conns {
		_, err := cmd.Run(conn)
		if err != nil {
			return err
		}
	}
	return nil
}

// SetOption sets option for a Redis instance
// This method can be called before Dial()
func (s *Sentinel) SetOption(name, option string, value interface{}) error {
	if len(s.servers) == 0 {
		return ErrNotConnected
	}
	conns := []*Conn{}
	defer func() {
		for _, conn := range conns {
			conn.Close()
		}
	}()
	for _, addr := range s.servers {
		conn, err := Dial(addr)
		if err != nil {
			return err
		}
		conns = append(conns, conn)
	}
	cmd := NewCommand("SENTINEL", "SET", name, option, value)
	for _, conn := range conns {
		rep, err := cmd.Run(conn)
		if err != nil {
			return err
		}
		if !rep.IsOk() {
			errMessage, _ := rep.Error()
			return errors.New(errMessage)
		}
	}
	return nil
}

// GetPool returns a pool of connection from a pool name.
// If the pool has not been retrieved before, gore will attempt to
// fetch the address from the sentinel server, and initialize connections
// with this address. The application should never call this function repeatedly
// to get the same pool, because internal locking can cause performance to drop.
// An error can be returned if the pool name is not monitored by the sentinel,
// or the redis server is currently dead, or the redis server cannot be connected
// (for example: firewall issues).
func (s *Sentinel) GetPool(name string) (*Pool, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if ins, ok := s.instances[name]; ok {
		return ins.pool, nil
	}
	rep, err := NewCommand("SENTINEL", "master", name).Run(s.conn)
	if err != nil {
		return nil, err
	}
	if rep.IsError() {
		return nil, ErrNil
	}
	master, err := rep.Map()
	if err != nil {
		return nil, err
	}
	flags := strings.Split(master["flags"], ",")
	for _, flag := range flags {
		if flag == "s_down" || flag == "o_down" {
			return nil, ErrNotConnected
		}
	}
	ins := &instance{
		name:    name,
		address: master["ip"] + ":" + master["port"],
		state:   connStateConnected,
		pool:    &Pool{sentinel: true},
	}
	err = ins.pool.Dial(ins.address)
	if err != nil {
		return nil, err
	}
	s.instances[name] = ins
	return ins.pool, nil
}

// GetCluster return a cluster monitored by the sentinel.
// The name of the cluster will determine name of Redis instances.
// For example, if the cluster name is "mycluster", the instances' name
// maybe "mycluster1", "mycluster2", ...
func (s *Sentinel) GetCluster(name string) (c *Cluster, err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	rep, err := NewCommand("SENTINEL", "masters").Run(s.conn)
	if err != nil {
		return nil, err
	}
	replies, err := rep.Array()
	if err != nil {
		return nil, err
	}
	if len(replies) == 0 {
		return nil, ErrNoShard
	}
	instances := make(map[string]*instance)
	defer func() {
		if err != nil {
			for _, ins := range instances {
				ins.pool.Close()
			}
		}
	}()
	for _, r := range replies {
		master, err := r.Map()
		if err != nil {
			return nil, err
		}
		suffix := strings.TrimPrefix(master["name"], name)
		if !suffixRegex.MatchString(suffix) {
			continue
		}
		ins := &instance{
			name:    master["name"],
			address: master["ip"] + ":" + master["port"],
			state:   connStateConnected,
			pool:    &Pool{sentinel: true},
		}
		err = ins.pool.Dial(ins.address)
		if err != nil {
			return nil, err
		}
		instances[ins.name] = ins
	}
	c = NewCluster()
	c.sentinel = true
	for _, ins := range instances {
		s.instances[ins.name] = ins
		c.addresses = append(c.addresses, ins.address)
		c.shards = append(c.shards, ins.pool)
	}
	return c, nil
}

var suffixRegex = regexp.MustCompile("^\\d+$")

func (s *Sentinel) connect() (err error) {
	for i, server := range s.servers {
		s.conn, err = DialTimeout(server, time.Duration(Config.ConnectTimeout)*time.Second)
		if err != nil {
			continue
		}
		s.subConn, err = DialTimeout(server, time.Duration(Config.ConnectTimeout)*time.Second)
		if err != nil {
			s.conn.Close()
			continue
		}
		s.state = connStateConnected
		s.subs = NewSubscriptions(s.subConn)
		s.subs.throwError = true
		err = s.subs.Subscribe("+sdown", "-sdown", "+odown", "-odown", "+switch-master")
		if err != nil {
			s.close()
			continue
		}
		s.servers = append(s.servers[0:i], s.servers[i+1:]...)
		s.servers = append(s.servers, server)
		go s.monitor()
		return nil
	}
	return ErrNotConnected
}

func (s *Sentinel) close() {
	s.state = connStateNotConnected
	s.subs.Close()
	s.subConn.Close()
	s.conn.Close()
	for _, ins := range s.instances {
		ins.pool.Close()
	}
	s.instances = make(map[string]*instance)
}

func (s *Sentinel) fail() {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.state == connStateConnected {
		s.state = connStateNotConnected
		s.reconnect()
	}
}

func (s *Sentinel) reconnect() {
	s.subs.Close()
	s.subConn.Close()
	s.conn.Close()
	sleepTime := Config.ReconnectTime
	for {
		err := s.connect()
		if err == nil {
			break
		}
		time.Sleep(time.Duration(sleepTime) * time.Second)
		if sleepTime < 30 {
			sleepTime += 2
		}
	}
}

func (s *Sentinel) monitor() {
	for message := range s.subs.Message() {
		if message == nil {
			s.fail()
			return
		}
		s.mutex.Lock()
		ins := s.getInstanceFromMessage(message)
		if ins == nil {
			s.mutex.Unlock()
			continue
		}
		if message.Channel == "+sdown" || message.Channel == "+odown" {
			ins.down(message)
		} else if message.Channel == "-sdown" || message.Channel == "-odown" {
			ins.up(message)
		} else if message.Channel == "+switch-master" {
			ins.switchMaster(s)
		}
		s.mutex.Unlock()
	}
}

func (s *Sentinel) getInstanceFromMessage(message *Message) *instance {
	if message.Channel == "+sdown" || message.Channel == "+odown" ||
		message.Channel == "-sdown" || message.Channel == "-odown" {
		pieces := strings.Split(string(message.Message), " ")
		if len(pieces) < 2 || pieces[0] != "master" {
			return nil
		}
		return s.instances[pieces[1]]
	} else if message.Channel == "+switch-master" {
		pieces := strings.Split(string(message.Message), " ")
		if len(pieces) < 1 {
			return nil
		}
		return s.instances[pieces[0]]
	}
	return nil
}

func (s *Sentinel) getInstanceAddress(name string) string {
	for {
		rep, err := NewCommand("SENTINEL", "get-master-addr-by-name", name).Run(s.conn)
		if err != nil {
			time.Sleep(2 * time.Second)
			continue
		}
		if !rep.IsArray() {
			return ""
		}
		result := []string{}
		rep.Slice(&result)
		if len(result) != 2 {
			return ""
		}
		return result[0] + ":" + result[1]
	}
}

type instance struct {
	name    string
	address string
	sdown   bool
	odown   bool
	pool    *Pool
	state   int
}

func (ins *instance) down(message *Message) {
	if ins.state != connStateConnected {
		return
	}
	ins.state = connStateNotConnected
	if message.Channel == "+sdown" {
		ins.sdown = true
	} else if message.Channel == "+odown" {
		ins.odown = true
	}
	ins.pool.sentinelGonnaLetYouDown()
}

func (ins *instance) up(message *Message) {
	if ins.state == connStateConnected {
		return
	}
	if message.Channel == "-sdown" {
		ins.sdown = false
	} else if message.Channel == "-odown" {
		ins.odown = false
	}
	if !ins.sdown && !ins.odown {
		ins.pool.sentinelGonnaGiveYouUp()
		ins.state = connStateConnected
	}
}

func (ins *instance) switchMaster(s *Sentinel) {
	address := s.getInstanceAddress(ins.name)
	if address == "" {
		// WTF
		return
	}
	if ins.state == connStateConnected {
		ins.pool.sentinelGonnaLetYouDown()
	}
	ins.pool.address = address
	ins.pool.sentinelGonnaGiveYouUp()
	ins.state = connStateConnected
}
<commit_msg>Revert sentinel utils<commit_after>package gore

import (
	"regexp"
	"strings"
	"sync"
	"time"
)

// Sentinel is a special Redis process that monitors other Redis instances,
// does fail-over, notifies client status of all monitored instances.
type Sentinel struct {
	servers   []string
	conn      *Conn
	subConn   *Conn // A dedicated connection for pubsub
	subs      *Subscriptions
	mutex     *sync.Mutex
	state     int
	instances map[string]*instance
}

// NewSentinel returns new Sentinel
func NewSentinel() *Sentinel {
	return &Sentinel{
		mutex:     &sync.Mutex{},
		state:     connStateNotConnected,
		instances: make(map[string]*instance),
	}
}

// AddServer adds new sentinel servers. Only one sentinel server is active
// at any time. If this server fails, gore will connect to other sentinel
// servers immediately.
//
// AddServer can be called at anytime, to add new server on the fly.
// In production environment, you should always have at least 3 sentinel
// servers up and running.
func (s *Sentinel) AddServer(addresses ...string) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.servers = append(s.servers, addresses...)
}

// Dial connects to one sentinel server in the list. If it fails to connect,
// it moves to the next on the list. If all servers cannot be connected,
// Init return error.
func (s *Sentinel) Dial() (err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.state != connStateNotConnected {
		return nil
	}
	return s.connect()
}

// Close gracefully closes the sentinel and all monitored connections
func (s *Sentinel) Close() {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	s.close()
}

// GetPool returns a pool of connection from a pool name.
// If the pool has not been retrieved before, gore will attempt to
// fetch the address from the sentinel server, and initialize connections
// with this address. The application should never call this function repeatedly
// to get the same pool, because internal locking can cause performance to drop.
// An error can be returned if the pool name is not monitored by the sentinel,
// or the redis server is currently dead, or the redis server cannot be connected
// (for example: firewall issues).
func (s *Sentinel) GetPool(name string) (*Pool, error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if ins, ok := s.instances[name]; ok {
		return ins.pool, nil
	}
	rep, err := NewCommand("SENTINEL", "master", name).Run(s.conn)
	if err != nil {
		return nil, err
	}
	if rep.IsError() {
		return nil, ErrNil
	}
	master, err := rep.Map()
	if err != nil {
		return nil, err
	}
	flags := strings.Split(master["flags"], ",")
	for _, flag := range flags {
		if flag == "s_down" || flag == "o_down" {
			return nil, ErrNotConnected
		}
	}
	ins := &instance{
		name:    name,
		address: master["ip"] + ":" + master["port"],
		state:   connStateConnected,
		pool:    &Pool{sentinel: true},
	}
	err = ins.pool.Dial(ins.address)
	if err != nil {
		return nil, err
	}
	s.instances[name] = ins
	return ins.pool, nil
}

// GetCluster return a cluster monitored by the sentinel.
// The name of the cluster will determine name of Redis instances.
// For example, if the cluster name is "mycluster", the instances' name
// maybe "mycluster1", "mycluster2", ...
func (s *Sentinel) GetCluster(name string) (c *Cluster, err error) {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	rep, err := NewCommand("SENTINEL", "masters").Run(s.conn)
	if err != nil {
		return nil, err
	}
	replies, err := rep.Array()
	if err != nil {
		return nil, err
	}
	if len(replies) == 0 {
		return nil, ErrNoShard
	}
	instances := make(map[string]*instance)
	defer func() {
		if err != nil {
			for _, ins := range instances {
				ins.pool.Close()
			}
		}
	}()
	for _, r := range replies {
		master, err := r.Map()
		if err != nil {
			return nil, err
		}
		suffix := strings.TrimPrefix(master["name"], name)
		if !suffixRegex.MatchString(suffix) {
			continue
		}
		ins := &instance{
			name:    master["name"],
			address: master["ip"] + ":" + master["port"],
			state:   connStateConnected,
			pool:    &Pool{sentinel: true},
		}
		err = ins.pool.Dial(ins.address)
		if err != nil {
			return nil, err
		}
		instances[ins.name] = ins
	}
	c = NewCluster()
	c.sentinel = true
	for _, ins := range instances {
		s.instances[ins.name] = ins
		c.addresses = append(c.addresses, ins.address)
		c.shards = append(c.shards, ins.pool)
	}
	return c, nil
}

var suffixRegex = regexp.MustCompile("^\\d+$")

func (s *Sentinel) connect() (err error) {
	for i, server := range s.servers {
		s.conn, err = DialTimeout(server, time.Duration(Config.ConnectTimeout)*time.Second)
		if err != nil {
			continue
		}
		s.subConn, err = DialTimeout(server, time.Duration(Config.ConnectTimeout)*time.Second)
		if err != nil {
			s.conn.Close()
			continue
		}
		s.state = connStateConnected
		s.subs = NewSubscriptions(s.subConn)
		s.subs.throwError = true
		err = s.subs.Subscribe("+sdown", "-sdown", "+odown", "-odown", "+switch-master")
		if err != nil {
			s.close()
			continue
		}
		s.servers = append(s.servers[0:i], s.servers[i+1:]...)
		s.servers = append(s.servers, server)
		go s.monitor()
		return nil
	}
	return ErrNotConnected
}

func (s *Sentinel) close() {
	s.state = connStateNotConnected
	s.subs.Close()
	s.subConn.Close()
	s.conn.Close()
	for _, ins := range s.instances {
		ins.pool.Close()
	}
	s.instances = make(map[string]*instance)
}

func (s *Sentinel) fail() {
	s.mutex.Lock()
	defer s.mutex.Unlock()
	if s.state == connStateConnected {
		s.state = connStateNotConnected
		s.reconnect()
	}
}

func (s *Sentinel) reconnect() {
	s.subs.Close()
	s.subConn.Close()
	s.conn.Close()
	sleepTime := Config.ReconnectTime
	for {
		err := s.connect()
		if err == nil {
			break
		}
		time.Sleep(time.Duration(sleepTime) * time.Second)
		if sleepTime < 30 {
			sleepTime += 2
		}
	}
}

func (s *Sentinel) monitor() {
	for message := range s.subs.Message() {
		if message == nil {
			s.fail()
			return
		}
		s.mutex.Lock()
		ins := s.getInstanceFromMessage(message)
		if ins == nil {
			s.mutex.Unlock()
			continue
		}
		if message.Channel == "+sdown" || message.Channel == "+odown" {
			ins.down(message)
		} else if message.Channel == "-sdown" || message.Channel == "-odown" {
			ins.up(message)
		} else if message.Channel == "+switch-master" {
			ins.switchMaster(s)
		}
		s.mutex.Unlock()
	}
}

func (s *Sentinel) getInstanceFromMessage(message *Message) *instance {
	if message.Channel == "+sdown" || message.Channel == "+odown" ||
		message.Channel == "-sdown" || message.Channel == "-odown" {
		pieces := strings.Split(string(message.Message), " ")
		if len(pieces) < 2 || pieces[0] != "master" {
			return nil
		}
		return s.instances[pieces[1]]
	} else if message.Channel == "+switch-master" {
		pieces := strings.Split(string(message.Message), " ")
		if len(pieces) < 1 {
			return nil
		}
		return s.instances[pieces[0]]
	}
	return nil
}

func (s *Sentinel) getInstanceAddress(name string) string {
	for {
		rep, err := NewCommand("SENTINEL", "get-master-addr-by-name", name).Run(s.conn)
		if err != nil {
			time.Sleep(2 * time.Second)
			continue
		}
		if !rep.IsArray() {
			return ""
		}
		result := []string{}
		rep.Slice(&result)
		if len(result) != 2 {
			return ""
		}
		return result[0] + ":" + result[1]
	}
}

type instance struct {
	name    string
	address string
	sdown   bool
	odown   bool
	pool    *Pool
	state   int
}

func (ins *instance) down(message *Message) {
	if ins.state != connStateConnected {
		return
	}
	ins.state = connStateNotConnected
	if message.Channel == "+sdown" {
		ins.sdown = true
	} else if message.Channel == "+odown" {
		ins.odown = true
	}
	ins.pool.sentinelGonnaLetYouDown()
}

func (ins *instance) up(message *Message) {
	if ins.state == connStateConnected {
		return
	}
	if message.Channel == "-sdown" {
		ins.sdown = false
	} else if message.Channel == "-odown" {
		ins.odown = false
	}
	if !ins.sdown && !ins.odown {
		ins.pool.sentinelGonnaGiveYouUp()
		ins.state = connStateConnected
	}
}

func (ins *instance) switchMaster(s *Sentinel) {
	address := s.getInstanceAddress(ins.name)
	if address == "" {
		// WTF
		return
	}
	if ins.state == connStateConnected {
		ins.pool.sentinelGonnaLetYouDown()
	}
	ins.pool.address = address
	ins.pool.sentinelGonnaGiveYouUp()
	ins.state = connStateConnected
}
<|endoftext|>