<commit_before>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/jack-zh/ztodo/task"
	"github.com/jack-zh/ztodo/utils"
)

var noAct = errors.New("")

var userconfig_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "userconfig.json")
var cloud_work_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "worktasks.json")
var cloud_backup_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "backuptasks.json")
var simple_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "simpletasks")

const version = "0.4.6"
const build_time = "2015-01-19"
const usage = `Incorrect Usage.

NAME:
   ztodo - a command line todo list!

USAGE:
   ztodo [global options] command [command options] [arguments...]

VERSION:
   version:` + version + " (" + build_time + ") build" + `

AUTHOR:
  Jack.z - <zzh.coder@qq.com>

COMMANDS:

	ztodo list|ls [verbose]    -- Show all tasks
	ztodo list|ls N [verbose]  -- Show task N
	ztodo rm|remove N          -- Remove task N
	ztodo done N               -- Done task N
	ztodo undo N               -- Undo task N
	ztodo doing N              -- Doing task N
	ztodo clean                -- Rm done task
	ztodo clear                -- Rm all task
	ztodo add ...              -- Add task to list

GLOBAL OPTIONS:
	ztodo version              -- Show ztodo version
	ztodo help                 -- Show usage
`

func printSimpleTask(t string, i string) {
	if strings.HasPrefix(t, "0") {
		t = strings.Replace(t, "0", "[New]", 1)
	}
	if strings.HasPrefix(t, "1") {
		t = strings.Replace(t, "1", "[Doing ]", 1)
	}
	if strings.HasPrefix(t, "2") {
		t = strings.Replace(t, "2", "[Done  ]", 1)
	}
	fmt.Printf("%2s: %s\n", i, t)
}

func dirCheck() error {
	var filename = filepath.Join(os.Getenv("HOME"), ".ztodo")
	finfo, err := os.Stat(filename)
	if err != nil {
		os.Mkdir(filename, os.ModePerm)
		return nil
	}
	if finfo.IsDir() {
		return nil
	} else {
		return errors.New("$HOME/.ztodo is a file not dir.")
	}
}

func main() {
	errdir := dirCheck()
	if errdir != nil {
		os.Exit(1)
	}
	flag.Usage = func() {
		fmt.Fprint(os.Stderr, usage)
		flag.PrintDefaults()
	}
	flag.Parse()

	simplelist := task.SimpleNewList(simple_tasks_filename)
	cloudlist := task.CloudNewList(cloud_work_tasks_filename, cloud_backup_tasks_filename, userconfig_filename)
	a, n := flag.Arg(0), len(flag.Args())

	a = strings.ToLower(a)
	if a == "ls" {
		a = "list"
	}
	if a == "remove" {
		a = "rm"
	}
	if a == "simplels" {
		a = "simplelist"
	}
	if a == "simpleremove" {
		a = "simplerm"
	}

	err := noAct
	switch {
	case a == "version" && n == 1:
		fmt.Println("ztodo version " + version + " (" + build_time + ") build")
		err = nil

	case a == "help" && n == 1:
		fmt.Println(usage)
		err = nil

	case a == "simplelist" && n == 1:
		var tasks []string
		tasks, err = simplelist.SimpleGet()
		for i := 0; i < len(tasks); i++ {
			printSimpleTask(tasks[i], strconv.Itoa(i+1))
		}
	case a == "simplelist" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		var task string
		task, err = simplelist.SimpleGetTask(i - 1)
		if err == nil {
			printSimpleTask(task, strconv.Itoa(i))
		}
	case a == "simplerm" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleRemoveTask(i - 1)
		if err != nil {
			break
		}
	case a == "simpleadd" && n > 1:
		t := strings.Join(flag.Args()[1:], " ")
		err = simplelist.SimpleAddTask(t)
		err = cloudlist.CloudAddTask(t)

	case a == "simpledoing" && n == 2:
		i, err3 := strconv.Atoi(flag.Args()[1])
		if err3 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleDoingTask(i - 1)

	case a == "simpledone" && n == 2:
		i, err4 := strconv.Atoi(flag.Args()[1])
		if err4 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleDoneTask(i - 1)
	case a == "simpleundo" && n == 2:
		i, err5 := strconv.Atoi(flag.Args()[1])
		if err5 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleUndoTask(i - 1)
	case a == "simpleclean" && n == 1:
		err = simplelist.SimpleCleanTask()
	case a == "simpleclear" && n == 1:
		err = simplelist.SimpleClearTask()

	case a == "list" && n == 1:
		err = cloudlist.CloudGetAllWorkTaskByFile()
		if err == nil {
			cloudlist.CloudTasksPrint(-1)
		}

	case a == "list" && n == 2:
		if flag.Arg(1) == "verbose" {
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrintVerbose(-1)
			}
		} else {
			i, err2 := strconv.Atoi(flag.Arg(1))
			if err2 != nil {
				fmt.Fprint(os.Stdout, usage)
				break
			}
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrint(i)
			}
		}
	case a == "list" && n == 3:
		if flag.Arg(2) == "verbose" {
			i, err2 := strconv.Atoi(flag.Arg(1))
			if err2 != nil {
				fmt.Fprint(os.Stdout, usage)
				break
			}
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrintVerbose(i)
			}
		} else {
			fmt.Fprint(os.Stdout, usage)
		}

	case a == "rm" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudRemoveTask(i)
		if err != nil {
			break
		}
	case a == "add" && n > 1:
		t := strings.Join(flag.Args()[1:], " ")
		err = cloudlist.CloudAddTask(t)

	case a == "doing" && n == 2:
		i, err3 := strconv.Atoi(flag.Args()[1])
		if err3 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudDoingTask(i)

	case a == "done" && n == 2:
		i, err4 := strconv.Atoi(flag.Args()[1])
		if err4 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudDoneTask(i)
	case a == "undo" && n == 2:
		i, err5 := strconv.Atoi(flag.Args()[1])
		if err5 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudUndoTask(i)
	case a == "clean" && n == 1:
		err = cloudlist.CloudCleanTask()
	case a == "clear" && n == 1:
		err = cloudlist.CloudClearTask()

	case a == "pull" && n == 1:
		_, _ = cloudlist.CloudPullAll()
	case a == "pull" && n == 2:
		i, err6 := strconv.Atoi(flag.Args()[1])
		if err6 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		_, _ = cloudlist.CloudPullOne(i)
	case a == "push" && n == 1:
		_ = cloudlist.CloudPushAll()
	case a == "push" && n == 2:
		i, err7 := strconv.Atoi(flag.Args()[1])
		if err7 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		_ = cloudlist.CloudPushOne(i)
	case a == "signup" && n == 1:
		username, password, retypepassword := utils.CredentialsRetype()
		if password == retypepassword {
			err = cloudlist.Signup(username, password)
		} else {
			err = errors.New("Mismatch")
		}
	case a == "login" && n == 1:
		username, password := utils.Credentials()
		err = cloudlist.Login(username, password)

	case a == "logout" && n == 1:
		err = cloudlist.Logout()

	case a == "user" && n == 1:
		err = cloudlist.ShowUserConfig()

	case a == "staying-up" && n == 1:
		err = cloudlist.ShowUserConfig()

	default:
		fmt.Fprint(os.Stdout, usage)
		err = nil
		os.Exit(1)
	}
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
<commit_msg>add fmt.Println<commit_after>package main

import (
	"errors"
	"flag"
	"fmt"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/jack-zh/ztodo/task"
	"github.com/jack-zh/ztodo/utils"
)

var noAct = errors.New("")

var userconfig_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "userconfig.json")
var cloud_work_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "worktasks.json")
var cloud_backup_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "backuptasks.json")
var simple_tasks_filename = filepath.Join(os.Getenv("HOME"), ".ztodo", "simpletasks")

const version = "0.4.6"
const build_time = "2015-01-19"
const usage = `Incorrect Usage.

NAME:
   ztodo - a command line todo list!

USAGE:
   ztodo [global options] command [command options] [arguments...]

VERSION:
   version:` + version + " (" + build_time + ") build" + `

AUTHOR:
  Jack.z - <zzh.coder@qq.com>

COMMANDS:

	ztodo list|ls [verbose]    -- Show all tasks
	ztodo list|ls N [verbose]  -- Show task N
	ztodo rm|remove N          -- Remove task N
	ztodo done N               -- Done task N
	ztodo undo N               -- Undo task N
	ztodo doing N              -- Doing task N
	ztodo clean                -- Rm done task
	ztodo clear                -- Rm all task
	ztodo add ...              -- Add task to list

GLOBAL OPTIONS:
	ztodo version              -- Show ztodo version
	ztodo help                 -- Show usage
`

func printSimpleTask(t string, i string) {
	if strings.HasPrefix(t, "0") {
		t = strings.Replace(t, "0", "[New]", 1)
	}
	if strings.HasPrefix(t, "1") {
		t = strings.Replace(t, "1", "[Doing ]", 1)
	}
	if strings.HasPrefix(t, "2") {
		t = strings.Replace(t, "2", "[Done  ]", 1)
	}
	fmt.Printf("%2s: %s\n", i, t)
}

func dirCheck() error {
	var filename = filepath.Join(os.Getenv("HOME"), ".ztodo")
	finfo, err := os.Stat(filename)
	if err != nil {
		os.Mkdir(filename, os.ModePerm)
		return nil
	}
	if finfo.IsDir() {
		return nil
	} else {
		return errors.New("$HOME/.ztodo is a file not dir.")
	}
}

func main() {
	errdir := dirCheck()
	if errdir != nil {
		os.Exit(1)
	}
	flag.Usage = func() {
		fmt.Fprint(os.Stderr, usage)
		flag.PrintDefaults()
	}
	flag.Parse()

	simplelist := task.SimpleNewList(simple_tasks_filename)
	cloudlist := task.CloudNewList(cloud_work_tasks_filename, cloud_backup_tasks_filename, userconfig_filename)
	a, n := flag.Arg(0), len(flag.Args())

	a = strings.ToLower(a)
	if a == "ls" {
		a = "list"
	}
	if a == "remove" {
		a = "rm"
	}
	if a == "simplels" {
		a = "simplelist"
	}
	if a == "simpleremove" {
		a = "simplerm"
	}

	err := noAct
	switch {
	case a == "version" && n == 1:
		fmt.Println("ztodo version " + version + " (" + build_time + ") build")
		err = nil

	case a == "help" && n == 1:
		fmt.Println(usage)
		err = nil

	case a == "simplelist" && n == 1:
		var tasks []string
		tasks, err = simplelist.SimpleGet()
		for i := 0; i < len(tasks); i++ {
			printSimpleTask(tasks[i], strconv.Itoa(i+1))
		}
	case a == "simplelist" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		var task string
		task, err = simplelist.SimpleGetTask(i - 1)
		if err == nil {
			printSimpleTask(task, strconv.Itoa(i))
		}
	case a == "simplerm" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleRemoveTask(i - 1)
		if err != nil {
			break
		}
	case a == "simpleadd" && n > 1:
		t := strings.Join(flag.Args()[1:], " ")
		err = simplelist.SimpleAddTask(t)
		err = cloudlist.CloudAddTask(t)

	case a == "simpledoing" && n == 2:
		i, err3 := strconv.Atoi(flag.Args()[1])
		if err3 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleDoingTask(i - 1)

	case a == "simpledone" && n == 2:
		i, err4 := strconv.Atoi(flag.Args()[1])
		if err4 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleDoneTask(i - 1)
	case a == "simpleundo" && n == 2:
		i, err5 := strconv.Atoi(flag.Args()[1])
		if err5 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = simplelist.SimpleUndoTask(i - 1)
	case a == "simpleclean" && n == 1:
		err = simplelist.SimpleCleanTask()
	case a == "simpleclear" && n == 1:
		err = simplelist.SimpleClearTask()

	case a == "list" && n == 1:
		err = cloudlist.CloudGetAllWorkTaskByFile()
		if err == nil {
			cloudlist.CloudTasksPrint(-1)
		}

	case a == "list" && n == 2:
		if flag.Arg(1) == "verbose" {
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrintVerbose(-1)
			}
		} else {
			i, err2 := strconv.Atoi(flag.Arg(1))
			if err2 != nil {
				fmt.Fprint(os.Stdout, usage)
				break
			}
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrint(i)
			}
		}
	case a == "list" && n == 3:
		if flag.Arg(2) == "verbose" {
			i, err2 := strconv.Atoi(flag.Arg(1))
			if err2 != nil {
				fmt.Fprint(os.Stdout, usage)
				break
			}
			err = cloudlist.CloudGetAllWorkTaskByFile()
			if err == nil {
				cloudlist.CloudTasksPrintVerbose(i)
			}
		} else {
			fmt.Fprint(os.Stdout, usage)
		}

	case a == "rm" && n == 2:
		i, err2 := strconv.Atoi(flag.Arg(1))
		if err2 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudRemoveTask(i)
		if err != nil {
			break
		}
	case a == "add" && n > 1:
		t := strings.Join(flag.Args()[1:], " ")
		err = cloudlist.CloudAddTask(t)

	case a == "doing" && n == 2:
		i, err3 := strconv.Atoi(flag.Args()[1])
		if err3 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudDoingTask(i)

	case a == "done" && n == 2:
		i, err4 := strconv.Atoi(flag.Args()[1])
		if err4 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudDoneTask(i)
	case a == "undo" && n == 2:
		i, err5 := strconv.Atoi(flag.Args()[1])
		if err5 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		err = cloudlist.CloudUndoTask(i)
	case a == "clean" && n == 1:
		err = cloudlist.CloudCleanTask()
	case a == "clear" && n == 1:
		err = cloudlist.CloudClearTask()

	case a == "pull" && n == 1:
		_, _ = cloudlist.CloudPullAll()
	case a == "pull" && n == 2:
		i, err6 := strconv.Atoi(flag.Args()[1])
		if err6 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		_, _ = cloudlist.CloudPullOne(i)
	case a == "push" && n == 1:
		_ = cloudlist.CloudPushAll()
	case a == "push" && n == 2:
		i, err7 := strconv.Atoi(flag.Args()[1])
		if err7 != nil {
			fmt.Fprint(os.Stdout, usage)
			break
		}
		_ = cloudlist.CloudPushOne(i)
	case a == "signup" && n == 1:
		username, password, retypepassword := utils.CredentialsRetype()
		if password == retypepassword {
			err = cloudlist.Signup(username, password)
		} else {
			err = errors.New("Mismatch")
		}
	case a == "login" && n == 1:
		username, password := utils.Credentials()
		err = cloudlist.Login(username, password)

	case a == "logout" && n == 1:
		err = cloudlist.Logout()

	case a == "user" && n == 1:
		err = cloudlist.ShowUserConfig()

	case a == "staying-up" && n == 1:
		fmt.Println("Happy New Year.")

	default:
		fmt.Fprint(os.Stdout, usage)
		err = nil
		os.Exit(1)
	}
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"
	"testing"

	"veyron.io/veyron/veyron2/naming"
	"veyron.io/veyron/veyron2/rt"
	"veyron.io/veyron/veyron2/services/mgmt/node"
	"veyron.io/veyron/veyron2/verror"
)

func TestListCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	// Test the 'list' command.
	tape.SetResponses([]interface{}{ListAssociationResponse{
		na: []node.Association{
			{
				"root/self",
				"alice_self_account",
			},
			{
				"root/other",
				"alice_other_account",
			},
		},
		err: nil,
	}})

	if err := cmd.Execute([]string{"associate", "list", nodeName}); err != nil {
		t.Fatalf("%v", err)
	}
	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
	}
	if got, expected := tape.Play(), []interface{}{"ListAssociations"}; !reflect.DeepEqual(expected, got) {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	// Test list with bad parameters.
	if err := cmd.Execute([]string{"associate", "list", nodeName, "hello"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestAddCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "/myapp/1")
	//nodeName := endpoint.String()

	if err := cmd.Execute([]string{"add", "one"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "add", nodeName, "alice", "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected := []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, "alice"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "add", nodeName, "alice", "root/other", "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected = []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/other", "root/self"}, "alice"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestRemoveCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	if err := cmd.Execute([]string{"remove", "one"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "remove", nodeName, "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected := []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, ""},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestInstallCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	if err := cmd.Execute([]string{"install", "blech"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	if err := cmd.Execute([]string{"install", "blech1", "blech2", "blech3"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	appId := "myBestAppID"
	tape.SetResponses([]interface{}{InstallResponse{
		appId: appId,
		err:   nil,
	}})
	if err := cmd.Execute([]string{"install", nodeName, "myBestApp"}); err != nil {
		t.Fatalf("%v", err)
	}

	eb := new(bytes.Buffer)
	fmt.Fprintf(eb, "Successfully installed: %q", naming.Join(nodeName, appId))
	if expected, got := eb.String(), strings.TrimSpace(stdout.String()); got != expected {
		t.Fatalf("Unexpected output from Install. Got %q, expected %q", got, expected)
	}
	expected := []interface{}{
		InstallStimulus{"Install", "myBestApp"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestClaimCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	// Confirm that we correctly enforce the number of arguments.
	if err := cmd.Execute([]string{"claim", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: claim: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	if err := cmd.Execute([]string{"claim", "nope", "nope", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: claim: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	// Correct operation.
	tape.SetResponses([]interface{}{
		nil,
	})
	if err := cmd.Execute([]string{"claim", nodeName, "grant"}); err != nil {
		t.Fatalf("Claim(%s, %s) failed: %v", nodeName, "grant", err)
	}
	if got, expected := len(tape.Play()), 1; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	if expected, got := "Successfully claimed.", strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	expected := []interface{}{
		"Claim",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

	// Error operation.
	tape.SetResponses([]interface{}{
		verror.BadArgf("oops!"),
	})
	if err := cmd.Execute([]string{"claim", nodeName, "grant"}); err == nil {
		t.Fatalf("claim() failed to detect error", err)
	}
	expected = []interface{}{
		"Claim",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

}

func TestStartCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	appName := naming.JoinAddressName(endpoint.String(), "")

	// Confirm that we correctly enforce the number of arguments.
	if err := cmd.Execute([]string{"start", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: start: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	if err := cmd.Execute([]string{"start", "nope", "nope", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: start: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	// Correct operation.
	tape.SetResponses([]interface{}{StartResponse{
		appIds: []string{"app1", "app2"},
		err:    nil,
	},
	})
	if err := cmd.Execute([]string{"start", appName, "grant"}); err != nil {
		t.Fatalf("Start(%s, %s) failed: %v", appName, "grant", err)
	}

	b := new(bytes.Buffer)
	fmt.Fprintf(b, "Successfully started: %q\nSuccessfully started: %q", appName+"/app1", appName+"/app2")
	if expected, got := b.String(), strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	expected := []interface{}{
		"Start",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

	// Error operation.
	tape.SetResponses([]interface{}{StartResponse{
		[]string{},
		verror.BadArgf("oops!"),
	},
	})
	if err := cmd.Execute([]string{"start", appName, "grant"}); err == nil {
		t.Fatalf("start failed to detect error")
	}
	expected = []interface{}{
		"Start",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()
}
<commit_msg>veyron/tools/mgmt/nodex: Code cleanup.<commit_after>package main

import (
	"bytes"
	"fmt"
	"reflect"
	"strings"
	"testing"

	"veyron.io/veyron/veyron2/naming"
	"veyron.io/veyron/veyron2/rt"
	"veyron.io/veyron/veyron2/services/mgmt/node"
	"veyron.io/veyron/veyron2/verror"
)

func TestListCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	// Test the 'list' command.
	tape.SetResponses([]interface{}{ListAssociationResponse{
		na: []node.Association{
			{
				"root/self",
				"alice_self_account",
			},
			{
				"root/other",
				"alice_other_account",
			},
		},
		err: nil,
	}})

	if err := cmd.Execute([]string{"associate", "list", nodeName}); err != nil {
		t.Fatalf("%v", err)
	}
	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
		t.Fatalf("Unexpected output from list. Got %q, expected %q", got, expected)
	}
	if got, expected := tape.Play(), []interface{}{"ListAssociations"}; !reflect.DeepEqual(expected, got) {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	// Test list with bad parameters.
	if err := cmd.Execute([]string{"associate", "list", nodeName, "hello"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestAddCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "/myapp/1")

	if err := cmd.Execute([]string{"add", "one"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "add", nodeName, "alice", "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected := []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, "alice"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "add", nodeName, "alice", "root/other", "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected = []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/other", "root/self"}, "alice"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestRemoveCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	if err := cmd.Execute([]string{"remove", "one"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	tape.SetResponses([]interface{}{nil})
	if err := cmd.Execute([]string{"associate", "remove", nodeName, "root/self"}); err != nil {
		t.Fatalf("%v", err)
	}
	expected := []interface{}{
		AddAssociationStimulus{"AssociateAccount", []string{"root/self"}, ""},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestInstallCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	if err := cmd.Execute([]string{"install", "blech"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	if err := cmd.Execute([]string{"install", "blech1", "blech2", "blech3"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if got, expected := len(tape.Play()), 0; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()

	appId := "myBestAppID"
	tape.SetResponses([]interface{}{InstallResponse{
		appId: appId,
		err:   nil,
	}})
	if err := cmd.Execute([]string{"install", nodeName, "myBestApp"}); err != nil {
		t.Fatalf("%v", err)
	}

	eb := new(bytes.Buffer)
	fmt.Fprintf(eb, "Successfully installed: %q", naming.Join(nodeName, appId))
	if expected, got := eb.String(), strings.TrimSpace(stdout.String()); got != expected {
		t.Fatalf("Unexpected output from Install. Got %q, expected %q", got, expected)
	}
	expected := []interface{}{
		InstallStimulus{"Install", "myBestApp"},
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
}

func TestClaimCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	nodeName := naming.JoinAddressName(endpoint.String(), "")

	// Confirm that we correctly enforce the number of arguments.
	if err := cmd.Execute([]string{"claim", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: claim: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	if err := cmd.Execute([]string{"claim", "nope", "nope", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: claim: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	// Correct operation.
	tape.SetResponses([]interface{}{
		nil,
	})
	if err := cmd.Execute([]string{"claim", nodeName, "grant"}); err != nil {
		t.Fatalf("Claim(%s, %s) failed: %v", nodeName, "grant", err)
	}
	if got, expected := len(tape.Play()), 1; got != expected {
		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
	}
	if expected, got := "Successfully claimed.", strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from claim. Got %q, expected prefix %q", got, expected)
	}
	expected := []interface{}{
		"Claim",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

	// Error operation.
	tape.SetResponses([]interface{}{
		verror.BadArgf("oops!"),
	})
	if err := cmd.Execute([]string{"claim", nodeName, "grant"}); err == nil {
		t.Fatalf("claim() failed to detect error", err)
	}
	expected = []interface{}{
		"Claim",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

}

func TestStartCommand(t *testing.T) {
	runtime := rt.Init()
	tape := NewTape()
	server, endpoint, err := startServer(t, runtime, tape)
	if err != nil {
		return
	}
	defer stopServer(t, server)

	// Setup the command-line.
	cmd := root()
	var stdout, stderr bytes.Buffer
	cmd.Init(nil, &stdout, &stderr)
	appName := naming.JoinAddressName(endpoint.String(), "")

	// Confirm that we correctly enforce the number of arguments.
	if err := cmd.Execute([]string{"start", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: start: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	if err := cmd.Execute([]string{"start", "nope", "nope", "nope"}); err == nil {
		t.Fatalf("wrongly failed to receive a non-nil error.")
	}
	if expected, got := "ERROR: start: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	stdout.Reset()
	stderr.Reset()
	tape.Rewind()

	// Correct operation.
	tape.SetResponses([]interface{}{StartResponse{
		appIds: []string{"app1", "app2"},
		err:    nil,
	},
	})
	if err := cmd.Execute([]string{"start", appName, "grant"}); err != nil {
		t.Fatalf("Start(%s, %s) failed: %v", appName, "grant", err)
	}

	b := new(bytes.Buffer)
	fmt.Fprintf(b, "Successfully started: %q\nSuccessfully started: %q", appName+"/app1", appName+"/app2")
	if expected, got := b.String(), strings.TrimSpace(stdout.String()); !strings.HasPrefix(got, expected) {
		t.Fatalf("Unexpected output from start. Got %q, expected prefix %q", got, expected)
	}
	expected := []interface{}{
		"Start",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()

	// Error operation.
	tape.SetResponses([]interface{}{StartResponse{
		[]string{},
		verror.BadArgf("oops!"),
	},
	})
	if err := cmd.Execute([]string{"start", appName, "grant"}); err == nil {
		t.Fatalf("start failed to detect error")
	}
	expected = []interface{}{
		"Start",
	}
	if got := tape.Play(); !reflect.DeepEqual(expected, got) {
		t.Errorf("unexpected result. Got %v want %v", got, expected)
	}
	tape.Rewind()
	stdout.Reset()
	stderr.Reset()
}
<|endoftext|>


<commit_before>// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package opencensus

import (
	"context"
	"fmt"
	"net/http"
	"net/url"

	"github.com/pkg/errors"
	"go.opencensus.io/trace"
)

// Transport for tracing Elastic operations.
type Transport struct {
	rt                http.RoundTripper
	defaultAttributes []trace.Attribute
}

// Option signature for specifying options, e.g. WithRoundTripper.
type Option func(t *Transport)

// WithRoundTripper specifies the http.RoundTripper to call
// next after this transport. If it is nil (default), the
// transport will use http.DefaultTransport.
func WithRoundTripper(rt http.RoundTripper) Option {
	return func(t *Transport) {
		t.rt = rt
	}
}

// WithDefaultAttributes specifies default attributes to add
// to each span.
func WithDefaultAttributes(attrs ...trace.Attribute) Option {
	return func(t *Transport) {
		t.defaultAttributes = attrs
	}
}

// NewTransport specifies a transport that will trace Elastic
// and report back via OpenTracing.
func NewTransport(opts ...Option) *Transport {
	t := &Transport{}
	for _, o := range opts {
		o(t)
	}
	return t
}

// RoundTrip captures the request and starts an OpenTracing span
// for Elastic PerformRequest operation.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	_, span := trace.StartSpan(req.Context(), "elastic:PerformRequest")
	attrs := append([]trace.Attribute(nil), t.defaultAttributes...)
	attrs = append(attrs,
		trace.StringAttribute("Component", "github.com/olivere/elastic/v7"),
		trace.StringAttribute("Method", req.Method),
		trace.StringAttribute("URL", req.URL.String()),
		trace.StringAttribute("Hostname", req.URL.Hostname()),
		trace.Int64Attribute("Port", atoi64(req.URL.Port())),
	)
	span.AddAttributes(attrs...)

	var (
		resp *http.Response
		err  error
	)
	defer func() {
		setSpanStatus(span, err)
		span.End()
	}()
	if t.rt != nil {
		resp, err = t.rt.RoundTrip(req)
	} else {
		resp, err = http.DefaultTransport.RoundTrip(req)
	}
	return resp, err
}

// See https://github.com/opencensus-integrations/ocsql/blob/master/driver.go#L749
func setSpanStatus(span *trace.Span, err error) {
	var status trace.Status
	switch {
	case err == nil:
		status.Code = trace.StatusCodeOK
		span.SetStatus(status)
		return
	case err == context.Canceled:
		status.Code = trace.StatusCodeCancelled
	case err == context.DeadlineExceeded:
		status.Code = trace.StatusCodeDeadlineExceeded
	case isConnErr(err):
		status.Code = trace.StatusCodeUnavailable
	case isNotFound(err):
		status.Code = trace.StatusCodeNotFound
	case isConflict(err):
		status.Code = trace.StatusCodeFailedPrecondition
	case isForbidden(err):
		status.Code = trace.StatusCodePermissionDenied
	case isTimeout(err):
		status.Code = trace.StatusCodeResourceExhausted
	default:
		status.Code = trace.StatusCodeUnknown
	}
	status.Message = err.Error()
	span.SetStatus(status)
}

// Copied from elastic to prevent cyclic dependencies.
type elasticError struct {
	Status  int           `json:"status"`
	Details *errorDetails `json:"error,omitempty"`
}

// errorDetails encapsulate error details from Elasticsearch.
// It is used in e.g. elastic.Error and elastic.BulkResponseItem.
type errorDetails struct {
	Type         string                   `json:"type"`
	Reason       string                   `json:"reason"`
	ResourceType string                   `json:"resource.type,omitempty"`
	ResourceId   string                   `json:"resource.id,omitempty"`
	Index        string                   `json:"index,omitempty"`
	Phase        string                   `json:"phase,omitempty"`
	Grouped      bool                     `json:"grouped,omitempty"`
	CausedBy     map[string]interface{}   `json:"caused_by,omitempty"`
	RootCause    []*errorDetails          `json:"root_cause,omitempty"`
	FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
}

// Error returns a string representation of the error.
func (e *elasticError) Error() string {
	if e.Details != nil && e.Details.Reason != "" {
		return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
	}
	return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
}

// isContextErr returns true if the error is from a context that was canceled or deadline exceeded
func isContextErr(err error) bool {
	if err == context.Canceled || err == context.DeadlineExceeded {
		return true
	}
	// This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
	if ue, ok := err.(*url.Error); ok {
		if ue.Temporary() {
			return true
		}
		// Use of an AWS Signing Transport can result in a wrapped url.Error
		return isContextErr(ue.Err)
	}
	return false
}

// isConnErr returns true if the error indicates that Elastic could not
// find an Elasticsearch host to connect to.
func isConnErr(err error) bool {
	if err == nil {
		return false
	}
	if err.Error() == "no Elasticsearch node available" {
		return true
	}
	innerErr := errors.Cause(err)
	if innerErr == nil {
		return false
	}
	if innerErr.Error() == "no Elasticsearch node available" {
		return true
	}
	return false
}

// isNotFound returns true if the given error indicates that Elasticsearch
// returned HTTP status 404. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isNotFound(err interface{}) bool {
	return isStatusCode(err, http.StatusNotFound)
}

// isTimeout returns true if the given error indicates that Elasticsearch
// returned HTTP status 408. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isTimeout(err interface{}) bool {
	return isStatusCode(err, http.StatusRequestTimeout)
}

// isConflict returns true if the given error indicates that the Elasticsearch
// operation resulted in a version conflict. This can occur in operations like
// `update` or `index` with `op_type=create`. The err parameter can be of
// type *elastic.Error, elastic.Error, *http.Response or int (indicating the
// HTTP status code).
func isConflict(err interface{}) bool {
	return isStatusCode(err, http.StatusConflict)
}

// isForbidden returns true if the given error indicates that Elasticsearch
// returned HTTP status 403. This happens e.g. due to a missing license.
// The err parameter can be of type *elastic.Error, elastic.Error,
// *http.Response or int (indicating the HTTP status code).
func isForbidden(err interface{}) bool {
	return isStatusCode(err, http.StatusForbidden)
}

// isStatusCode returns true if the given error indicates that the Elasticsearch
// operation returned the specified HTTP status code. The err parameter can be of
// type *http.Response, *Error, Error, or int (indicating the HTTP status code).
func isStatusCode(err interface{}, code int) bool {
	switch e := err.(type) {
	case *http.Response:
		return e.StatusCode == code
	case *elasticError:
		return e.Status == code
	case elasticError:
		return e.Status == code
	case int:
		return e == code
	}
	return false
}
<commit_msg>Redact URL in opencensus tracing<commit_after>// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package opencensus

import (
	"context"
	"fmt"
	"net/http"
	"net/url"

	"github.com/pkg/errors"
	"go.opencensus.io/trace"
)

// Transport for tracing Elastic operations.
type Transport struct {
	rt                http.RoundTripper
	defaultAttributes []trace.Attribute
}

// Option signature for specifying options, e.g. WithRoundTripper.
type Option func(t *Transport)

// WithRoundTripper specifies the http.RoundTripper to call
// next after this transport. If it is nil (default), the
// transport will use http.DefaultTransport.
func WithRoundTripper(rt http.RoundTripper) Option {
	return func(t *Transport) {
		t.rt = rt
	}
}

// WithDefaultAttributes specifies default attributes to add
// to each span.
func WithDefaultAttributes(attrs ...trace.Attribute) Option {
	return func(t *Transport) {
		t.defaultAttributes = attrs
	}
}

// NewTransport specifies a transport that will trace Elastic
// and report back via OpenTracing.
func NewTransport(opts ...Option) *Transport {
	t := &Transport{}
	for _, o := range opts {
		o(t)
	}
	return t
}

// RoundTrip captures the request and starts an OpenTracing span
// for Elastic PerformRequest operation.
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
	_, span := trace.StartSpan(req.Context(), "elastic:PerformRequest")
	attrs := append([]trace.Attribute(nil), t.defaultAttributes...)
	attrs = append(attrs,
		trace.StringAttribute("Component", "github.com/olivere/elastic/v7"),
		trace.StringAttribute("Method", req.Method),
		trace.StringAttribute("URL", req.URL.Redacted()),
		trace.StringAttribute("Hostname", req.URL.Hostname()),
		trace.Int64Attribute("Port", atoi64(req.URL.Port())),
	)
	span.AddAttributes(attrs...)

	var (
		resp *http.Response
		err  error
	)
	defer func() {
		setSpanStatus(span, err)
		span.End()
	}()
	if t.rt != nil {
		resp, err = t.rt.RoundTrip(req)
	} else {
		resp, err = http.DefaultTransport.RoundTrip(req)
	}
	return resp, err
}

// See https://github.com/opencensus-integrations/ocsql/blob/master/driver.go#L749
func setSpanStatus(span *trace.Span, err error) {
	var status trace.Status
	switch {
	case err == nil:
		status.Code = trace.StatusCodeOK
		span.SetStatus(status)
		return
	case err == context.Canceled:
		status.Code = trace.StatusCodeCancelled
	case err == context.DeadlineExceeded:
		status.Code = trace.StatusCodeDeadlineExceeded
	case isConnErr(err):
		status.Code = trace.StatusCodeUnavailable
	case isNotFound(err):
		status.Code = trace.StatusCodeNotFound
	case isConflict(err):
		status.Code = trace.StatusCodeFailedPrecondition
	case isForbidden(err):
		status.Code = trace.StatusCodePermissionDenied
	case isTimeout(err):
		status.Code = trace.StatusCodeResourceExhausted
	default:
		status.Code = trace.StatusCodeUnknown
	}
	status.Message = err.Error()
	span.SetStatus(status)
}

// Copied from elastic to prevent cyclic dependencies.
type elasticError struct {
	Status  int           `json:"status"`
	Details *errorDetails `json:"error,omitempty"`
}

// errorDetails encapsulate error details from Elasticsearch.
// It is used in e.g. elastic.Error and elastic.BulkResponseItem.
type errorDetails struct {
	Type         string                   `json:"type"`
	Reason       string                   `json:"reason"`
	ResourceType string                   `json:"resource.type,omitempty"`
	ResourceId   string                   `json:"resource.id,omitempty"`
	Index        string                   `json:"index,omitempty"`
	Phase        string                   `json:"phase,omitempty"`
	Grouped      bool                     `json:"grouped,omitempty"`
	CausedBy     map[string]interface{}   `json:"caused_by,omitempty"`
	RootCause    []*errorDetails          `json:"root_cause,omitempty"`
	FailedShards []map[string]interface{} `json:"failed_shards,omitempty"`
}

// Error returns a string representation of the error.
func (e *elasticError) Error() string {
	if e.Details != nil && e.Details.Reason != "" {
		return fmt.Sprintf("elastic: Error %d (%s): %s [type=%s]", e.Status, http.StatusText(e.Status), e.Details.Reason, e.Details.Type)
	}
	return fmt.Sprintf("elastic: Error %d (%s)", e.Status, http.StatusText(e.Status))
}

// isContextErr returns true if the error is from a context that was canceled or deadline exceeded
func isContextErr(err error) bool {
	if err == context.Canceled || err == context.DeadlineExceeded {
		return true
	}
	// This happens e.g. on redirect errors, see https://golang.org/src/net/http/client_test.go#L329
	if ue, ok := err.(*url.Error); ok {
		if ue.Temporary() {
			return true
		}
		// Use of an AWS Signing Transport can result in a wrapped url.Error
		return isContextErr(ue.Err)
	}
	return false
}

// isConnErr returns true if the error indicates that Elastic could not
// find an Elasticsearch host to connect to.
func isConnErr(err error) bool {
	if err == nil {
		return false
	}
	if err.Error() == "no Elasticsearch node available" {
		return true
	}
	innerErr := errors.Cause(err)
	if innerErr == nil {
		return false
	}
	if innerErr.Error() == "no Elasticsearch node available" {
		return true
	}
	return false
}

// isNotFound returns true if the given error indicates that Elasticsearch
// returned HTTP status 404. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isNotFound(err interface{}) bool {
	return isStatusCode(err, http.StatusNotFound)
}

// isTimeout returns true if the given error indicates that Elasticsearch
// returned HTTP status 408. The err parameter can be of type *elastic.Error,
// elastic.Error, *http.Response or int (indicating the HTTP status code).
func isTimeout(err interface{}) bool {
	return isStatusCode(err, http.StatusRequestTimeout)
}

// isConflict returns true if the given error indicates that the Elasticsearch
// operation resulted in a version conflict. This can occur in operations like
// `update` or `index` with `op_type=create`. The err parameter can be of
// type *elastic.Error, elastic.Error, *http.Response or int (indicating the
// HTTP status code).
func isConflict(err interface{}) bool {
	return isStatusCode(err, http.StatusConflict)
}

// isForbidden returns true if the given error indicates that Elasticsearch
// returned HTTP status 403. This happens e.g. due to a missing license.
// The err parameter can be of type *elastic.Error, elastic.Error,
// *http.Response or int (indicating the HTTP status code).
func isForbidden(err interface{}) bool {
	return isStatusCode(err, http.StatusForbidden)
}

// isStatusCode returns true if the given error indicates that the Elasticsearch
// operation returned the specified HTTP status code. The err parameter can be of
// type *http.Response, *Error, Error, or int (indicating the HTTP status code).
func isStatusCode(err interface{}, code int) bool {
	switch e := err.(type) {
	case *http.Response:
		return e.StatusCode == code
	case *elasticError:
		return e.Status == code
	case elasticError:
		return e.Status == code
	case int:
		return e == code
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"

	"github.com/pivotal-golang/bytefmt"
)

var (
	awsProfile   = flag.String("awsProfile", "", "aws profile")
	s3Bucket     = flag.String("s3Bucket", "", "s3 bucket")
	s3Prefix     = flag.String("s3Prefix", "", "s3 prefix")
	localDir     = flag.String("localDir", "", "local directory")
	gcpProjectId = flag.String("gcpProjectId", "", "gcp project id")
	gsBucket     = flag.String("gsBucket", "", "gs bucket")
	dryRun       = flag.Bool("dryRun", false, "dry run")
)

type Exit struct{ Code int }

// exit code handler
// http://stackoverflow.com/a/27630092/1881379
func handleExit() {
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok == true {
			os.Exit(exit.Code)
		}
		panic(e) // not an Exit, bubble up
	}
	os.Exit(0)
}

// timing
// https://coderwall.com/p/cp5fya/measuring-execution-time-in-go
func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	log.Printf("%s took %s", name, elapsed)
}

func writeToGS(file *os.File, w *storage.Writer) error {
	var content io.Reader
	content, err := os.Open(file.Name())
	if err != nil {
		log.Fatal(err)
		return err
	}
	const maxSlurp = 1 << 20
	var buf bytes.Buffer
	n, err := io.CopyN(&buf, content, maxSlurp)
	if err != nil && err != io.EOF {
		log.Fatalf("Error reading from stdin: %v, %v", n, err)
		return err
	}
	w.ContentType = http.DetectContentType(buf.Bytes())
	_, err = io.Copy(w, io.MultiReader(&buf, content))
	if cerr := w.Close(); cerr != nil && err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatalf("Write error: %v", err)
		return err
	}
	return nil
}

func main() {
	defer handleExit()
	defer timeTrack(time.Now(), "S3toGS")

	flag.Parse()

	// Set up AWS clients
	awsSession := session.New(&aws.Config{
		Region:      aws.String("us-east-1"),
		Credentials: credentials.NewSharedCredentials("", *awsProfile),
	})
	s3Client := s3.New(awsSession)
	s3Downloader := s3manager.NewDownloader(awsSession)

	// Set up GCP clients
	gcpContext := context.Background()
	gsClient, err := storage.NewClient(gcpContext)
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}
	defer gsClient.Close()

	// S3 List
	s3List, err := s3Client.ListObjectsV2(&s3.ListObjectsV2Input{
		Bucket: aws.String(*s3Bucket),
		Prefix: aws.String(*s3Prefix),
	})
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}

	amtTransferred := uint64(0)

	for _, key := range s3List.Contents {
		gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)

		s3MD5 := strings.Replace(*key.ETag, "\"", "", -1)
		s3Size := *key.Size

		localFilepath := filepath.Join(*localDir, filepath.Base(*key.Key))

		if gsErr != nil || // doesn't exist in GS
			!strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) ||
			s3Size != gsAttrs.Size {

			if gsErr == nil && strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) {
				fmt.Println("Hash matches, skipping", *key.Key)
			} else if gsErr == nil && s3Size == gsAttrs.Size {
				fmt.Println("Size matches, skipping", *key.Key)
			} else if *dryRun {
				amtTransferred += uint64(s3Size)
				fmt.Println("Would download/upload", *key.Key)
			} else {
				amtTransferred += uint64(s3Size)

				// Create local file path and file
				err := os.MkdirAll(filepath.Dir(localFilepath), 0777)
				if err != nil {
					log.Fatal("Failed to create dirs", err)
					panic(Exit{1})
				}
				file, err := os.Create(localFilepath)
				if err != nil {
					log.Fatal("Failed to create file", err)
					panic(Exit{1})
				}
				defer file.Close()

				// Download from S3
				fmt.Println("Downloading from S3", *key.Key, "to", localFilepath)
				s3Downloader.Download(file,
					&s3.GetObjectInput{
						Bucket: aws.String(*s3Bucket),
						Key:    aws.String(*key.Key),
					})

				// Upload to GS
				// https://github.com/golang/build/blob/master/cmd/upload/upload.go
				fmt.Println("Uploading", localFilepath, "to GS at", *key.Key)
				w := gsClient.Bucket(*gsBucket).Object(*key.Key).NewWriter(gcpContext)
				writeToGS(file, w)

				// Delete local file
				fmt.Println("Removing", file.Name())
				os.Remove(file.Name())

				gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)
				if gsErr != nil || s3Size != gsAttrs.Size {
					log.Fatal("Upload failed")
					panic(Exit{1})
				}
			}
		} else {
			fmt.Println("Already in GS, skipping", *key.Key)
		}
	}

	fmt.Println("Amount transferred", bytefmt.ByteSize(amtTransferred))
}
<commit_msg>Remove unused var<commit_after>package main

import (
	"bytes"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/s3/s3manager"

	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"

	"github.com/pivotal-golang/bytefmt"
)

var (
	awsProfile = flag.String("awsProfile", "", "aws profile")
	s3Bucket   = flag.String("s3Bucket", "", "s3 bucket")
	s3Prefix   = flag.String("s3Prefix", "", "s3 prefix")
	localDir   = flag.String("localDir", "", "local directory")
	gsBucket   = flag.String("gsBucket", "", "gs bucket")
	dryRun     = flag.Bool("dryRun", false, "dry run")
)

// Exit struct helper
type Exit struct{ Code int }

// exit code handler
// http://stackoverflow.com/a/27630092/1881379
func handleExit() {
	if e := recover(); e != nil {
		if exit, ok := e.(Exit); ok == true {
			os.Exit(exit.Code)
		}
		panic(e) // not an Exit, bubble up
	}
	os.Exit(0)
}

// timing
// https://coderwall.com/p/cp5fya/measuring-execution-time-in-go
func timeTrack(start time.Time, name string) {
	elapsed := time.Since(start)
	log.Printf("%s took %s", name, elapsed)
}

func writeToGS(file *os.File, w *storage.Writer) error {
	var content io.Reader
	content, err := os.Open(file.Name())
	if err != nil {
		log.Fatal(err)
		return err
	}
	const maxSlurp = 1 << 20
	var buf bytes.Buffer
	n, err := io.CopyN(&buf, content, maxSlurp)
	if err != nil && err != io.EOF {
		log.Fatalf("Error reading from stdin: %v, %v", n, err)
		return err
	}
	w.ContentType = http.DetectContentType(buf.Bytes())
	_, err = io.Copy(w, io.MultiReader(&buf, content))
	if cerr := w.Close(); cerr != nil && err == nil {
		err = cerr
	}
	if err != nil {
		log.Fatalf("Write error: %v", err)
		return err
	}
	return nil
}

func main() {
	defer handleExit()
	defer timeTrack(time.Now(), "S3toGS")

	flag.Parse()

	// Set up AWS clients
	awsSession := session.New(&aws.Config{
		Region:      aws.String("us-east-1"),
		Credentials: credentials.NewSharedCredentials("", *awsProfile),
	})
	s3Client := s3.New(awsSession)
	s3Downloader := s3manager.NewDownloader(awsSession)

	// Set up GCP clients
	gcpContext := context.Background()
	gsClient, err := storage.NewClient(gcpContext)
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}
	defer gsClient.Close()

	// S3 List
	s3List, err := s3Client.ListObjectsV2(&s3.ListObjectsV2Input{
		Bucket: aws.String(*s3Bucket),
		Prefix: aws.String(*s3Prefix),
	})
	if err != nil {
		log.Fatal(err)
		panic(Exit{1})
	}

	amtTransferred := uint64(0)

	for _, key := range s3List.Contents {
		gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)

		s3MD5 := strings.Replace(*key.ETag, "\"", "", -1)
		s3Size := *key.Size

		localFilepath := filepath.Join(*localDir, filepath.Base(*key.Key))

		if gsErr != nil || // doesn't exist in GS
			!strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) ||
			s3Size != gsAttrs.Size {

			if gsErr == nil && strings.EqualFold(s3MD5, hex.EncodeToString(gsAttrs.MD5)) {
				fmt.Println("Hash matches, skipping", *key.Key)
			} else if gsErr == nil && s3Size == gsAttrs.Size {
				fmt.Println("Size matches, skipping", *key.Key)
			} else if *dryRun {
				amtTransferred += uint64(s3Size)
				fmt.Println("Would download/upload", *key.Key)
			} else {
				amtTransferred += uint64(s3Size)

				// Create local file path and file
				err := os.MkdirAll(filepath.Dir(localFilepath), 0777)
				if err != nil {
					log.Fatal("Failed to create dirs", err)
					panic(Exit{1})
				}
				file, err := os.Create(localFilepath)
				if err != nil {
					log.Fatal("Failed to create file", err)
					panic(Exit{1})
				}
				defer file.Close()

				// Download from S3
				fmt.Println("Downloading from S3", *key.Key, "to", localFilepath)
				s3Downloader.Download(file,
					&s3.GetObjectInput{
						Bucket: aws.String(*s3Bucket),
						Key:    aws.String(*key.Key),
					})

				// Upload to GS
				// https://github.com/golang/build/blob/master/cmd/upload/upload.go
				fmt.Println("Uploading", localFilepath, "to GS at", *key.Key)
				w := gsClient.Bucket(*gsBucket).Object(*key.Key).NewWriter(gcpContext)
				writeToGS(file, w)

				// Delete local file
				fmt.Println("Removing", file.Name())
				os.Remove(file.Name())

				gsAttrs, gsErr := gsClient.Bucket(*gsBucket).Object(*key.Key).Attrs(gcpContext)
				if gsErr != nil || s3Size != gsAttrs.Size {
					log.Fatal("Upload failed")
					panic(Exit{1})
				}
			}
		} else {
			fmt.Println("Already in GS, skipping", *key.Key)
		}
	}

	fmt.Println("Amount transferred", bytefmt.ByteSize(amtTransferred))
}
<|endoftext|>


<commit_before>package main

import (
  "database/sql"
  _ "github.com/lib/pq"
	"fmt"
	"time"
	"os"
	"os/exec"
	"syscall"
	"strings"
	"github.com/hoisie/mustache"
)

type Piper struct {
	Prefix string
	// just need a couple methods
}

func (p Piper) Write(d []byte) (int, error) {
	log.Info("%s %s", p.Prefix, d)
	return len(d), nil
}

var	cmd	*exec.Cmd

var	running bool

// Listen on the action channel and perform the action
func ActionStart() error {
	running = false
	go func() {
		for {
			select {
			case act := <-actions:
				log.Info("[action] new action: " + act)
				doAction(act)
			}
		}
	}()

	return nil
}

// switch through the actions and perform the requested action
func doAction(act string) {
	switch act {
	case "kill":
		killDB()
	case "master":
		startMaster()
	case "slave":
		startSlave()
	case "single":
		startSingle()
	default:
		fmt.Println("i dont know what to do with this action: " + act)
	}
}

// Starts the database as a master node and sends its data file to the slave
func startMaster() {
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
	status.SetState("configuring")
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// delete recovery.conf
	destroyRecovery()
	// start the database
	status.SetState("starting")
	startDB()
	// connect to DB and tell it to start backup
  db, err := sql.Open("postgres", fmt.Sprintf("user=postgres sslmode=disable host=localhost port=%d", conf.PGPort))
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt establish Database connection (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }
  defer db.Close()

  _, err = db.Exec("select pg_start_backup('replication')")
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt start backup (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }
	// rsync my files over to the slave server
	status.SetState("syncing")
	self := myself()
	other, _ := Whois(otherRole(self))
	// rsync -a {{local_dir}} {{slave_ip}}:{{slave_dir}}
  sync := mustache.Render(conf.SyncCommand, map[string]string{"local_dir":conf.DataDir,"slave_ip":other.Ip,"slave_dir":other.DataDir})
  cmd := strings.Split(sync, " ")
  sc := exec.Command(cmd[0], cmd[1:]...)
	sc.Stdout = Piper{"[sync.stdout]"}
	sc.Stderr = Piper{"[sync.stderr]"}

	if err = sc.Run(); err != nil {
		log.Error("[action] sync failed.")
	}

	// connect to DB and tell it to stop backup
  _, err = db.Exec("select pg_stop_backup()")
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt start backup (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }

	// set postgresql.conf as master
	configurePGConf(true)

	// start refresh/restart server
	restartDB()

	// make sure slave is connected and in sync
	status.SetState("waiting")
  defer status.SetState("running")
	for {
	  rows, err := db.Query("SELECT application_name, client_addr, state, sync_state, pg_xlog_location_diff(pg_current_xlog_location(), sent_location) FROM pg_stat_replication")
	  if err != nil {

	  }
	  for rows.Next() {
      var name string
      var addr string
      var state string
      var sync string
      var behind int64
      err = rows.Scan(&name, &addr, &state, &sync, &behind)
      if err != nil { panic(err) }
      if behind > 0 {
      	log.Info("Sync is catching up (name:%s,address:%s,state:%s,sync:%s,behind:%d)", name, addr, state, sync, behind)
      } else {
      	return
      }
	  }
	  time.Sleep(time.Second)
	}

}

// Starts the database as a slave node after waiting for master to come online
func startSlave() {
	// wait for master server to be running
	status.SetState("waiting")
	self := myself()
	for {
		other, err := Whois(otherRole(self))
		if err != nil {
			log.Error("I have lost communication with the other server")
			status.SetState("master_lost")
			return
		}
		if other.State == "running" || other.State == "waiting" {
			break
		}
		time.Sleep(time.Second)
	}
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
  status.SetState("configuring")
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// set recovery.conf
	createRecovery()
	// start the database
	status.SetState("starting")
	startDB()
  status.SetState("running")
}

// Starts the database as a single node
func startSingle() {
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// delete recovery.conf
	destroyRecovery()
	// start the database
	startDB()
}

// this will kill the database that is running. reguardless of its current state
func killDB() {
	defer func() {
		status.SetState("down")
	}()
	// return if it was never created or up
	if cmd == nil {
		return
	}

	// db is no longer running
	if running == false {
		cmd = nil
		return
	}
	// if it is running kill it and wait for it to go down
	status.SetState("signaling")
	cmd.Process.Signal(syscall.SIGQUIT)

	// waiting for shutdown
	status.SetState("waiting")
	cmd.Wait()
	cmd = nil
}

func startDB() {
	// if we still happen to have a cmd running kill it
	if cmd != nil {
		killDB()
	}
	cmd = exec.Command("postgres", "-D", conf.DataDir)
	cmd.Stdout = Piper{"[postgres.stdout]"}
	cmd.Stderr = Piper{"[postgres.stderr]"}
	cmd.Start()
	running = true
	waiter(cmd)
	time.Sleep(10 * time.Second)
	if running == false {
		log.Fatal("I just started the database and it is not running")
		log.Close()
		os.Exit(1)
	}
}

func restartDB() {
	killDB()
	startDB()
}

func initDB() {
	if _, err := os.Stat(conf.DataDir+"/postgresql.conf"); os.IsNotExist(err) {
		init := exec.Command("initdb", conf.DataDir)
		init.Stdout = Piper{"[initdb.stdout]"}
		init.Stderr = Piper{"[initdb.stderr]"}

		if err = init.Run(); err != nil {
			log.Fatal("[action] initdb failed. Are you missing your postgresql.conf")
			log.Close()
			os.Exit(1)
		}
	}
}

func waiter(c *exec.Cmd) {
	c.Wait()
	running = false
}

<commit_msg>waiter has to be a go routine<commit_after>package main

import (
  "database/sql"
  _ "github.com/lib/pq"
	"fmt"
	"time"
	"os"
	"os/exec"
	"syscall"
	"strings"
	"github.com/hoisie/mustache"
)

type Piper struct {
	Prefix string
	// just need a couple methods
}

func (p Piper) Write(d []byte) (int, error) {
	log.Info("%s %s", p.Prefix, d)
	return len(d), nil
}

var	cmd	*exec.Cmd

var	running bool

// Listen on the action channel and perform the action
func ActionStart() error {
	running = false
	go func() {
		for {
			select {
			case act := <-actions:
				log.Info("[action] new action: " + act)
				doAction(act)
			}
		}
	}()

	return nil
}

// switch through the actions and perform the requested action
func doAction(act string) {
	switch act {
	case "kill":
		killDB()
	case "master":
		startMaster()
	case "slave":
		startSlave()
	case "single":
		startSingle()
	default:
		fmt.Println("i dont know what to do with this action: " + act)
	}
}

// Starts the database as a master node and sends its data file to the slave
func startMaster() {
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
	status.SetState("configuring")
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// delete recovery.conf
	destroyRecovery()
	// start the database
	status.SetState("starting")
	startDB()
	// connect to DB and tell it to start backup
  db, err := sql.Open("postgres", fmt.Sprintf("user=postgres sslmode=disable host=localhost port=%d", conf.PGPort))
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt establish Database connection (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }
  defer db.Close()

  _, err = db.Exec("select pg_start_backup('replication')")
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt start backup (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }
	// rsync my files over to the slave server
	status.SetState("syncing")
	self := myself()
	other, _ := Whois(otherRole(self))
	// rsync -a {{local_dir}} {{slave_ip}}:{{slave_dir}}
  sync := mustache.Render(conf.SyncCommand, map[string]string{"local_dir":conf.DataDir,"slave_ip":other.Ip,"slave_dir":other.DataDir})
  cmd := strings.Split(sync, " ")
  sc := exec.Command(cmd[0], cmd[1:]...)
	sc.Stdout = Piper{"[sync.stdout]"}
	sc.Stderr = Piper{"[sync.stderr]"}

	if err = sc.Run(); err != nil {
		log.Error("[action] sync failed.")
	}

	// connect to DB and tell it to stop backup
  _, err = db.Exec("select pg_stop_backup()")
  if err != nil {
  	log.Fatal("[action.startMaster] Couldnt start backup (%s)", err.Error())
  	log.Close()
  	os.Exit(1)
  }

	// set postgresql.conf as master
	configurePGConf(true)

	// start refresh/restart server
	restartDB()

	// make sure slave is connected and in sync
	status.SetState("waiting")
  defer status.SetState("running")
	for {
	  rows, err := db.Query("SELECT application_name, client_addr, state, sync_state, pg_xlog_location_diff(pg_current_xlog_location(), sent_location) FROM pg_stat_replication")
	  if err != nil {

	  }
	  for rows.Next() {
      var name string
      var addr string
      var state string
      var sync string
      var behind int64
      err = rows.Scan(&name, &addr, &state, &sync, &behind)
      if err != nil { panic(err) }
      if behind > 0 {
      	log.Info("Sync is catching up (name:%s,address:%s,state:%s,sync:%s,behind:%d)", name, addr, state, sync, behind)
      } else {
      	return
      }
	  }
	  time.Sleep(time.Second)
	}

}

// Starts the database as a slave node after waiting for master to come online
func startSlave() {
	// wait for master server to be running
	status.SetState("waiting")
	self := myself()
	for {
		other, err := Whois(otherRole(self))
		if err != nil {
			log.Error("I have lost communication with the other server")
			status.SetState("master_lost")
			return
		}
		if other.State == "running" || other.State == "waiting" {
			break
		}
		time.Sleep(time.Second)
	}
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
  status.SetState("configuring")
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// set recovery.conf
	createRecovery()
	// start the database
	status.SetState("starting")
	startDB()
  status.SetState("running")
}

// Starts the database as a single node
func startSingle() {
	// make sure we have a database in the data folder
	initDB()
	// set postgresql.conf as not master
	configurePGConf(false)
	// set pg_hba.conf
	configureHBAConf()
	// delete recovery.conf
	destroyRecovery()
	// start the database
	startDB()
}

// this will kill the database that is running. reguardless of its current state
func killDB() {
	defer func() {
		status.SetState("down")
	}()
	// return if it was never created or up
	if cmd == nil {
		return
	}

	// db is no longer running
	if running == false {
		cmd = nil
		return
	}
	// if it is running kill it and wait for it to go down
	status.SetState("signaling")
	cmd.Process.Signal(syscall.SIGQUIT)

	// waiting for shutdown
	status.SetState("waiting")
	cmd.Wait()
	cmd = nil
}

func startDB() {
	// if we still happen to have a cmd running kill it
	if cmd != nil {
		killDB()
	}
	cmd = exec.Command("postgres", "-D", conf.DataDir)
	cmd.Stdout = Piper{"[postgres.stdout]"}
	cmd.Stderr = Piper{"[postgres.stderr]"}
	cmd.Start()
	running = true
	go waiter(cmd)
	time.Sleep(10 * time.Second)
	if running == false {
		log.Fatal("I just started the database and it is not running")
		log.Close()
		os.Exit(1)
	}
}

func restartDB() {
	killDB()
	startDB()
}

func initDB() {
	if _, err := os.Stat(conf.DataDir+"/postgresql.conf"); os.IsNotExist(err) {
		init := exec.Command("initdb", conf.DataDir)
		init.Stdout = Piper{"[initdb.stdout]"}
		init.Stderr = Piper{"[initdb.stderr]"}

		if err = init.Run(); err != nil {
			log.Fatal("[action] initdb failed. Are you missing your postgresql.conf")
			log.Close()
			os.Exit(1)
		}
	}
}

func waiter(c *exec.Cmd) {
	c.Wait()
	running = false
}

<|endoftext|>


<commit_before>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

package mango

type mangoAction int

const (
	actionEvents mangoAction = iota
	actionAllUsers

	actionCreateNaturalUser
	actionEditNaturalUser
	actionFetchNaturalUser

	actionCreateLegalUser
	actionEditLegalUser
	actionFetchLegalUser

	actionFetchUser
	actionFetchUserTransfers
	actionFetchUserWallets
	actionFetchUserCards
	actionFetchUserBankAccounts

	actionCreateWallet
	actionEditWallet
	actionFetchWallet
	actionFetchWalletTransactions

	actionCreateTransfer
	actionFetchTransfer

	actionFetchPayIn
	actionCreateWebPayIn
	actionCreateDirectPayIn
	actionCreateBankwireDirectPayIn
	actionCreateDirectDebitWebPayIn

	actionCreateCardRegistration
	actionSendCardRegistrationData

	actionFetchCard

	actionCreateTransferRefund
	actionCreatePayInRefund
	actionFetchRefund

	actionCreateBankAccount
	actionFetchBankAccount

	actionCreatePayOut
	actionFetchPayOut

	actionCreateKYCDocument
	actionFetchKYCDocument
	actionSubmitKYCDocument
	actionCreateKYCPage
	actionFetchUserKYCDocuments
	actionFetchAllKYCDocuments

	actionCreateHook
	actionUpdateHook
	actionFetchHook
	actionFetchAllHooks
)

// JsonObject is used to manage JSON data.
type JsonObject map[string]interface{}

type mangoRequest struct {
	Method, Path string
	PathValues   JsonObject
}

// Defines mango requests metadata.
var mangoRequests = map[mangoAction]mangoRequest{
	actionEvents: mangoRequest{
		"GET",
		"/events",
		nil,
	},
	actionCreateNaturalUser: mangoRequest{
		"POST",
		"/users/natural",
		nil,
	},
	actionEditNaturalUser: mangoRequest{
		"PUT",
		"/users/natural/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionAllUsers: mangoRequest{
		"GET",
		"/users",
		nil,
	},
	actionFetchNaturalUser: mangoRequest{
		"GET",
		"/users/natural/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateLegalUser: mangoRequest{
		"POST",
		"/users/legal",
		nil,
	},
	actionEditLegalUser: mangoRequest{
		"PUT",
		"/users/legal/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchLegalUser: mangoRequest{
		"GET",
		"/users/legal/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchUser: mangoRequest{
		"GET",
		"/users/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchUserTransfers: mangoRequest{
		"GET",
		"/users/{{Id}}/transactions",
		JsonObject{"Id": ""},
	},
	actionFetchUserWallets: mangoRequest{
		"GET",
		"/users/{{Id}}/wallets",
		JsonObject{"Id": ""},
	},
	actionFetchUserCards: mangoRequest{
		"GET",
		"/users/{{Id}}/cards",
		JsonObject{"Id": ""},
	},
	actionCreateWallet: mangoRequest{
		"POST",
		"/wallets",
		nil,
	},
	actionEditWallet: mangoRequest{
		"PUT",
		"/wallets/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchWallet: mangoRequest{
		"GET",
		"/wallets/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchWalletTransactions: mangoRequest{
		"GET",
		"/wallets/{{Id}}/transactions",
		JsonObject{"Id": ""},
	},
	actionCreateTransfer: mangoRequest{
		"POST",
		"/transfers",
		nil,
	},
	actionFetchTransfer: mangoRequest{
		"GET",
		"/transfers/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchPayIn: mangoRequest{
		"GET",
		"/payins/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateWebPayIn: mangoRequest{
		"POST",
		"/payins/card/web",
		nil,
	},
	actionCreateDirectPayIn: mangoRequest{
		"POST",
		"/payins/card/direct",
		nil,
	},
	actionCreateBankwireDirectPayIn: mangoRequest{
		"POST",
		"/payins/bankwire/direct",
		nil,
	},
	actionCreateDirectDebitWebPayIn: mangoRequest{
		"POST",
		"/payins/directdebit/web",
		nil,
	},
	actionCreateCardRegistration: mangoRequest{
		"POST",
		"/cardregistrations",
		nil,
	},
	actionSendCardRegistrationData: mangoRequest{
		"PUT",
		"/CardRegistrations/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchCard: mangoRequest{
		"GET",
		"/cards/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateTransferRefund: mangoRequest{
		"POST",
		"/transfers/{{TransferId}}/refunds",
		JsonObject{"TransferId": ""},
	},
	actionCreatePayInRefund: mangoRequest{
		"POST",
		"/payins/{{PayInId}}/refunds",
		JsonObject{"PayInId": ""},
	},
	actionFetchRefund: mangoRequest{
		"GET",
		"/refunds/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateBankAccount: mangoRequest{
		"POST",
		"/users/{{UserId}}/bankaccounts/{{Type}}",
		JsonObject{"UserId": "", "Type": ""},
	},
	actionFetchBankAccount: mangoRequest{
		"GET",
		"/users/{{UserId}}/bankaccounts/{{Id}}",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionFetchUserBankAccounts: mangoRequest{
		"GET",
		"/users/{{Id}}/bankaccounts",
		JsonObject{"Id": ""},
	},
	actionCreatePayOut: mangoRequest{
		"POST",
		"/payouts/bankwire",
		nil,
	},
	actionFetchPayOut: mangoRequest{
		"GET",
		"/payouts/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateKYCDocument: mangoRequest{
		"POST",
		"/users/{{UserId}}/kyc/documents",
		JsonObject{"UserId": ""},
	},
	actionFetchKYCDocument: mangoRequest{
		"GET",
		"kyc/documents/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionSubmitKYCDocument: mangoRequest{
		"PUT",
		"/users/{{UserId}}/kyc/documents/{{Id}}",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionCreateKYCPage: mangoRequest{
		"POST",
		"/users/{{UserId}}/kyc/documents/{{Id}}/pages",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionFetchUserKYCDocuments: mangoRequest{
		"GET",
		"/users/{{UserId}}/kyc/documents",
		JsonObject{"UserId": ""},
	},
	actionFetchAllKYCDocuments: mangoRequest{
		"GET",
		"/kyc/documents",
		nil,
	},

	actionCreateHook: mangoRequest{
		"POST",
		"/hooks",
		nil,
	},
	actionUpdateHook: mangoRequest{
		"PUT",
		"/hooks/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchHook: mangoRequest{
		"GET",
		"/hooks/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchAllHooks: mangoRequest{
		"GET",
		"/hooks/",
		nil,
	},
}
<commit_msg>Fix KYC document fetch<commit_after>// Copyright 2014 Mathias Monnerville. All rights reserved.
// Use of this source code is governed by a GPL
// license that can be found in the LICENSE file.

package mango

type mangoAction int

const (
	actionEvents mangoAction = iota
	actionAllUsers

	actionCreateNaturalUser
	actionEditNaturalUser
	actionFetchNaturalUser

	actionCreateLegalUser
	actionEditLegalUser
	actionFetchLegalUser

	actionFetchUser
	actionFetchUserTransfers
	actionFetchUserWallets
	actionFetchUserCards
	actionFetchUserBankAccounts

	actionCreateWallet
	actionEditWallet
	actionFetchWallet
	actionFetchWalletTransactions

	actionCreateTransfer
	actionFetchTransfer

	actionFetchPayIn
	actionCreateWebPayIn
	actionCreateDirectPayIn
	actionCreateBankwireDirectPayIn
	actionCreateDirectDebitWebPayIn

	actionCreateCardRegistration
	actionSendCardRegistrationData

	actionFetchCard

	actionCreateTransferRefund
	actionCreatePayInRefund
	actionFetchRefund

	actionCreateBankAccount
	actionFetchBankAccount

	actionCreatePayOut
	actionFetchPayOut

	actionCreateKYCDocument
	actionFetchKYCDocument
	actionSubmitKYCDocument
	actionCreateKYCPage
	actionFetchUserKYCDocuments
	actionFetchAllKYCDocuments

	actionCreateHook
	actionUpdateHook
	actionFetchHook
	actionFetchAllHooks
)

// JsonObject is used to manage JSON data.
type JsonObject map[string]interface{}

type mangoRequest struct {
	Method, Path string
	PathValues   JsonObject
}

// Defines mango requests metadata.
var mangoRequests = map[mangoAction]mangoRequest{
	actionEvents: {
		"GET",
		"/events",
		nil,
	},
	actionCreateNaturalUser: {
		"POST",
		"/users/natural",
		nil,
	},
	actionEditNaturalUser: {
		"PUT",
		"/users/natural/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionAllUsers: {
		"GET",
		"/users",
		nil,
	},
	actionFetchNaturalUser: {
		"GET",
		"/users/natural/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateLegalUser: {
		"POST",
		"/users/legal",
		nil,
	},
	actionEditLegalUser: {
		"PUT",
		"/users/legal/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchLegalUser: {
		"GET",
		"/users/legal/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchUser: {
		"GET",
		"/users/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchUserTransfers: {
		"GET",
		"/users/{{Id}}/transactions",
		JsonObject{"Id": ""},
	},
	actionFetchUserWallets: {
		"GET",
		"/users/{{Id}}/wallets",
		JsonObject{"Id": ""},
	},
	actionFetchUserCards: {
		"GET",
		"/users/{{Id}}/cards",
		JsonObject{"Id": ""},
	},
	actionCreateWallet: {
		"POST",
		"/wallets",
		nil,
	},
	actionEditWallet: {
		"PUT",
		"/wallets/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchWallet: {
		"GET",
		"/wallets/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchWalletTransactions: {
		"GET",
		"/wallets/{{Id}}/transactions",
		JsonObject{"Id": ""},
	},
	actionCreateTransfer: {
		"POST",
		"/transfers",
		nil,
	},
	actionFetchTransfer: {
		"GET",
		"/transfers/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchPayIn: {
		"GET",
		"/payins/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateWebPayIn: {
		"POST",
		"/payins/card/web",
		nil,
	},
	actionCreateDirectPayIn: {
		"POST",
		"/payins/card/direct",
		nil,
	},
	actionCreateBankwireDirectPayIn: {
		"POST",
		"/payins/bankwire/direct",
		nil,
	},
	actionCreateDirectDebitWebPayIn: {
		"POST",
		"/payins/directdebit/web",
		nil,
	},
	actionCreateCardRegistration: {
		"POST",
		"/cardregistrations",
		nil,
	},
	actionSendCardRegistrationData: {
		"PUT",
		"/CardRegistrations/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchCard: {
		"GET",
		"/cards/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateTransferRefund: {
		"POST",
		"/transfers/{{TransferId}}/refunds",
		JsonObject{"TransferId": ""},
	},
	actionCreatePayInRefund: {
		"POST",
		"/payins/{{PayInId}}/refunds",
		JsonObject{"PayInId": ""},
	},
	actionFetchRefund: {
		"GET",
		"/refunds/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateBankAccount: {
		"POST",
		"/users/{{UserId}}/bankaccounts/{{Type}}",
		JsonObject{"UserId": "", "Type": ""},
	},
	actionFetchBankAccount: {
		"GET",
		"/users/{{UserId}}/bankaccounts/{{Id}}",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionFetchUserBankAccounts: {
		"GET",
		"/users/{{Id}}/bankaccounts",
		JsonObject{"Id": ""},
	},
	actionCreatePayOut: {
		"POST",
		"/payouts/bankwire",
		nil,
	},
	actionFetchPayOut: {
		"GET",
		"/payouts/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionCreateKYCDocument: {
		"POST",
		"/users/{{UserId}}/kyc/documents",
		JsonObject{"UserId": ""},
	},
	actionFetchKYCDocument: {
		"GET",
		"/kyc/documents/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionSubmitKYCDocument: {
		"PUT",
		"/users/{{UserId}}/kyc/documents/{{Id}}",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionCreateKYCPage: {
		"POST",
		"/users/{{UserId}}/kyc/documents/{{Id}}/pages",
		JsonObject{"UserId": "", "Id": ""},
	},
	actionFetchUserKYCDocuments: {
		"GET",
		"/users/{{UserId}}/kyc/documents",
		JsonObject{"UserId": ""},
	},
	actionFetchAllKYCDocuments: {
		"GET",
		"/kyc/documents",
		nil,
	},

	actionCreateHook: {
		"POST",
		"/hooks",
		nil,
	},
	actionUpdateHook: {
		"PUT",
		"/hooks/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchHook: {
		"GET",
		"/hooks/{{Id}}",
		JsonObject{"Id": ""},
	},
	actionFetchAllHooks: {
		"GET",
		"/hooks/",
		nil,
	},
}
<|endoftext|>


<commit_before>package activitypub

import (
	"errors"
	as "github.com/go-ap/activitystreams"
)

// Endpoints a json object which maps additional (typically server/domain-wide)
// endpoints which may be useful either for this actor or someone referencing this actor.
// This mapping may be nested inside the actor document as the value or may be a link to
// a JSON-LD document with these properties.
type Endpoints struct {
	// UploadMedia Upload endpoint URI for this user for binary data.
	UploadMedia as.Item `jsonld:"uploadMedia,omitempty"`
	// OauthAuthorizationEndpoint Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication
	// to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being
	// the id of the requested ActivityStreams object.
	OauthAuthorizationEndpoint as.Item `jsonld:"oauthAuthorizationEndpoint,omitempty"`
	// OauthTokenEndpoint If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
	// this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
	OauthTokenEndpoint as.Item `jsonld:"oauthTokenEndpoint,omitempty"`
	// ProvideClientKey  If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
	// this endpoint specifies a URI at which a client may acquire an access token.
	ProvideClientKey as.Item `jsonld:"provideClientKey,omitempty"`
	// SignClientKey If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
	// this endpoint specifies a URI at which browser-authenticated users may authorize a client's public
	// key for client to server interactions.
	SignClientKey as.Item `jsonld:"signClientKey,omitempty"`
	// SharedInbox If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
	// this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to
	// act on behalf of the actor in interacting with foreign servers.
	SharedInbox as.Item `jsonld:"sharedInbox,omitempty"`
}

// Actor is generally one of the ActivityStreams actor Types, but they don't have to be.
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
// Actors are retrieved like any other Object in ActivityPub.
// Like other ActivityStreams objects, actors have an id, which is a URI.
type actor struct {
	as.Parent
	// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor;
	// see 5.2 Inbox.
	Inbox as.Item `jsonld:"inbox,omitempty"`
	// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor;
	// see 5.1 Outbox.
	Outbox as.Item `jsonld:"outbox,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that this actor is following;
	// see 5.4 Following Collection
	Following as.Item `jsonld:"following,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.3 Followers Collection.
	Followers as.Item `jsonld:"followers,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.5 Liked Collection.
	Liked as.Item `jsonld:"liked,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.7 Likes Collection.
	Likes as.Item `jsonld:"likes,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.8 Shares Collection
	Shares as.Item `jsonld:"shares,omitempty"`
	// A short username which may be used to refer to the actor, with no uniqueness guarantees.
	PreferredUsername as.NaturalLanguageValues `jsonld:"preferredUsername,omitempty,collapsible"`
	// A json object which maps additional (typically server/domain-wide) endpoints which may be useful either
	// for this actor or someone referencing this actor.
	// This mapping may be nested inside the actor document as the value or may be a link
	// to a JSON-LD document with these properties.
	Endpoints *Endpoints `jsonld:"endpoints,omitempty"`
	// A list of supplementary Collections which may be of interest.
	Streams []as.ItemCollection `jsonld:"streams,omitempty"`
}

type (
	// Application describes a software application.
	Application = actor

	// Group represents a formal or informal collective of Actors.
	Group = actor

	// Organization represents an organization.
	Organization = actor

	// Person represents an individual person.
	Person = actor

	// Service represents a service of any kind.
	Service = actor
)

// actorNew initializes an actor type actor
func actorNew(id as.ObjectID, typ as.ActivityVocabularyType) *actor {
	if !as.ActorTypes.Contains(typ) {
		typ = as.ActorType
	}

	a := actor{Parent: as.Object{ID: id, Type: typ}}
	a.Name = as.NaturalLanguageValuesNew()
	a.Content = as.NaturalLanguageValuesNew()
	a.Summary = as.NaturalLanguageValuesNew()
	in := as.OrderedCollectionNew(as.ObjectID("test-inbox"))
	out := as.OrderedCollectionNew(as.ObjectID("test-outbox"))
	liked := as.OrderedCollectionNew(as.ObjectID("test-liked"))

	a.Inbox = in
	a.Outbox = out
	a.Liked = liked
	a.PreferredUsername = as.NaturalLanguageValuesNew()

	return &a
}

// ApplicationNew initializes an Application type actor
func ApplicationNew(id as.ObjectID) *Application {
	return actorNew(id, as.ApplicationType)
}

// GroupNew initializes a Group type actor
func GroupNew(id as.ObjectID) *Group {
	return actorNew(id, as.GroupType)
}

// OrganizationNew initializes an Organization type actor
func OrganizationNew(id as.ObjectID) *Organization {
	return actorNew(id, as.OrganizationType)
}

// PersonNew initializes a Person type actor
func PersonNew(id as.ObjectID) *Person {
	return actorNew(id, as.PersonType)
}

// ServiceNew initializes a Service type actor
func ServiceNew(id as.ObjectID) *Service {
	return actorNew(id, as.ServiceType)
}

func (a *actor) UnmarshalJSON(data []byte) error {
	if as.ItemTyperFunc == nil {
		as.ItemTyperFunc = JSONGetItemByType
	}
	a.Parent.UnmarshalJSON(data)
	a.PreferredUsername = as.JSONGetNaturalLanguageField(data, "preferredUsername")
	a.Followers = as.JSONGetItem(data, "followers")
	a.Following = as.JSONGetItem(data, "following")
	a.Inbox = as.JSONGetItem(data, "inbox")
	a.Outbox = as.JSONGetItem(data, "outbox")
	a.Liked = as.JSONGetItem(data, "liked")
	a.Endpoints = JSONGetActorEndpoints(data, "endpoints")
	// TODO(marius): Streams needs custom unmarshalling
	//a.Streams = as.JSONGetItems(data, "streams")
	return nil
}

// ToObject
func ToPerson(it as.Item) (*Person, error) {
	switch i := it.(type) {
	case *as.Object:
		return &Person{Parent: *i}, nil
	case as.Object:
		return &Person{Parent: i}, nil
	case *actor:
		return i, nil
	case actor:
		return &i, nil
	}
	return nil, errors.New("unable to convert object")
}

// UnmarshalJSON
func (e *Endpoints) UnmarshalJSON(data []byte) error {
	e.OauthAuthorizationEndpoint = as.JSONGetItem(data, "oauthAuthorizationEndpoint")
	e.OauthTokenEndpoint = as.JSONGetItem(data, "oauthTokenEndpoint")
	e.UploadMedia = as.JSONGetItem(data, "uploadMedia")
	e.ProvideClientKey = as.JSONGetItem(data, "provideClientKey")
	e.SignClientKey = as.JSONGetItem(data, "signClientKey")
	e.SharedInbox = as.JSONGetItem(data, "sharedInbox")
	return nil
}
<commit_msg>Corrected hinted ToPerson comment<commit_after>package activitypub

import (
	"errors"
	as "github.com/go-ap/activitystreams"
)

// Endpoints a json object which maps additional (typically server/domain-wide)
// endpoints which may be useful either for this actor or someone referencing this actor.
// This mapping may be nested inside the actor document as the value or may be a link to
// a JSON-LD document with these properties.
type Endpoints struct {
	// UploadMedia Upload endpoint URI for this user for binary data.
	UploadMedia as.Item `jsonld:"uploadMedia,omitempty"`
	// OauthAuthorizationEndpoint Endpoint URI so this actor's clients may access remote ActivityStreams objects which require authentication
	// to access. To use this endpoint, the client posts an x-www-form-urlencoded id parameter with the value being
	// the id of the requested ActivityStreams object.
	OauthAuthorizationEndpoint as.Item `jsonld:"oauthAuthorizationEndpoint,omitempty"`
	// OauthTokenEndpoint If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
	// this endpoint specifies a URI at which a browser-authenticated user may obtain a new authorization grant.
	OauthTokenEndpoint as.Item `jsonld:"oauthTokenEndpoint,omitempty"`
	// ProvideClientKey  If OAuth 2.0 bearer tokens [RFC6749] [RFC6750] are being used for authenticating client to server interactions,
	// this endpoint specifies a URI at which a client may acquire an access token.
	ProvideClientKey as.Item `jsonld:"provideClientKey,omitempty"`
	// SignClientKey If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
	// this endpoint specifies a URI at which browser-authenticated users may authorize a client's public
	// key for client to server interactions.
	SignClientKey as.Item `jsonld:"signClientKey,omitempty"`
	// SharedInbox If Linked Data Signatures and HTTP Signatures are being used for authentication and authorization,
	// this endpoint specifies a URI at which a client key may be signed by the actor's key for a time window to
	// act on behalf of the actor in interacting with foreign servers.
	SharedInbox as.Item `jsonld:"sharedInbox,omitempty"`
}

// Actor is generally one of the ActivityStreams actor Types, but they don't have to be.
// For example, a Profile object might be used as an actor, or a type from an ActivityStreams extension.
// Actors are retrieved like any other Object in ActivityPub.
// Like other ActivityStreams objects, actors have an id, which is a URI.
type actor struct {
	as.Parent
	// A reference to an [ActivityStreams] OrderedCollection comprised of all the messages received by the actor;
	// see 5.2 Inbox.
	Inbox as.Item `jsonld:"inbox,omitempty"`
	// An [ActivityStreams] OrderedCollection comprised of all the messages produced by the actor;
	// see 5.1 Outbox.
	Outbox as.Item `jsonld:"outbox,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that this actor is following;
	// see 5.4 Following Collection
	Following as.Item `jsonld:"following,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.3 Followers Collection.
	Followers as.Item `jsonld:"followers,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.5 Liked Collection.
	Liked as.Item `jsonld:"liked,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.7 Likes Collection.
	Likes as.Item `jsonld:"likes,omitempty"`
	// A link to an [ActivityStreams] collection of the actors that follow this actor;
	// see 5.8 Shares Collection
	Shares as.Item `jsonld:"shares,omitempty"`
	// A short username which may be used to refer to the actor, with no uniqueness guarantees.
	PreferredUsername as.NaturalLanguageValues `jsonld:"preferredUsername,omitempty,collapsible"`
	// A json object which maps additional (typically server/domain-wide) endpoints which may be useful either
	// for this actor or someone referencing this actor.
	// This mapping may be nested inside the actor document as the value or may be a link
	// to a JSON-LD document with these properties.
	Endpoints *Endpoints `jsonld:"endpoints,omitempty"`
	// A list of supplementary Collections which may be of interest.
	Streams []as.ItemCollection `jsonld:"streams,omitempty"`
}

type (
	// Application describes a software application.
	Application = actor

	// Group represents a formal or informal collective of Actors.
	Group = actor

	// Organization represents an organization.
	Organization = actor

	// Person represents an individual person.
	Person = actor

	// Service represents a service of any kind.
	Service = actor
)

// actorNew initializes an actor type actor
func actorNew(id as.ObjectID, typ as.ActivityVocabularyType) *actor {
	if !as.ActorTypes.Contains(typ) {
		typ = as.ActorType
	}

	a := actor{Parent: as.Object{ID: id, Type: typ}}
	a.Name = as.NaturalLanguageValuesNew()
	a.Content = as.NaturalLanguageValuesNew()
	a.Summary = as.NaturalLanguageValuesNew()
	in := as.OrderedCollectionNew(as.ObjectID("test-inbox"))
	out := as.OrderedCollectionNew(as.ObjectID("test-outbox"))
	liked := as.OrderedCollectionNew(as.ObjectID("test-liked"))

	a.Inbox = in
	a.Outbox = out
	a.Liked = liked
	a.PreferredUsername = as.NaturalLanguageValuesNew()

	return &a
}

// ApplicationNew initializes an Application type actor
func ApplicationNew(id as.ObjectID) *Application {
	return actorNew(id, as.ApplicationType)
}

// GroupNew initializes a Group type actor
func GroupNew(id as.ObjectID) *Group {
	return actorNew(id, as.GroupType)
}

// OrganizationNew initializes an Organization type actor
func OrganizationNew(id as.ObjectID) *Organization {
	return actorNew(id, as.OrganizationType)
}

// PersonNew initializes a Person type actor
func PersonNew(id as.ObjectID) *Person {
	return actorNew(id, as.PersonType)
}

// ServiceNew initializes a Service type actor
func ServiceNew(id as.ObjectID) *Service {
	return actorNew(id, as.ServiceType)
}

func (a *actor) UnmarshalJSON(data []byte) error {
	if as.ItemTyperFunc == nil {
		as.ItemTyperFunc = JSONGetItemByType
	}
	a.Parent.UnmarshalJSON(data)
	a.PreferredUsername = as.JSONGetNaturalLanguageField(data, "preferredUsername")
	a.Followers = as.JSONGetItem(data, "followers")
	a.Following = as.JSONGetItem(data, "following")
	a.Inbox = as.JSONGetItem(data, "inbox")
	a.Outbox = as.JSONGetItem(data, "outbox")
	a.Liked = as.JSONGetItem(data, "liked")
	a.Endpoints = JSONGetActorEndpoints(data, "endpoints")
	// TODO(marius): Streams needs custom unmarshalling
	//a.Streams = as.JSONGetItems(data, "streams")
	return nil
}

// ToPerson
func ToPerson(it as.Item) (*Person, error) {
	switch i := it.(type) {
	case *as.Object:
		return &Person{Parent: *i}, nil
	case as.Object:
		return &Person{Parent: i}, nil
	case *actor:
		return i, nil
	case actor:
		return &i, nil
	}
	return nil, errors.New("unable to convert object")
}

// UnmarshalJSON
func (e *Endpoints) UnmarshalJSON(data []byte) error {
	e.OauthAuthorizationEndpoint = as.JSONGetItem(data, "oauthAuthorizationEndpoint")
	e.OauthTokenEndpoint = as.JSONGetItem(data, "oauthTokenEndpoint")
	e.UploadMedia = as.JSONGetItem(data, "uploadMedia")
	e.ProvideClientKey = as.JSONGetItem(data, "provideClientKey")
	e.SignClientKey = as.JSONGetItem(data, "signClientKey")
	e.SharedInbox = as.JSONGetItem(data, "sharedInbox")
	return nil
}
<|endoftext|>