<commit_before>package forge_test

import (
	"testing"

	"github.com/brettlangdon/forge"
)

func TestNewPrimative(t *testing.T) {
	t.Parallel()

	value, err := forge.NewPrimative(true)
	if err != nil {
		t.Error(err)
		return
	}

	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case bool:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a bool")
		return
	}

	if rawVal.(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}
}

func TestNewBoolean(t *testing.T) {
	t.Parallel()

	value := forge.NewBoolean(true)

	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case bool:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a bool")
		return
	}

	if rawVal.(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}
}

func TestNewFloat(t *testing.T) {
	t.Parallel()

	value := forge.NewFloat(50.5)

	if value.GetType() != forge.FLOAT {
		t.Error("value is not a FLOAT")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case float64:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a float64")
		return
	}

	if rawVal.(float64) != 50.5 {
		t.Error("expected value.GetValue() to be equal to '50.5'")
	}
}

func TestNewInteger(t *testing.T) {
	t.Parallel()

	value := forge.NewInteger(50)

	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case int64:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a int64")
		return
	}

	if rawVal.(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}
}

func TestNewNull(t *testing.T) {
	t.Parallel()

	value := forge.NewNull()

	if value.GetType() != forge.NULL {
		t.Error("value is not a NULL")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case nil:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a nil")
		return
	}

	if rawVal != nil {
		t.Error("expected value.GetValue() to be equal to 'nil'")
	}
}

func TestNewString(t *testing.T) {
	t.Parallel()

	value := forge.NewString("hello")

	if value.GetType() != forge.STRING {
		t.Error("value is not a STRING")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case string:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a string")
		return
	}

	if rawVal.(string) != "hello" {
		t.Error("expected value.GetValue() to be equal to '\"hello\"'")
	}
}

func TestUpdateValue(t *testing.T) {
	t.Parallel()

	value := forge.NewNull()

	// Boolean
	err := value.UpdateValue(true)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}
	if value.GetValue().(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}

	// Float
	err = value.UpdateValue(50.5)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.FLOAT {
		t.Error("value is not a FLOAT")
		return
	}
	if value.GetValue().(float64) != 50.5 {
		t.Error("expected value.GetValue() to be equal to '50.5'")
	}

	// Integer int
	err = value.UpdateValue(50)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}
	if value.GetValue().(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// Integer int64
	err = value.UpdateValue(int64(50))
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}
	if value.GetValue().(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// Null
	err = value.UpdateValue(nil)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.NULL {
		t.Error("value is not a NULL")
		return
	}
	if value.GetValue() != nil {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// String
	err = value.UpdateValue("hello")
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.STRING {
		t.Error("value is not a STRING")
		return
	}
	if value.GetValue().(string) != "hello" {
		t.Error("expected value.GetValue() to be equal to '\"hello\"'")
		return
	}

}

func TestUpdateValueUnknown(t *testing.T) {
	value, err := forge.NewPrimative("hello")
	if err != nil {
		t.Error(err)
		return
	}

	newVal := []string{"hello", "world"}
	err = value.UpdateValue(newVal)
	if err == nil {
		t.Error("expected an error, got none")
		return
	}
}
<commit_msg>add more tests<commit_after>package forge_test

import (
	"testing"

	"github.com/brettlangdon/forge"
)

func TestNewPrimative(t *testing.T) {
	t.Parallel()

	value, err := forge.NewPrimative(true)
	if err != nil {
		t.Error(err)
		return
	}

	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case bool:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a bool")
		return
	}

	if rawVal.(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}
}

func TestNewBoolean(t *testing.T) {
	t.Parallel()

	value := forge.NewBoolean(true)

	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case bool:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a bool")
		return
	}

	if rawVal.(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}
}

func TestNewFloat(t *testing.T) {
	t.Parallel()

	value := forge.NewFloat(50.5)

	if value.GetType() != forge.FLOAT {
		t.Error("value is not a FLOAT")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case float64:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a float64")
		return
	}

	if rawVal.(float64) != 50.5 {
		t.Error("expected value.GetValue() to be equal to '50.5'")
	}
}

func TestNewInteger(t *testing.T) {
	t.Parallel()

	value := forge.NewInteger(50)

	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case int64:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a int64")
		return
	}

	if rawVal.(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}
}

func TestNewNull(t *testing.T) {
	t.Parallel()

	value := forge.NewNull()

	if value.GetType() != forge.NULL {
		t.Error("value is not a NULL")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case nil:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a nil")
		return
	}

	if rawVal != nil {
		t.Error("expected value.GetValue() to be equal to 'nil'")
	}
}

func TestNewString(t *testing.T) {
	t.Parallel()

	value := forge.NewString("hello")

	if value.GetType() != forge.STRING {
		t.Error("value is not a STRING")
		return
	}

	rawVal := value.GetValue()
	switch rawVal.(type) {
	case string:
		// this is what we want to happen
	default:
		t.Error("expected value.GetValue() to be a string")
		return
	}

	if rawVal.(string) != "hello" {
		t.Error("expected value.GetValue() to be equal to '\"hello\"'")
	}
}

func TestUpdateValue(t *testing.T) {
	t.Parallel()

	value := forge.NewNull()

	// Boolean
	err := value.UpdateValue(true)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.BOOLEAN {
		t.Error("value is not a BOOLEAN")
		return
	}
	if value.GetValue().(bool) != true {
		t.Error("expected value.GetValue() to be equal to 'true'")
	}

	// Float
	err = value.UpdateValue(50.5)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.FLOAT {
		t.Error("value is not a FLOAT")
		return
	}
	if value.GetValue().(float64) != 50.5 {
		t.Error("expected value.GetValue() to be equal to '50.5'")
	}

	// Integer int
	err = value.UpdateValue(50)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}
	if value.GetValue().(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// Integer int64
	err = value.UpdateValue(int64(50))
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.INTEGER {
		t.Error("value is not a INTEGER")
		return
	}
	if value.GetValue().(int64) != 50 {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// Null
	err = value.UpdateValue(nil)
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.NULL {
		t.Error("value is not a NULL")
		return
	}
	if value.GetValue() != nil {
		t.Error("expected value.GetValue() to be equal to '50'")
	}

	// String
	err = value.UpdateValue("hello")
	if err != nil {
		t.Error(err)
		return
	}
	if value.GetType() != forge.STRING {
		t.Error("value is not a STRING")
		return
	}
	if value.GetValue().(string) != "hello" {
		t.Error("expected value.GetValue() to be equal to '\"hello\"'")
		return
	}

}

func TestUpdateValueUnknown(t *testing.T) {
	t.Parallel()
	value, err := forge.NewPrimative("hello")
	if err != nil {
		t.Error(err)
		return
	}

	newVal := []string{"hello", "world"}
	err = value.UpdateValue(newVal)
	if err == nil {
		t.Error("expected an error, got none")
		return
	}
}

func TestAsBoolean(t *testing.T) {
	t.Parallel()

	// Boolean true
	value := forge.NewBoolean(true)
	val, err := value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != true {
		t.Error("expected value to be 'true'")
		return
	}

	// Boolean false
	err = value.UpdateValue(false)
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != false {
		t.Error("expected value to be 'false'")
		return
	}

	// Int true
	err = value.UpdateValue(1)
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != true {
		t.Error("expected value to be 'true'")
		return
	}

	// Int false
	err = value.UpdateValue(0)
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != false {
		t.Error("expected value to be 'false'")
		return
	}

	// Float true
	err = value.UpdateValue(float64(1))
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != true {
		t.Error("expected value to be 'true'")
		return
	}

	// Float false
	err = value.UpdateValue(float64(0))
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != false {
		t.Error("expected value to be 'false'")
		return
	}

	// Null true
	err = value.UpdateValue(nil)
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != false {
		t.Error("expected value to be 'false'")
		return
	}

	// String true
	err = value.UpdateValue("anything")
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != true {
		t.Error("expected value to be 'true'")
		return
	}

	// String false
	err = value.UpdateValue("")
	if err != nil {
		t.Error(err)
		return
	}
	val, err = value.AsBoolean()
	if err != nil {
		t.Error(err)
		return
	}
	if val != false {
		t.Error("expected value to be 'false'")
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"code.google.com/p/go-netrc/netrc"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"sort"
)

const (
	Version = "0.0.1"
)

func getCreds(machine string) (user, pass string) {
	m, err := netrc.FindMachine(os.Getenv("HOME")+"/.netrc", machine)
	if err != nil {
		panic(err)
	}

	return m.Login, m.Password
}

// generic api requests
func apiReq(v interface{}, meth string, url string) {
	req, err := http.NewRequest(meth, url, nil)
	if err != nil {
		panic(err)
	}

	req.SetBasicAuth(getCreds(req.Host))
	req.Header.Add("User-Agent", fmt.Sprintf("hk/%s", Version))
	req.Header.Add("Accept", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode == 401 {
		error("Unauthorized")
	}
	if res.StatusCode == 403 {
		error("Unauthorized")
	}
	if res.StatusCode != 200 {
		fmt.Printf("%v\n", res)
		error("Unexpected error")
	}

	err = json.NewDecoder(res.Body).Decode(v)
	if err != nil {
		panic(err)
	}
}

// error formatting
func error(msg string) {
	fmt.Fprintf(os.Stderr, "Error: %s.\n", msg)
	os.Exit(1)
}

func unrecArg(arg, cmd string) {
	error(fmt.Sprintf("Unrecognized argument '%s'. See 'hk help %s'", arg, cmd))
}

func unrecCmd(cmd string) {
	error(fmt.Sprintf("'%s' is not an hk command. See 'hk help'", cmd))
}

// info formatting
func cmdHelp(usage string, desc string) {
	fmt.Printf("Usage: %s\n\n", usage)
	fmt.Printf("%s.\n", desc)
	os.Exit(0)
}

// commands
func envHelp() {
	cmdHelp("hk env -a <app>", "Show all config vars")
}

func env() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help env'")
	}
	appName := os.Args[3]
	var config map[string]string
	apiReq(&config, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	for k, v := range config {
		fmt.Printf("%s=%s\n", k, v)
	}
	os.Exit(0)
}

func getHelp() {
	cmdHelp("hk get -a <app> <key>", "Get the value of a config var")
}

func get() {
	if (len(os.Args) != 5) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help get'")
	}
	appName := os.Args[3]
	key := os.Args[4]
	var config map[string]string
	apiReq(&config, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	value, found := config[key]
	if !found {
		error(fmt.Sprintf("No such key as '%s'", key))
	}
	fmt.Println(value)
	os.Exit(0)
}

func infoHelp() {
	cmdHelp("hk info -a <app>", "Show app info")
}

func info() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help info'")
	}
	appName := os.Args[3]
	var info struct {
		Name   string
		Owner  string `json:"owner_email"`
		Stack  string
		GitURL string `json:"git_url"`
		WebURL string `json:"web_url"`
	}
	apiReq(&info, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s", appName))
	fmt.Printf("Name:     %s\n", info.Name)
	fmt.Printf("Owner:    %s\n", info.Owner)
	fmt.Printf("Stack:    %s\n", info.Stack)
	fmt.Printf("Git URL:  %s\n", info.GitURL)
	fmt.Printf("Web URL:  %s\n", info.WebURL)
	os.Exit(0)
}

func credsHelp() {
	cmdHelp("hk creds", "Show API credentials")
}

func creds() {
	fmt.Println(getCreds("api.heroku.com"))
	os.Exit(0)
}

func listHelp() {
	cmdHelp("hk list", "List accessible apps")
}

func list() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2], "list")
	}
	var apps []struct{ Name string }
	apiReq(&apps, "GET", "https://api.heroku.com/apps")
	for _, app := range apps {
		fmt.Printf("%s\n", app.Name)
	}
	os.Exit(0)
}

func psHelp() {
	cmdHelp("hk ps -a <app>", "List app processes")
}

type Proc struct {
	Name    string `json:"process"`
	State   string
	Command string
}

type Procs []*Proc

func (p Procs) Len() int           { return len(p) }
func (p Procs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Procs) Less(i, j int) bool { return p[i].Name < p[j].Name }

func ps() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help ps'")
	}
	appName := os.Args[3]
	var procs Procs
	apiReq(&procs, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/ps", appName))
	sort.Sort(procs)
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for _, proc := range procs {
		fmt.Printf("%-16s  %-10s  %s\n", proc.Name, proc.State, proc.Command)
	}
	os.Exit(0)
}

func versionHelp() {
	cmdHelp("hk version", "Show hk client version")
}

func version() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2], "version")
	}
	fmt.Printf("%s\n", Version)
	os.Exit(0)
}

func help() {
	if len(os.Args) <= 2 {
		usage()
	} else {
		cmd := os.Args[2]
		switch cmd {
		case "env":
			envHelp()
		case "get":
			getHelp()
		case "info":
			infoHelp()
		case "creds":
			credsHelp()
		case "list":
			listHelp()
		case "ps":
			psHelp()
		case "version":
			versionHelp()
		}
		unrecCmd(cmd)
	}
}

// top-level usage
func usage() {
	fmt.Printf("Usage: hk <command> [-a <app>] [command-specific-options]\n\n")
	fmt.Printf("Supported hk commands are:\n")
	fmt.Printf("  addons          List add-ons\n")
	fmt.Printf("  addons-add      Add an add-on\n")
	fmt.Printf("  addons-open     Open an add-on page\n")
	fmt.Printf("  addons-remove   Remove an add-on \n")
	fmt.Printf("  create          Create an app\n")
	fmt.Printf("  destroy         Destroy an app\n")
	fmt.Printf("  env             List config vars\n")
	fmt.Printf("  get             Get config var\n")
	fmt.Printf("  help            Show this help\n")
	fmt.Printf("  info            Show app info\n")
	fmt.Printf("  list            List apps\n")
	fmt.Printf("  login           Log in\n")
	fmt.Printf("  logout          Log out\n")
	fmt.Printf("  logs            Show logs\n")
	fmt.Printf("  open            Open app\n")
	fmt.Printf("  pg              List databases\n")
	fmt.Printf("  pg-info         Show database info\n")
	fmt.Printf("  pg-promote      Promote a database\n")
	fmt.Printf("  ps-psql         Open a psql database shell\n")
	fmt.Printf("  pg-wait         Await a database\n")
	fmt.Printf("  ps              List processes\n")
	fmt.Printf("  release         Show release info\n")
	fmt.Printf("  releases        List releases\n")
	fmt.Printf("  rename          Rename an app\n")
	fmt.Printf("  restart         Restart processes\n")
	fmt.Printf("  rollback        Rollback to a previous release\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  set             Set config var\n")
	fmt.Printf("  scale           Scale processes\n")
	fmt.Printf("  stop            Stop a process\n")
	fmt.Printf("  creds           Show auth creds\n")
	fmt.Printf("  unset           Unset config vars\n")
	fmt.Printf("  version         Display version\n\n")
	fmt.Printf("See 'hk help <command>' for more information on a specific command.\n")
	os.Exit(0)
}

// entry point
func main() {
	if len(os.Args) <= 1 {
		usage()
	} else {
		cmd := os.Args[1]
		switch cmd {
		case "env":
			env()
		case "get":
			get()
		case "help":
			help()
		case "info":
			info()
		case "creds":
			creds()
		case "list":
			list()
		case "ps":
			ps()
		case "version":
			version()
		}
		unrecCmd(cmd)
	}
}
<commit_msg>refactor<commit_after>package main

import (
	"code.google.com/p/go-netrc/netrc"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"sort"
)

const (
	Version = "0.0.1"
)

func getCreds(machine string) (user, pass string) {
	m, err := netrc.FindMachine(os.Getenv("HOME")+"/.netrc", machine)
	if err != nil {
		panic(err)
	}

	return m.Login, m.Password
}

// generic api requests
func apiReq(v interface{}, meth string, url string) {
	req, err := http.NewRequest(meth, url, nil)
	if err != nil {
		panic(err)
	}

	req.SetBasicAuth(getCreds(req.Host))
	req.Header.Add("User-Agent", fmt.Sprintf("hk/%s", Version))
	req.Header.Add("Accept", "application/json")
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	if res.StatusCode == 401 {
		error("Unauthorized")
	}
	if res.StatusCode == 403 {
		error("Unauthorized")
	}
	if res.StatusCode != 200 {
		fmt.Printf("%v\n", res)
		error("Unexpected error")
	}

	err = json.NewDecoder(res.Body).Decode(v)
	if err != nil {
		panic(err)
	}
}

// error formatting
func error(msg string) {
	fmt.Fprintf(os.Stderr, "Error: %s.\n", msg)
	os.Exit(1)
}

func unrecArg(arg, cmd string) {
	error(fmt.Sprintf("Unrecognized argument '%s'. See 'hk help %s'", arg, cmd))
}

func unrecCmd(cmd string) {
	error(fmt.Sprintf("'%s' is not an hk command. See 'hk help'", cmd))
}

// info formatting
func cmdHelp(usage string, desc string) {
	fmt.Printf("Usage: %s\n\n", usage)
	fmt.Printf("%s.\n", desc)
}

// commands
func envHelp() {
	cmdHelp("hk env -a <app>", "Show all config vars")
}

func env() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help env'")
	}
	appName := os.Args[3]
	var config map[string]string
	apiReq(&config, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	for k, v := range config {
		fmt.Printf("%s=%s\n", k, v)
	}
}

func getHelp() {
	cmdHelp("hk get -a <app> <key>", "Get the value of a config var")
}

func get() {
	if (len(os.Args) != 5) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help get'")
	}
	appName := os.Args[3]
	key := os.Args[4]
	var config map[string]string
	apiReq(&config, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/config_vars", appName))
	value, found := config[key]
	if !found {
		error(fmt.Sprintf("No such key as '%s'", key))
	}
	fmt.Println(value)
}

func infoHelp() {
	cmdHelp("hk info -a <app>", "Show app info")
}

func info() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help info'")
	}
	appName := os.Args[3]
	var info struct {
		Name   string
		Owner  string `json:"owner_email"`
		Stack  string
		GitURL string `json:"git_url"`
		WebURL string `json:"web_url"`
	}
	apiReq(&info, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s", appName))
	fmt.Printf("Name:     %s\n", info.Name)
	fmt.Printf("Owner:    %s\n", info.Owner)
	fmt.Printf("Stack:    %s\n", info.Stack)
	fmt.Printf("Git URL:  %s\n", info.GitURL)
	fmt.Printf("Web URL:  %s\n", info.WebURL)
}

func credsHelp() {
	cmdHelp("hk creds", "Show API credentials")
}

func creds() {
	fmt.Println(getCreds("api.heroku.com"))
}

func listHelp() {
	cmdHelp("hk list", "List accessible apps")
}

func list() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2], "list")
	}
	var apps []struct{ Name string }
	apiReq(&apps, "GET", "https://api.heroku.com/apps")
	for _, app := range apps {
		fmt.Printf("%s\n", app.Name)
	}
}

func psHelp() {
	cmdHelp("hk ps -a <app>", "List app processes")
}

type Proc struct {
	Name    string `json:"process"`
	State   string
	Command string
}

type Procs []*Proc

func (p Procs) Len() int           { return len(p) }
func (p Procs) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Procs) Less(i, j int) bool { return p[i].Name < p[j].Name }

func ps() {
	if (len(os.Args) != 4) || (os.Args[2] != "-a") {
		error("Invalid usage. See 'hk help ps'")
	}
	appName := os.Args[3]
	var procs Procs
	apiReq(&procs, "GET", fmt.Sprintf("https://api.heroku.com/apps/%s/ps", appName))
	sort.Sort(procs)
	fmt.Printf("Process           State       Command\n")
	fmt.Printf("----------------  ----------  ------------------------\n")
	for _, proc := range procs {
		fmt.Printf("%-16s  %-10s  %s\n", proc.Name, proc.State, proc.Command)
	}
}

func versionHelp() {
	cmdHelp("hk version", "Show hk client version")
}

func version() {
	if len(os.Args) != 2 {
		unrecArg(os.Args[2], "version")
	}
	fmt.Printf("%s\n", Version)
}

func help() {
	if len(os.Args) <= 2 {
		usage()
	} else {
		cmd := os.Args[2]
		switch cmd {
		case "env":
			envHelp()
		case "get":
			getHelp()
		case "info":
			infoHelp()
		case "creds":
			credsHelp()
		case "list":
			listHelp()
		case "ps":
			psHelp()
		case "version":
			versionHelp()
		default:
			unrecCmd(cmd)
		}
	}
}

// top-level usage
func usage() {
	fmt.Printf("Usage: hk <command> [-a <app>] [command-specific-options]\n\n")
	fmt.Printf("Supported hk commands are:\n")
	fmt.Printf("  addons          List add-ons\n")
	fmt.Printf("  addons-add      Add an add-on\n")
	fmt.Printf("  addons-open     Open an add-on page\n")
	fmt.Printf("  addons-remove   Remove an add-on \n")
	fmt.Printf("  create          Create an app\n")
	fmt.Printf("  destroy         Destroy an app\n")
	fmt.Printf("  env             List config vars\n")
	fmt.Printf("  get             Get config var\n")
	fmt.Printf("  help            Show this help\n")
	fmt.Printf("  info            Show app info\n")
	fmt.Printf("  list            List apps\n")
	fmt.Printf("  login           Log in\n")
	fmt.Printf("  logout          Log out\n")
	fmt.Printf("  logs            Show logs\n")
	fmt.Printf("  open            Open app\n")
	fmt.Printf("  pg              List databases\n")
	fmt.Printf("  pg-info         Show database info\n")
	fmt.Printf("  pg-promote      Promote a database\n")
	fmt.Printf("  ps-psql         Open a psql database shell\n")
	fmt.Printf("  pg-wait         Await a database\n")
	fmt.Printf("  ps              List processes\n")
	fmt.Printf("  release         Show release info\n")
	fmt.Printf("  releases        List releases\n")
	fmt.Printf("  rename          Rename an app\n")
	fmt.Printf("  restart         Restart processes\n")
	fmt.Printf("  rollback        Rollback to a previous release\n")
	fmt.Printf("  run             Run a process\n")
	fmt.Printf("  set             Set config var\n")
	fmt.Printf("  scale           Scale processes\n")
	fmt.Printf("  stop            Stop a process\n")
	fmt.Printf("  creds           Show auth creds\n")
	fmt.Printf("  unset           Unset config vars\n")
	fmt.Printf("  version         Display version\n\n")
	fmt.Printf("See 'hk help <command>' for more information on a specific command.\n")
}

// entry point
func main() {
	if len(os.Args) <= 1 {
		usage()
	} else {
		cmd := os.Args[1]
		switch cmd {
		case "env":
			env()
		case "get":
			get()
		case "help":
			help()
		case "info":
			info()
		case "creds":
			creds()
		case "list":
			list()
		case "ps":
			ps()
		case "version":
			version()
		default:
			unrecCmd(cmd)
		}
	}
}
<|endoftext|>


<commit_before>/*
Usage: processor command

Commands:
	detect-usable-apps - Detect usable apps
	detect-unusable-apps - Detect unusable apps
	cleanup - Usage history cleanup
*/
package main

import (
	"fmt"
	"log"
	"os"
	"sync"

	"github.com/tsukanov/steamhistory/collector/steam"
	"github.com/tsukanov/steamhistory/storage/apps"
	"github.com/tsukanov/steamhistory/storage/history"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("No argument! See documentation to find out how to use this application.")
		return
	}

	switch os.Args[1] {

	case "detect-unusable-apps":
		log.Println("Detecting unusable apps...")
		err := detectUnusableApps()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Detection completed!")

	case "detect-usable-apps":
		log.Println("Detecting usable apps...")
		err := detectUsableApps()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Detection completed!")

	case "cleanup":
		log.Println("Doing history cleanup...")
		err := history.HistoryCleanup()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("History cleanup completed!")

	default:
		fmt.Println("Unknown command! See documentation.")
	}
}

// detectUnusableApps finds applications that have no active users and marks
// them as unusable.
func detectUnusableApps() error {
	applications, err := apps.AllUsableApps()
	if err != nil {
		return err
	}

	for _, app := range applications {
		db, err := history.OpenAppUsageDB(app.ID)
		if err != nil {
			log.Println(app, err)
			continue
		}

		rows, err := db.Query("SELECT count(*), avg(count) FROM records")
		if err != nil {
			log.Println(err)
			continue
		}
		var count int
		var avg float32
		rows.Next()
		err = rows.Scan(&count, &avg)
		rows.Close()
		if err != nil {
			log.Println(err)
			continue
		}
		if count > 10 && avg < 1 {
			err = apps.MarkAppAsUnusable(app.ID)
			log.Println(fmt.Sprintf("Marked app %s (%d) as unusable.", app.Name, app.ID))
			if err != nil {
				log.Println(err)
				continue
			}
			// Removing history
			err = history.RemoveAppUsageDB(app.ID)
			if err != nil {
				log.Println(err)
			}
		}

		db.Close()
	}
	return nil
}

// detectUsableApps checks if any of the unusable applications become usable.
func detectUsableApps() error {
	applications, err := apps.AllUnusableApps()
	if err != nil {
		return err
	}

	appChan := make(chan steam.App)
	wg := new(sync.WaitGroup)
	// Adding goroutines to workgroup
	for i := 0; i < 200; i++ {
		wg.Add(1)
		go func(appChan chan steam.App, wg *sync.WaitGroup) {
			defer wg.Done() // Decreasing internal counter for wait-group as soon as goroutine finishes
			for app := range appChan {
				count, err := steam.GetUserCount(app.ID)
				if err != nil {
					log.Println(err)
					continue
				}
				if count > 5 {
					err = apps.MarkAppAsUsable(app.ID)
					if err != nil {
						log.Println(err)
						continue
					}
					log.Println(fmt.Sprintf("Marked app %s (%d) as usable.", app.Name, app.ID))
				}
			}
		}(appChan, wg)
	}

	// Processing all links by spreading them to `free` goroutines
	for _, app := range applications {
		appChan <- app
	}
	close(appChan) // Closing channel (waiting in goroutines won't continue any more)
	wg.Wait()      // Waiting for all goroutines to finish
	return nil
}
<commit_msg>Changed number of records required for unusable apps detection.<commit_after>/*
Usage: processor command

Commands:
	detect-usable-apps - Detect usable apps
	detect-unusable-apps - Detect unusable apps
	cleanup - Usage history cleanup
*/
package main

import (
	"fmt"
	"log"
	"os"
	"sync"

	"github.com/tsukanov/steamhistory/collector/steam"
	"github.com/tsukanov/steamhistory/storage/apps"
	"github.com/tsukanov/steamhistory/storage/history"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Println("No argument! See documentation to find out how to use this application.")
		return
	}

	switch os.Args[1] {

	case "detect-unusable-apps":
		log.Println("Detecting unusable apps...")
		err := detectUnusableApps()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Detection completed!")

	case "detect-usable-apps":
		log.Println("Detecting usable apps...")
		err := detectUsableApps()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("Detection completed!")

	case "cleanup":
		log.Println("Doing history cleanup...")
		err := history.HistoryCleanup()
		if err != nil {
			log.Fatal(err)
		}
		log.Println("History cleanup completed!")

	default:
		fmt.Println("Unknown command! See documentation.")
	}
}

// detectUnusableApps finds applications that have no active users and marks
// them as unusable.
func detectUnusableApps() error {
	applications, err := apps.AllUsableApps()
	if err != nil {
		return err
	}

	for _, app := range applications {
		db, err := history.OpenAppUsageDB(app.ID)
		if err != nil {
			log.Println(app, err)
			continue
		}

		rows, err := db.Query("SELECT count(*), avg(count) FROM records")
		if err != nil {
			log.Println(err)
			continue
		}
		var count int
		var avg float32
		rows.Next()
		err = rows.Scan(&count, &avg)
		rows.Close()
		if err != nil {
			log.Println(err)
			continue
		}
		if count > 5 && avg < 1 {
			err = apps.MarkAppAsUnusable(app.ID)
			log.Println(fmt.Sprintf("Marked app %s (%d) as unusable.", app.Name, app.ID))
			if err != nil {
				log.Println(err)
				continue
			}
			// Removing history
			err = history.RemoveAppUsageDB(app.ID)
			if err != nil {
				log.Println(err)
			}
		}

		db.Close()
	}
	return nil
}

// detectUsableApps checks if any of the unusable applications become usable.
func detectUsableApps() error {
	applications, err := apps.AllUnusableApps()
	if err != nil {
		return err
	}

	appChan := make(chan steam.App)
	wg := new(sync.WaitGroup)
	// Adding goroutines to workgroup
	for i := 0; i < 200; i++ {
		wg.Add(1)
		go func(appChan chan steam.App, wg *sync.WaitGroup) {
			defer wg.Done() // Decreasing internal counter for wait-group as soon as goroutine finishes
			for app := range appChan {
				count, err := steam.GetUserCount(app.ID)
				if err != nil {
					log.Println(err)
					continue
				}
				if count > 5 {
					err = apps.MarkAppAsUsable(app.ID)
					if err != nil {
						log.Println(err)
						continue
					}
					log.Println(fmt.Sprintf("Marked app %s (%d) as usable.", app.Name, app.ID))
				}
			}
		}(appChan, wg)
	}

	// Processing all links by spreading them to `free` goroutines
	for _, app := range applications {
		appChan <- app
	}
	close(appChan) // Closing channel (waiting in goroutines won't continue any more)
	wg.Wait()      // Waiting for all goroutines to finish
	return nil
}
<|endoftext|>


<commit_before>package nomad

import (
	"fmt"
	"math"
	"math/big"
	"math/rand"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"time"

	crand "crypto/rand"

	"github.com/hashicorp/serf/serf"
)

// ensurePath is used to make sure a path exists
func ensurePath(path string, dir bool) error {
	if !dir {
		path = filepath.Dir(path)
	}
	return os.MkdirAll(path, 0755)
}

// RuntimeStats is used to return various runtime information
func RuntimeStats() map[string]string {
	return map[string]string{
		"kernel.name": runtime.GOOS,
		"arch":        runtime.GOARCH,
		"version":     runtime.Version(),
		"max_procs":   strconv.FormatInt(int64(runtime.GOMAXPROCS(0)), 10),
		"goroutines":  strconv.FormatInt(int64(runtime.NumGoroutine()), 10),
		"cpu_count":   strconv.FormatInt(int64(runtime.NumCPU()), 10),
	}
}

// serverParts is used to return the parts of a server role
type serverParts struct {
	Name       string
	Region     string
	Datacenter string
	Port       int
	Bootstrap  bool
	Expect     int
	Version    int
	Addr       net.Addr
}

func (s *serverParts) String() string {
	return fmt.Sprintf("%s (Addr: %s) (DC: %s)",
		s.Name, s.Addr, s.Datacenter)
}

// Returns if a member is a Nomad server. Returns a boolean,
// and a struct with the various important components
func isNomadServer(m serf.Member) (bool, *serverParts) {
	if m.Tags["role"] != "nomad" {
		return false, nil
	}

	region := m.Tags["region"]
	datacenter := m.Tags["dc"]
	_, bootstrap := m.Tags["bootstrap"]

	expect := 0
	expect_str, ok := m.Tags["expect"]
	var err error
	if ok {
		expect, err = strconv.Atoi(expect_str)
		if err != nil {
			return false, nil
		}
	}

	port_str := m.Tags["port"]
	port, err := strconv.Atoi(port_str)
	if err != nil {
		return false, nil
	}

	vsn_str := m.Tags["vsn"]
	vsn, err := strconv.Atoi(vsn_str)
	if err != nil {
		return false, nil
	}

	addr := &net.TCPAddr{IP: m.Addr, Port: port}
	parts := &serverParts{
		Name:       m.Name,
		Region:     region,
		Datacenter: datacenter,
		Port:       port,
		Bootstrap:  bootstrap,
		Expect:     expect,
		Addr:       addr,
		Version:    vsn,
	}
	return true, parts
}

// Returns a random stagger interval between 0 and the duration
func randomStagger(intv time.Duration) time.Duration {
	return time.Duration(uint64(rand.Int63()) % uint64(intv))
}

// shuffleStrings randomly shuffles the list of strings
func shuffleStrings(list []string) {
	for i := range list {
		j := rand.Intn(i + 1)
		list[i], list[j] = list[j], list[i]
	}
}

// maxUint64 returns the maximum value
func maxUint64(a, b uint64) uint64 {
	if a >= b {
		return a
	}
	return b
}

// rateScaledInterval is used to choose an interval to perform an action in order
// to target an aggregate number of actions per second across the whole cluster.
func rateScaledInterval(rate float64, min time.Duration, n int) time.Duration {
	interval := time.Duration(float64(time.Second) * float64(n) / rate)
	if interval < min {
		return min
	}
	return interval
}

// seedRandom seeds the global random variable using a cryptographically random
// seed. It returns an error if determing the random seed fails.
func seedRandom() error {
	n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
	if err != nil {
		return err
	}
	rand.Seed(n.Int64())
	return nil
}

<commit_msg>Remove blank line<commit_after>package nomad

import (
	"fmt"
	"math"
	"math/big"
	"math/rand"
	"net"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"time"

	crand "crypto/rand"

	"github.com/hashicorp/serf/serf"
)

// ensurePath is used to make sure a path exists
func ensurePath(path string, dir bool) error {
	if !dir {
		path = filepath.Dir(path)
	}
	return os.MkdirAll(path, 0755)
}

// RuntimeStats is used to return various runtime information
func RuntimeStats() map[string]string {
	return map[string]string{
		"kernel.name": runtime.GOOS,
		"arch":        runtime.GOARCH,
		"version":     runtime.Version(),
		"max_procs":   strconv.FormatInt(int64(runtime.GOMAXPROCS(0)), 10),
		"goroutines":  strconv.FormatInt(int64(runtime.NumGoroutine()), 10),
		"cpu_count":   strconv.FormatInt(int64(runtime.NumCPU()), 10),
	}
}

// serverParts is used to return the parts of a server role
type serverParts struct {
	Name       string
	Region     string
	Datacenter string
	Port       int
	Bootstrap  bool
	Expect     int
	Version    int
	Addr       net.Addr
}

func (s *serverParts) String() string {
	return fmt.Sprintf("%s (Addr: %s) (DC: %s)",
		s.Name, s.Addr, s.Datacenter)
}

// Returns if a member is a Nomad server. Returns a boolean,
// and a struct with the various important components
func isNomadServer(m serf.Member) (bool, *serverParts) {
	if m.Tags["role"] != "nomad" {
		return false, nil
	}

	region := m.Tags["region"]
	datacenter := m.Tags["dc"]
	_, bootstrap := m.Tags["bootstrap"]

	expect := 0
	expect_str, ok := m.Tags["expect"]
	var err error
	if ok {
		expect, err = strconv.Atoi(expect_str)
		if err != nil {
			return false, nil
		}
	}

	port_str := m.Tags["port"]
	port, err := strconv.Atoi(port_str)
	if err != nil {
		return false, nil
	}

	vsn_str := m.Tags["vsn"]
	vsn, err := strconv.Atoi(vsn_str)
	if err != nil {
		return false, nil
	}

	addr := &net.TCPAddr{IP: m.Addr, Port: port}
	parts := &serverParts{
		Name:       m.Name,
		Region:     region,
		Datacenter: datacenter,
		Port:       port,
		Bootstrap:  bootstrap,
		Expect:     expect,
		Addr:       addr,
		Version:    vsn,
	}
	return true, parts
}

// Returns a random stagger interval between 0 and the duration
func randomStagger(intv time.Duration) time.Duration {
	return time.Duration(uint64(rand.Int63()) % uint64(intv))
}

// shuffleStrings randomly shuffles the list of strings
func shuffleStrings(list []string) {
	for i := range list {
		j := rand.Intn(i + 1)
		list[i], list[j] = list[j], list[i]
	}
}

// maxUint64 returns the maximum value
func maxUint64(a, b uint64) uint64 {
	if a >= b {
		return a
	}
	return b
}

// rateScaledInterval is used to choose an interval to perform an action in order
// to target an aggregate number of actions per second across the whole cluster.
func rateScaledInterval(rate float64, min time.Duration, n int) time.Duration {
	interval := time.Duration(float64(time.Second) * float64(n) / rate)
	if interval < min {
		return min
	}
	return interval
}

// seedRandom seeds the global random variable using a cryptographically random
// seed. It returns an error if determing the random seed fails.
func seedRandom() error {
	n, err := crand.Int(crand.Reader, big.NewInt(math.MaxInt64))
	if err != nil {
		return err
	}
	rand.Seed(n.Int64())
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"../nsq"
	"../util"
	"../util/notify"
	"log"
	"sync/atomic"
)

type Topic struct {
	name                 string
	newChannelChan       chan util.ChanReq
	channelMap           map[string]*Channel
	backend              nsq.BackendQueue
	incomingMessageChan  chan *nsq.Message
	msgChan              chan *nsq.Message
	routerSyncChan       chan int
	readSyncChan         chan int
	exitChan             chan int
	channelWriterStarted int32
}

var TopicMap = make(map[string]*Topic)
var newTopicChan = make(chan util.ChanReq)
var topicFactoryStarted = int32(0)

// Topic constructor
func NewTopic(topicName string, inMemSize int, dataPath string) *Topic {
	topic := &Topic{name: topicName,
		newChannelChan:       make(chan util.ChanReq),
		channelMap:           make(map[string]*Channel),
		backend:              nsq.NewDiskQueue(topicName, dataPath),
		incomingMessageChan:  make(chan *nsq.Message, 5),
		msgChan:              make(chan *nsq.Message, inMemSize),
		routerSyncChan:       make(chan int, 1),
		readSyncChan:         make(chan int),
		exitChan:             make(chan int),
		channelWriterStarted: 0}
	go topic.Router(inMemSize, dataPath)
	notify.Post("new_topic", topic)
	return topic
}

// GetTopic performs a thread safe operation
// to return a pointer to a Topic object (potentially new)
// see: topicFactory()
func GetTopic(topicName string) *Topic {
	topicChan := make(chan interface{})
	newTopicChan <- util.ChanReq{topicName, topicChan}
	return (<-topicChan).(*Topic)
}

// topicFactory is executed in a goroutine and manages
// the creation/retrieval of Topic objects
func TopicFactory(inMemSize int, dataPath string) {
	var topic *Topic

	if !atomic.CompareAndSwapInt32(&topicFactoryStarted, 0, 1) {
		return
	}

	for {
		topicReq, ok := <-newTopicChan
		if !ok {
			break
		}
		name := topicReq.Variable.(string)
		if topic, ok = TopicMap[name]; !ok {
			topic = NewTopic(name, inMemSize, dataPath)
			TopicMap[name] = topic
			log.Printf("TOPIC(%s): created", topic.name)
		}
		topicReq.RetChan <- topic
	}
}

// GetChannel performs a thread safe operation
// to return a pointer to a Channel object (potentially new)
// for the given Topic
// see: Topic.Router()
func (t *Topic) GetChannel(channelName string) *Channel {
	channelChan := make(chan interface{})
	t.newChannelChan <- util.ChanReq{channelName, channelChan}
	return (<-channelChan).(*Channel)
}

// PutMessage writes to the appropriate incoming
// message channel
func (t *Topic) PutMessage(msg *nsq.Message) {
	// log.Printf("TOPIC(%s): PutMessage(%s, %s)", t.name, util.UuidToStr(msg.Uuid), string(msg.Body))
	t.incomingMessageChan <- msg
}

// MessagePump selects over the in-memory and backend queue and
// writes messages to every channel for this topic, synchronizing
// with the channel router
func (t *Topic) MessagePump() {
	var msg *nsq.Message

	for {
		select {
		case msg = <-t.msgChan:
		case <-t.backend.ReadReadyChan():
			buf, err := t.backend.Get()
			if err != nil {
				log.Printf("ERROR: t.backend.Get() - %s", err.Error())
				continue
			}
			msg, err = nsq.DecodeMessage(buf)
			if err != nil {
				log.Printf("ERROR: failed to decode message - %s", err.Error())
				continue
			}
		}

		t.readSyncChan <- 1
		log.Printf("TOPIC(%s): channelMap %#v", t.name, t.channelMap)
		for _, channel := range t.channelMap {
			go func(c *Channel, m *nsq.Message) {
				// log.Printf("TOPIC(%s): writing message to channel(%s)", t.name, c.name)
				c.PutMessage(m)
			}(channel, msg)
		}
		t.routerSyncChan <- 1
	}
}

// Router handles muxing of Topic messages including
// creation of new Channel objects, proxying messages
// to memory or backend, and synchronizing reads
func (t *Topic) Router(inMemSize int, dataPath string) {
	var msg *nsq.Message

	for {
		select {
		case channelReq := <-t.newChannelChan:
			name := channelReq.Variable.(string)
			channel, ok := t.channelMap[name]
			if !ok {
				channel = NewChannel(name, inMemSize, dataPath)
				t.channelMap[name] = channel
				log.Printf("TOPIC(%s): new channel(%s)", t.name, channel.name)
			}
			channelReq.RetChan <- channel
			if atomic.CompareAndSwapInt32(&t.channelWriterStarted, 0, 1) {
				go t.MessagePump()
			}
		case msg = <-t.incomingMessageChan:
			select {
			case t.msgChan <- msg:
				// log.Printf("TOPIC(%s): wrote to messageChan", t.name)
			default:
				data, err := msg.Encode()
				if err != nil {
					log.Printf("ERROR: failed to Encode() message - %s", err.Error())
					// TODO: shrug
					continue
				}
				err = t.backend.Put(data)
				if err != nil {
					log.Printf("ERROR: t.backend.Put() - %s", err.Error())
					// TODO: requeue?
				}
				// log.Printf("TOPIC(%s): wrote to backend", t.name)
			}
		case <-t.readSyncChan:
			// log.Printf("TOPIC(%s): read sync START", t.name)
			<-t.routerSyncChan
			// log.Printf("TOPIC(%s): read sync END", t.name)
		case <-t.exitChan:
			return
		}
	}
}

func (t *Topic) Close() error {
	var err error

	log.Printf("TOPIC(%s): closing", t.name)

	t.exitChan <- 1

	for _, channel := range t.channelMap {
		err = channel.Close()
		if err != nil {
			// we need to continue regardless of error to close all the channels
			log.Printf("ERROR: channel(%s) close - %s", channel.name, err.Error())
		}
	}

	err = t.backend.Close()
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>fix bug where messages were being re-used across channels for a single topic<commit_after>package main

import (
	"../nsq"
	"../util"
	"../util/notify"
	"log"
	"sync/atomic"
)

type Topic struct {
	name                 string
	newChannelChan       chan util.ChanReq
	channelMap           map[string]*Channel
	backend              nsq.BackendQueue
	incomingMessageChan  chan *nsq.Message
	msgChan              chan *nsq.Message
	routerSyncChan       chan int
	readSyncChan         chan int
	exitChan             chan int
	channelWriterStarted int32
}

var TopicMap = make(map[string]*Topic)
var newTopicChan = make(chan util.ChanReq)
var topicFactoryStarted = int32(0)

// Topic constructor
func NewTopic(topicName string, inMemSize int, dataPath string) *Topic {
	topic := &Topic{name: topicName,
		newChannelChan:       make(chan util.ChanReq),
		channelMap:           make(map[string]*Channel),
		backend:              nsq.NewDiskQueue(topicName, dataPath),
		incomingMessageChan:  make(chan *nsq.Message, 5),
		msgChan:              make(chan *nsq.Message, inMemSize),
		routerSyncChan:       make(chan int, 1),
		readSyncChan:         make(chan int),
		exitChan:             make(chan int),
		channelWriterStarted: 0}
	go topic.Router(inMemSize, dataPath)
	notify.Post("new_topic", topic)
	return topic
}

// GetTopic performs a thread safe operation
// to return a pointer to a Topic object (potentially new)
// see: topicFactory()
func GetTopic(topicName string) *Topic {
	topicChan := make(chan interface{})
	newTopicChan <- util.ChanReq{topicName, topicChan}
	return (<-topicChan).(*Topic)
}

// topicFactory is executed in a goroutine and manages
// the creation/retrieval of Topic objects
func TopicFactory(inMemSize int, dataPath string) {
	var topic *Topic

	if !atomic.CompareAndSwapInt32(&topicFactoryStarted, 0, 1) {
		return
	}

	for {
		topicReq, ok := <-newTopicChan
		if !ok {
			break
		}
		name := topicReq.Variable.(string)
		if topic, ok = TopicMap[name]; !ok {
			topic = NewTopic(name, inMemSize, dataPath)
			TopicMap[name] = topic
			log.Printf("TOPIC(%s): created", topic.name)
		}
		topicReq.RetChan <- topic
	}
}

// GetChannel performs a thread safe operation
// to return a pointer to a Channel object (potentially new)
// for the given Topic
// see: Topic.Router()
func (t *Topic) GetChannel(channelName string) *Channel {
	channelChan := make(chan interface{})
	t.newChannelChan <- util.ChanReq{channelName, channelChan}
	return (<-channelChan).(*Channel)
}

// PutMessage writes to the appropriate incoming
// message channel
func (t *Topic) PutMessage(msg *nsq.Message) {
	// log.Printf("TOPIC(%s): PutMessage(%s, %s)", t.name, util.UuidToStr(msg.Uuid), string(msg.Body))
	t.incomingMessageChan <- msg
}

// MessagePump selects over the in-memory and backend queue and
// writes messages to every channel for this topic, synchronizing
// with the channel router
func (t *Topic) MessagePump() {
	var msg *nsq.Message

	for {
		select {
		case msg = <-t.msgChan:
		case <-t.backend.ReadReadyChan():
			buf, err := t.backend.Get()
			if err != nil {
				log.Printf("ERROR: t.backend.Get() - %s", err.Error())
				continue
			}
			msg, err = nsq.DecodeMessage(buf)
			if err != nil {
				log.Printf("ERROR: failed to decode message - %s", err.Error())
				continue
			}
		}

		t.readSyncChan <- 1
		log.Printf("TOPIC(%s): channelMap %#v", t.name, t.channelMap)
		for _, channel := range t.channelMap {
			// copy the message because each channel
			// needs a unique instance
			chanMsg := nsq.NewMessage(msg.Uuid, msg.Body)
			chanMsg.Timestamp = msg.Timestamp
			go channel.PutMessage(chanMsg)
		}
		t.routerSyncChan <- 1
	}
}

// Router handles muxing of Topic messages including
// creation of new Channel objects, proxying messages
// to memory or backend, and synchronizing reads
func (t *Topic) Router(inMemSize int, dataPath string) {
	var msg *nsq.Message

	for {
		select {
		case channelReq := <-t.newChannelChan:
			name := channelReq.Variable.(string)
			channel, ok := t.channelMap[name]
			if !ok {
				channel = NewChannel(name, inMemSize, dataPath)
				t.channelMap[name] = channel
				log.Printf("TOPIC(%s): new channel(%s)", t.name, channel.name)
			}
			channelReq.RetChan <- channel
			if atomic.CompareAndSwapInt32(&t.channelWriterStarted, 0, 1) {
				go t.MessagePump()
			}
		case msg = <-t.incomingMessageChan:
			select {
			case t.msgChan <- msg:
				// log.Printf("TOPIC(%s): wrote to messageChan", t.name)
			default:
				data, err := msg.Encode()
				if err != nil {
					log.Printf("ERROR: failed to Encode() message - %s", err.Error())
					// TODO: shrug
					continue
				}
				err = t.backend.Put(data)
				if err != nil {
					log.Printf("ERROR: t.backend.Put() - %s", err.Error())
					// TODO: requeue?
				}
				// log.Printf("TOPIC(%s): wrote to backend", t.name)
			}
		case <-t.readSyncChan:
			// log.Printf("TOPIC(%s): read sync START", t.name)
			<-t.routerSyncChan
			// log.Printf("TOPIC(%s): read sync END", t.name)
		case <-t.exitChan:
			return
		}
	}
}

func (t *Topic) Close() error {
	var err error

	log.Printf("TOPIC(%s): closing", t.name)

	t.exitChan <- 1

	for _, channel := range t.channelMap {
		err = channel.Close()
		if err != nil {
			// we need to continue regardless of error to close all the channels
			log.Printf("ERROR: channel(%s) close - %s", channel.name, err.Error())
		}
	}

	err = t.backend.Close()
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"
)

var _ = log.Print

var cmdLs = &Command{
	Run:   runLs,
	Usage: "ls [-l] [app...]",
	Short: "list apps",
	Long: `
       hk ls [-l] [-a app] releases [name...]

       hk ls [-l] [-a app] addons [name...]

Command hk ls lists apps, releases, and addons.

Options:

    -l       long listing
    -a=name  app name

Examples:

    $ hk ls
    myapp
    myapp2

    $ hk ls -l
    app  me  1234k  Jan 2 12:34  myapp
    app  me  4567k  Jan 2 12:34  myapp2

    $ hk ls rel
    v1
    v2

    $ hk ls -l rel
    3ae20c2  me  Jun 12 18:28  v1  Deploy 3ae20c2
    0fda0ae  me  Jun 13 18:14  v2  Deploy 0fda0ae
    ed39b69  me  Jun 13 18:31  v3  Deploy ed39b69

    $ hk ls -g rel v3
    ed39b69  me  Jun 13 18:31  v3  Deploy ed39b69

    $ hk ls addons
    DATABASE_URL
    REDIS_URL

    $ hk ls -l addons REDIS_URL
    redistogo:nano  me  soaring-ably-1234  REDIS_URL
`,
}

func init() {
	cmdLs.Flag.StringVar(&flagApp, "a", "", "app")
	cmdLs.Flag.BoolVar(&flagLong, "l", false, "long listing")
}

func runLs(cmd *Command, args []string) {
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	list(w, cmd, args)
	w.Flush()
}

func list(w io.Writer, cmd *Command, args []string) {
	if len(args) == 0 {
		var apps []*App
		must(Get(&apps, "/apps"))
		sort.Sort(appsByName(apps))
		abbrevEmailApps(apps)
		for _, a := range apps {
			listApp(w, a)
		}
		return
	}
	switch a0 := args[0]; {
	case strings.HasPrefix("releases", a0):
		listRels(w, args[1:])
	case strings.HasPrefix("addons", a0):
		listAddons(w, args[1:])
	default:
		listApps(w, args)
	}
}

func listApps(w io.Writer, names []string) {
	ch := make(chan error, len(names))
	var apps []*App
	for _, name := range names {
		if name == "" {
			ch <- nil
		} else {
			v, url := new(App), "/apps/"+name
			apps = append(apps, v)
			go func() { ch <- Get(v, url) }()
		}
	}
	for _ = range names {
		if err := <-ch; err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	}
	sort.Sort(appsByName(apps))
	abbrevEmailApps(apps)
	for _, a := range apps {
		if a.Name != "" {
			listApp(w, a)
		}
	}
}

type appsByName []*App

func (a appsByName) Len() int           { return len(a) }
func (a appsByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a appsByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

func listRels(w io.Writer, names []string) {
	if len(names) == 0 {
		var rels []*Release
		must(Get(&rels, "/apps/"+mustApp()+"/releases"))
		gitDescribe(rels)
		abbrevEmailReleases(rels)
		for _, r := range rels {
			listRelease(w, r)
		}
		return
	}

	app := mustApp()
	ch := make(chan error, len(names))
	var rels []*Release
	for _, name := range names {
		if name == "" {
			ch <- nil
		} else {
			r, url := new(Release), "/apps/"+app+"/releases/"+name
			rels = append(rels, r)
			go func() { ch <- Get(r, url) }()
		}
	}
	for _ = range names {
		if err := <-ch; err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	}
	sort.Sort(releasesByName(rels))
	gitDescribe(rels)
	abbrevEmailReleases(rels)
	for _, r := range rels {
		if r.Name != "" {
			listRelease(w, r)
		}
	}
}

func abbrevEmailReleases(rels []*Release) {
	domains := make(map[string]int)
	for _, r := range rels {
		parts := strings.SplitN(r.User, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
		}
	}
	for _, r := range rels {
		if strings.HasSuffix(r.User, smax) {
			r.User = r.User[:len(r.User)-len(smax)]
		}
	}
}

func abbrevEmailApps(apps []*App) {
	domains := make(map[string]int)
	for _, a := range apps {
		parts := strings.SplitN(a.Owner.Email, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
			nmax = n
		}
	}
	for _, a := range apps {
		if strings.HasSuffix(a.Owner.Email, smax) {
			a.Owner.Email = a.Owner.Email[:len(a.Owner.Email)-len(smax)]
		}
	}
}

func abbrevEmailResources(ms []*mergedAddon) {
	domains := make(map[string]int)
	for _, m := range ms {
		parts := strings.SplitN(m.Owner, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
			nmax = n
		}
	}
	for _, m := range ms {
		if strings.HasSuffix(m.Owner, smax) {
			m.Owner = m.Owner[:len(m.Owner)-len(smax)]
		}
	}
}

type releasesByName []*Release

func (a releasesByName) Len() int           { return len(a) }
func (a releasesByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a releasesByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

func listAddons(w io.Writer, names []string) {
	ms := getMergedAddons(mustApp())
	abbrevEmailResources(ms)
	for i, s := range names {
		names[i] = strings.ToLower(s)
	}
	for _, m := range ms {
		if len(names) == 0 || addonMatch(m, names) {
			listAddon(w, m)
		}
	}
}

func addonMatch(m *mergedAddon, a []string) bool {
	for _, s := range a {
		if s == strings.ToLower(m.Type) {
			return true
		}
		if s == strings.ToLower(m.Name) {
			return true
		}
		if s == strings.ToLower(m.ConfigVar) {
			return true
		}
	}
	return false
}

func listApp(w io.Writer, a *App) {
	if flagLong {
		size := 0
		if a.SlugSize != nil {
			size = *a.SlugSize
		}
		t := a.CreatedAt
		if a.ReleasedAt != nil {
			t = *a.ReleasedAt
		}
		listRec(w,
			"app",
			abbrev(a.Owner.Email, 10),
			fmt.Sprintf("%6dk", (size+501)/(1000)),
			prettyTime{t},
			a.Name,
		)
	} else {
		fmt.Fprintln(w, a.Name)
	}
}

func listRelease(w io.Writer, r *Release) {
	if flagLong {
		listRec(w,
			abbrev(GitRef(r.Commit), 10),
			abbrev(r.User, 10),
			prettyTime{r.CreatedAt.Time},
			r.Name,
			r.Descr,
		)
	} else {
		fmt.Fprintln(w, r.Name)
	}
}

func listAddon(w io.Writer, m *mergedAddon) {
	if flagLong {
		listRec(w,
			m.Type,
			abbrev(m.Owner, 10),
			m.Name,
			m.ConfigVar,
		)
	} else {
		name := m.ConfigVar
		if name == "" {
			name = "(" + m.Type + ")"
		}
		fmt.Fprintln(w, m.String())
	}
}

type prettyTime struct {
	time.Time
}

func (s prettyTime) String() string {
	if time.Now().Sub(s.Time) < 12*30*24*time.Hour {
		return s.Local().Format("Jan _2 15:04")
	}
	return s.Local().Format("Jan _2  2006")
}

func roundTime(d, k time.Duration) int {
	return int((d + k - 1) / k)
}

func abbrev(s string, n int) string {
	if len(s) > n {
		return s[:n-1] + "…"
	}
	return s
}

func listRec(w io.Writer, a ...interface{}) {
	for i, x := range a {
		fmt.Fprint(w, x)
		if i+1 < len(a) {
			w.Write([]byte{'\t'})
		} else {
			w.Write([]byte{'\n'})
		}
	}
}
<commit_msg>fix typo<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strings"
	"text/tabwriter"
	"time"
)

var _ = log.Print

var cmdLs = &Command{
	Run:   runLs,
	Usage: "ls [-l] [app...]",
	Short: "list apps",
	Long: `
       hk ls [-l] [-a app] releases [name...]

       hk ls [-l] [-a app] addons [name...]

Command hk ls lists apps, releases, and addons.

Options:

    -l       long listing
    -a=name  app name

Examples:

    $ hk ls
    myapp
    myapp2

    $ hk ls -l
    app  me  1234k  Jan 2 12:34  myapp
    app  me  4567k  Jan 2 12:34  myapp2

    $ hk ls rel
    v1
    v2

    $ hk ls -l rel
    3ae20c2  me  Jun 12 18:28  v1  Deploy 3ae20c2
    0fda0ae  me  Jun 13 18:14  v2  Deploy 0fda0ae
    ed39b69  me  Jun 13 18:31  v3  Deploy ed39b69

    $ hk ls -l rel v3
    ed39b69  me  Jun 13 18:31  v3  Deploy ed39b69

    $ hk ls addons
    DATABASE_URL
    REDIS_URL

    $ hk ls -l addons REDIS_URL
    redistogo:nano  me  soaring-ably-1234  REDIS_URL
`,
}

func init() {
	cmdLs.Flag.StringVar(&flagApp, "a", "", "app")
	cmdLs.Flag.BoolVar(&flagLong, "l", false, "long listing")
}

func runLs(cmd *Command, args []string) {
	w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0)
	list(w, cmd, args)
	w.Flush()
}

func list(w io.Writer, cmd *Command, args []string) {
	if len(args) == 0 {
		var apps []*App
		must(Get(&apps, "/apps"))
		sort.Sort(appsByName(apps))
		abbrevEmailApps(apps)
		for _, a := range apps {
			listApp(w, a)
		}
		return
	}
	switch a0 := args[0]; {
	case strings.HasPrefix("releases", a0):
		listRels(w, args[1:])
	case strings.HasPrefix("addons", a0):
		listAddons(w, args[1:])
	default:
		listApps(w, args)
	}
}

func listApps(w io.Writer, names []string) {
	ch := make(chan error, len(names))
	var apps []*App
	for _, name := range names {
		if name == "" {
			ch <- nil
		} else {
			v, url := new(App), "/apps/"+name
			apps = append(apps, v)
			go func() { ch <- Get(v, url) }()
		}
	}
	for _ = range names {
		if err := <-ch; err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	}
	sort.Sort(appsByName(apps))
	abbrevEmailApps(apps)
	for _, a := range apps {
		if a.Name != "" {
			listApp(w, a)
		}
	}
}

type appsByName []*App

func (a appsByName) Len() int           { return len(a) }
func (a appsByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a appsByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

func listRels(w io.Writer, names []string) {
	if len(names) == 0 {
		var rels []*Release
		must(Get(&rels, "/apps/"+mustApp()+"/releases"))
		gitDescribe(rels)
		abbrevEmailReleases(rels)
		for _, r := range rels {
			listRelease(w, r)
		}
		return
	}

	app := mustApp()
	ch := make(chan error, len(names))
	var rels []*Release
	for _, name := range names {
		if name == "" {
			ch <- nil
		} else {
			r, url := new(Release), "/apps/"+app+"/releases/"+name
			rels = append(rels, r)
			go func() { ch <- Get(r, url) }()
		}
	}
	for _ = range names {
		if err := <-ch; err != nil {
			fmt.Fprintln(os.Stderr, err)
		}
	}
	sort.Sort(releasesByName(rels))
	gitDescribe(rels)
	abbrevEmailReleases(rels)
	for _, r := range rels {
		if r.Name != "" {
			listRelease(w, r)
		}
	}
}

func abbrevEmailReleases(rels []*Release) {
	domains := make(map[string]int)
	for _, r := range rels {
		parts := strings.SplitN(r.User, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
		}
	}
	for _, r := range rels {
		if strings.HasSuffix(r.User, smax) {
			r.User = r.User[:len(r.User)-len(smax)]
		}
	}
}

func abbrevEmailApps(apps []*App) {
	domains := make(map[string]int)
	for _, a := range apps {
		parts := strings.SplitN(a.Owner.Email, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
			nmax = n
		}
	}
	for _, a := range apps {
		if strings.HasSuffix(a.Owner.Email, smax) {
			a.Owner.Email = a.Owner.Email[:len(a.Owner.Email)-len(smax)]
		}
	}
}

func abbrevEmailResources(ms []*mergedAddon) {
	domains := make(map[string]int)
	for _, m := range ms {
		parts := strings.SplitN(m.Owner, "@", 2)
		if len(parts) == 2 {
			domains["@"+parts[1]]++
		}
	}
	smax, nmax := "", 0
	for s, n := range domains {
		if n > nmax {
			smax = s
			nmax = n
		}
	}
	for _, m := range ms {
		if strings.HasSuffix(m.Owner, smax) {
			m.Owner = m.Owner[:len(m.Owner)-len(smax)]
		}
	}
}

type releasesByName []*Release

func (a releasesByName) Len() int           { return len(a) }
func (a releasesByName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a releasesByName) Less(i, j int) bool { return a[i].Name < a[j].Name }

func listAddons(w io.Writer, names []string) {
	ms := getMergedAddons(mustApp())
	abbrevEmailResources(ms)
	for i, s := range names {
		names[i] = strings.ToLower(s)
	}
	for _, m := range ms {
		if len(names) == 0 || addonMatch(m, names) {
			listAddon(w, m)
		}
	}
}

func addonMatch(m *mergedAddon, a []string) bool {
	for _, s := range a {
		if s == strings.ToLower(m.Type) {
			return true
		}
		if s == strings.ToLower(m.Name) {
			return true
		}
		if s == strings.ToLower(m.ConfigVar) {
			return true
		}
	}
	return false
}

func listApp(w io.Writer, a *App) {
	if flagLong {
		size := 0
		if a.SlugSize != nil {
			size = *a.SlugSize
		}
		t := a.CreatedAt
		if a.ReleasedAt != nil {
			t = *a.ReleasedAt
		}
		listRec(w,
			"app",
			abbrev(a.Owner.Email, 10),
			fmt.Sprintf("%6dk", (size+501)/(1000)),
			prettyTime{t},
			a.Name,
		)
	} else {
		fmt.Fprintln(w, a.Name)
	}
}

func listRelease(w io.Writer, r *Release) {
	if flagLong {
		listRec(w,
			abbrev(GitRef(r.Commit), 10),
			abbrev(r.User, 10),
			prettyTime{r.CreatedAt.Time},
			r.Name,
			r.Descr,
		)
	} else {
		fmt.Fprintln(w, r.Name)
	}
}

func listAddon(w io.Writer, m *mergedAddon) {
	if flagLong {
		listRec(w,
			m.Type,
			abbrev(m.Owner, 10),
			m.Name,
			m.ConfigVar,
		)
	} else {
		name := m.ConfigVar
		if name == "" {
			name = "(" + m.Type + ")"
		}
		fmt.Fprintln(w, m.String())
	}
}

type prettyTime struct {
	time.Time
}

func (s prettyTime) String() string {
	if time.Now().Sub(s.Time) < 12*30*24*time.Hour {
		return s.Local().Format("Jan _2 15:04")
	}
	return s.Local().Format("Jan _2  2006")
}

func roundTime(d, k time.Duration) int {
	return int((d + k - 1) / k)
}

func abbrev(s string, n int) string {
	if len(s) > n {
		return s[:n-1] + "…"
	}
	return s
}

func listRec(w io.Writer, a ...interface{}) {
	for i, x := range a {
		fmt.Fprint(w, x)
		if i+1 < len(a) {
			w.Write([]byte{'\t'})
		} else {
			w.Write([]byte{'\n'})
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
	"strings"
)

type Weight map[string]map[string]float64

type PassiveAgressive struct {
	weight       Weight
	C            float64
	labelDefault string
	loop         int
}

func NewPassiveAgressive(C float64, loop int) PassiveAgressive {
	p := PassiveAgressive{Weight{}, C, "", loop}
	return p
}

func Dot(v, w map[string]float64) float64 {
	dot := 0.
	for key, value := range v {
		_, ok := w[key]
		if ok {
			dot += value * w[key]
		}
	}
	return dot
}

func Norm(x map[string]float64) float64 {
	n := 0.
	for _, v := range x {
		n += math.Pow(math.Abs(v), 2)
	}
	return math.Sqrt(n)
}

func (p *PassiveAgressive) InitWeigt(y []string) Weight {
	labelDist := map[string]int{}
	for _, y_i := range y {
		p.weight[y_i] = map[string]float64{}
		if _, ok := labelDist[y_i]; ok {
			labelDist[y_i] += 1
		} else {
			labelDist[y_i] = 1
		}
	}

	maxCnt := -1
	l := ""
	for k, cnt := range labelDist {
		if cnt > maxCnt {
			p.labelDefault = k
			maxCnt = cnt
		}
		l = k
	}
	if p.labelDefault == "" {
		p.labelDefault = l
	}

	return p.weight
}

func (p *PassiveAgressive) Update(X map[string]float64, y string, sign float64) Weight {
	loss := math.Max(0, 1-sign*Dot(X, p.weight[y]))
//         tau := loss / Norm(X)
	tau := loss / (Norm(X) + 1 / (2 * p.C))
	if _, ok := p.weight[y]; ok == false {
		p.weight[y] = map[string]float64{}
	}

	for f, _ := range X {
		if _, ok := p.weight[y][f]; ok {
			p.weight[y][f] += tau * sign
		} else {
			p.weight[y][f] = tau * sign
		}
	}
	return p.weight
}

func (p *PassiveAgressive) haveSameScores(scores []float64) bool {
	n_t := 0
	var prev float64
	for i, score := range scores {
		if i == 0 {
			prev = score
		} else {
			if score == prev { // current score is the same with previous score
				n_t += 1
			}
			prev = score
		}
	}
	if n_t == len(scores)-1 {
		return true
	} else {
		return false
	}
}

func (p *PassiveAgressive) Predict(X map[string]float64) string {
	maxScore := math.Inf(-1)
	pred_y_i := ""
	scores := []float64{}
	for y_j, _ := range p.weight { // calculate scores for each label
		dot := Dot(X, p.weight[y_j])
		scores = append(scores, dot)
		//                 fmt.Println(y_j, dot)
		if dot > maxScore {
			maxScore = dot
			pred_y_i = y_j
		}
	}
        if p.haveSameScores(scores) {
                pred_y_i = p.labelDefault
        }
//                 fmt.Println(scores)
//                 fmt.Println("pred y", pred_y_i, "Default", p.labelDefault)
//                 fmt.Println("")
	return pred_y_i
}

func (p *PassiveAgressive) Fit(X []map[string]float64, y []string) Weight {
	p.weight = p.InitWeigt(y)
	for loop := 0; loop < p.loop; loop++ {
		fmt.Println(loop)
		for i, X_i := range X {
			//                         fmt.Println(i, X_i)
			pred_y_i := p.Predict(X_i)
			if y[i] != pred_y_i {
				p.Update(X_i, y[i], 1.)
				p.Update(X_i, pred_y_i, -1.)
			}
		}
	}
	return p.weight
}

func SaveModel(p PassiveAgressive, fname string) {
	model_f, err := os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0777)
	if err != nil {
		panic("Failed to dump model")
	}

	writer := bufio.NewWriterSize(model_f, 4096*32)
	writer.WriteString("DEFAULTLABEL\n")
	writer.WriteString(fmt.Sprintf("%s\n", p.labelDefault))
	writer.WriteString("C\n")
	writer.WriteString(fmt.Sprintf("%f\n", p.C))
	writer.WriteString("WEIGHT\n")
	for y, weight := range p.weight {
		for ft, w := range weight {
			writer.WriteString(fmt.Sprintf("%s\t%s\t%f\n", y, ft, w))
		}
	}
	writer.Flush()
}

func LoadModel(fname string) PassiveAgressive {
	model_f, err := os.OpenFile(fname, os.O_RDONLY, 0644)
	if err != nil {
		panic("Failed to dump model")
	}
	weight := Weight{}
	var C float64
	var labelDefault string
	reader := bufio.NewReaderSize(model_f, 4096)
	a := ""
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		text := string(line)
		if text == "WEIGHT" {
			a = "w"
			continue
		}
		if text == "C" {
			a = "C"
			continue
		}
		if text == "DEFAULTLABEL" {
			a = "d"
			continue
		}
		if a == "w" {
			elems := strings.Split(text, "\t")
			label := elems[0]
			id := elems[1]
			w, _ := strconv.ParseFloat(elems[2], 64)
			_, ok := weight[label]
			if ok {
				weight[label][id] = w
			} else {
				weight[label] = map[string]float64{}
			}
		}
		if a == "C" {
			C, _ = strconv.ParseFloat(text, 64)
		}
		if a == "d" {
			labelDefault = text
		}

	}
	p := PassiveAgressive{weight, C, labelDefault, 0}
	return p
}
<commit_msg>Fix typo<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"math"
	"os"
	"strconv"
	"strings"
)

type Weight map[string]map[string]float64

type PassiveAggressive struct {
	weight       Weight
	C            float64
	labelDefault string
	loop         int
}

func NewPassiveAggressive(C float64, loop int) PassiveAggressive {
	p := PassiveAggressive{Weight{}, C, "", loop}
	return p
}

func Dot(v, w map[string]float64) float64 {
	dot := 0.
	for key, value := range v {
		_, ok := w[key]
		if ok {
			dot += value * w[key]
		}
	}
	return dot
}

func Norm(x map[string]float64) float64 {
	n := 0.
	for _, v := range x {
		n += math.Pow(math.Abs(v), 2)
	}
	return math.Sqrt(n)
}

func (p *PassiveAggressive) InitWeigt(y []string) Weight {
	labelDist := map[string]int{}
	for _, y_i := range y {
		p.weight[y_i] = map[string]float64{}
		if _, ok := labelDist[y_i]; ok {
			labelDist[y_i] += 1
		} else {
			labelDist[y_i] = 1
		}
	}

	maxCnt := -1
	l := ""
	for k, cnt := range labelDist {
		if cnt > maxCnt {
			p.labelDefault = k
			maxCnt = cnt
		}
		l = k
	}
	if p.labelDefault == "" {
		p.labelDefault = l
	}

	return p.weight
}

func (p *PassiveAggressive) Update(X map[string]float64, y string, sign float64) Weight {
	loss := math.Max(0, 1-sign*Dot(X, p.weight[y]))
//         tau := loss / Norm(X)
	tau := loss / (Norm(X) + 1 / (2 * p.C))
	if _, ok := p.weight[y]; ok == false {
		p.weight[y] = map[string]float64{}
	}

	for f, _ := range X {
		if _, ok := p.weight[y][f]; ok {
			p.weight[y][f] += tau * sign
		} else {
			p.weight[y][f] = tau * sign
		}
	}
	return p.weight
}

func (p *PassiveAggressive) haveSameScores(scores []float64) bool {
	n_t := 0
	var prev float64
	for i, score := range scores {
		if i == 0 {
			prev = score
		} else {
			if score == prev { // current score is the same with previous score
				n_t += 1
			}
			prev = score
		}
	}
	if n_t == len(scores)-1 {
		return true
	} else {
		return false
	}
}

func (p *PassiveAggressive) Predict(X map[string]float64) string {
	maxScore := math.Inf(-1)
	pred_y_i := ""
	scores := []float64{}
	for y_j, _ := range p.weight { // calculate scores for each label
		dot := Dot(X, p.weight[y_j])
		scores = append(scores, dot)
		//                 fmt.Println(y_j, dot)
		if dot > maxScore {
			maxScore = dot
			pred_y_i = y_j
		}
	}
        if p.haveSameScores(scores) {
                pred_y_i = p.labelDefault
        }
//                 fmt.Println(scores)
//                 fmt.Println("pred y", pred_y_i, "Default", p.labelDefault)
//                 fmt.Println("")
	return pred_y_i
}

func (p *PassiveAggressive) Fit(X []map[string]float64, y []string) Weight {
	p.weight = p.InitWeigt(y)
	for loop := 0; loop < p.loop; loop++ {
		fmt.Println(loop)
		for i, X_i := range X {
			//                         fmt.Println(i, X_i)
			pred_y_i := p.Predict(X_i)
			if y[i] != pred_y_i {
				p.Update(X_i, y[i], 1.)
				p.Update(X_i, pred_y_i, -1.)
			}
		}
	}
	return p.weight
}

func SaveModel(p PassiveAggressive, fname string) {
	model_f, err := os.OpenFile(fname, os.O_CREATE|os.O_RDWR, 0777)
	if err != nil {
		panic("Failed to dump model")
	}

	writer := bufio.NewWriterSize(model_f, 4096*32)
	writer.WriteString("DEFAULTLABEL\n")
	writer.WriteString(fmt.Sprintf("%s\n", p.labelDefault))
	writer.WriteString("C\n")
	writer.WriteString(fmt.Sprintf("%f\n", p.C))
	writer.WriteString("WEIGHT\n")
	for y, weight := range p.weight {
		for ft, w := range weight {
			writer.WriteString(fmt.Sprintf("%s\t%s\t%f\n", y, ft, w))
		}
	}
	writer.Flush()
}

func LoadModel(fname string) PassiveAggressive {
	model_f, err := os.OpenFile(fname, os.O_RDONLY, 0644)
	if err != nil {
		panic("Failed to dump model")
	}
	weight := Weight{}
	var C float64
	var labelDefault string
	reader := bufio.NewReaderSize(model_f, 4096)
	a := ""
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		} else if err != nil {
			panic(err)
		}
		text := string(line)
		if text == "WEIGHT" {
			a = "w"
			continue
		}
		if text == "C" {
			a = "C"
			continue
		}
		if text == "DEFAULTLABEL" {
			a = "d"
			continue
		}
		if a == "w" {
			elems := strings.Split(text, "\t")
			label := elems[0]
			id := elems[1]
			w, _ := strconv.ParseFloat(elems[2], 64)
			_, ok := weight[label]
			if ok {
				weight[label][id] = w
			} else {
				weight[label] = map[string]float64{}
			}
		}
		if a == "C" {
			C, _ = strconv.ParseFloat(text, 64)
		}
		if a == "d" {
			labelDefault = text
		}

	}
	p := PassiveAggressive{weight, C, labelDefault, 0}
	return p
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/nictuku/dht"
	nn "github.com/op/go-nanomsg"
)

type BusMessage struct {
	From    string
	To      string
	Payload []string
}

func NewBusMessage(data []byte, err error) (*BusMessage, error) {
	if err != nil {
		return nil, err
	}

	rv := &BusMessage{}
	if err = json.Unmarshal(data, rv); err != nil {
		return nil, err
	}
	return rv, nil
}

func (b *BusMessage) Bytes() []byte {
	m, err := json.Marshal(b)
	if err != nil {
		log.Fatalf(`can't marshall bus message to json: %s`, err)
	}
	return m
}

type Friend struct {
	name     string
	msg      *BusMessage
	lastSeen time.Time
	mtx      *sync.Mutex
}

func NewFriend(name string) *Friend {
	return &Friend{name: name, mtx: &sync.Mutex{}, lastSeen: time.Now()}
}

func (f *Friend) String() string {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	return fmt.Sprintf("Name: %s, last message: %s, last seen: %s", f.name, f.msg.Payload, f.lastSeen)
}

func (f *Friend) HandleMessage(msg *BusMessage) {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	f.msg = msg
	f.lastSeen = time.Now()
}

type PeerManager struct {
	d          *dht.DHT
	bus        *nn.BusSocket
	nick       string
	friends    map[string]*Friend
	oldfriends map[string]*Friend
	venue      string
	mtx        *sync.RWMutex
}

func NewPeerManager() *PeerManager {
	conf := dht.NewConfig()
	conf.Port = 55000
	conf.CleanupPeriod = 3 * time.Second

	pm := PeerManager{}

	var err error
	pm.bus, err = nn.NewBusSocket()
	if err != nil {
		log.Fatalf(`can't create BUS socket: %s`, err)
	}

	if pm.d, err = dht.New(conf); err != nil {
		log.Fatalf(`can't create DHT: %s`, err)
	}

	sum := sha1.Sum([]byte("LetsMeetHere"))
	pm.venue = hex.EncodeToString(sum[:])

	buf := make([]byte, 4)
	rand.Read(buf)
	pm.nick = hex.EncodeToString(buf)

	log.Printf(`My nickname is %s`, pm.nick)
	log.Printf(`I will meet my friends at %s`, pm.venue)

	pm.friends = make(map[string]*Friend)
	pm.oldfriends = make(map[string]*Friend)

	pm.mtx = &sync.RWMutex{}

	return &pm
}

func (pm *PeerManager) String() string {
	pm.mtx.RLock()
	defer pm.mtx.RUnlock()

	s := []string{
		fmt.Sprintf("DHT: Port: %d", pm.d.Port()),
		fmt.Sprintf("\r\nI am %s", pm.nick),
		fmt.Sprintf("I have %d friend(s)", len(pm.friends)),
		fmt.Sprintf("We're meeting at '%s'", pm.venue),
		"\r\nThese are my friends:\r\n",
	}

	for _, f := range pm.friends {
		s = append(s, f.String())
	}

	s = append(s, "\r\nI haven't heard from these guys in a while:\r\n")
	for _, f := range pm.oldfriends {
		s = append(s, f.String())
	}

	return strings.Join(s, "\r\n")
}

func (pm *PeerManager) Publish(dst string, payload []string) error {
	m := &BusMessage{From: pm.nick, To: dst, Payload: payload}
	_, err := pm.bus.Send(m.Bytes(), 0)
	return err
}

func (pm *PeerManager) Loop() {
	ih, err := dht.DecodeInfoHash(pm.venue)
	if err != nil {
		log.Printf(`can't decode infohash: %s`, err)
		return
	}

	if err := pm.d.Start(); err != nil {
		log.Printf(`can't start DHT: %s`, err)
		return
	}

	log.Printf(`DHT bound to port %d`, pm.d.Port())

	ep, err := pm.bus.Bind(fmt.Sprintf("tcp://*:%d", pm.d.Port()))
	if err != nil {
		log.Fatalf(`can't bind BUS socket: %s`, err)
	}
	log.Printf(`BUS endpoint: %s`, ep)

	go pm.drainPeers()

	go func() {
		for {
			time.Sleep(2 * time.Second)
			pm.mtx.Lock()
			for name, f := range pm.friends {
				f.mtx.Lock()
				if f.lastSeen.Add(10 * time.Second).Before(time.Now()) {
					log.Printf(`haven't heard from %s in a while`, name)
					pm.oldfriends[name] = f
					delete(pm.friends, name)
				}
				f.mtx.Unlock()
			}
			pm.mtx.Unlock()
		}
	}()

	go func() {
		for {
			/* Receive message */
			m, err := NewBusMessage(pm.bus.Recv(0))
			if err != nil {
				log.Printf(`can't receive message from bus: %s`, err)
				continue
			}

			/* Ignore messages from ourselves */
			if m.From == pm.nick {
				continue
			}

			/* Ignore messages not to 'everyone' or us */
			if m.To != pm.nick && m.To != "*" {
				continue
			}

			log.Printf(`tgt: %s, src: %s, msg: %v`, m.To, m.From, m.Payload)

			pm.mtx.Lock()
			f, ok := pm.friends[m.From]
			if !ok {
				f = NewFriend(m.From)
				pm.friends[m.From] = f
			}
			pm.mtx.Unlock()

			f.HandleMessage(m)

			if !ok {
				log.Printf(`%s is a new friend!`, m.From)
				pm.Publish(m.From, []string{"hello friend :)"})
			}
		}
	}()

	go func() {
		i := 0
		for {
			pm.Publish("*", []string{"I'm alive", fmt.Sprintf("%d", i)})
			i++
			time.Sleep(5 * time.Second)
		}
	}()

	for {
		pm.d.PeersRequest(string(ih), true)
		time.Sleep(5 * time.Second)
	}
}

func (pm *PeerManager) drainPeers() {
	log.Printf(`draining DHT`)
	seen := make(map[string]struct{})

	for r := range pm.d.PeersRequestResults {
		for _, peers := range r {
			for _, x := range peers {
				addr := dht.DecodePeerAddress(x)
				if _, ok := seen[addr]; !ok {
					pm.connectToPeer(a)
				}
				seen[addr] = struct{}{}
			}
		}
	}
}

func (pm *PeerManager) connectToPeer(addr string) {
	if _, err := pm.bus.Connect(fmt.Sprintf("tcp://%s", addr)); err != nil {
		log.Printf(`can't connect SUB to %s: %s`, addr, err)
	}
}
<commit_msg>Fix typo<commit_after>package main

import (
	"crypto/rand"
	"crypto/sha1"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"sync"
	"time"

	"github.com/nictuku/dht"
	nn "github.com/op/go-nanomsg"
)

type BusMessage struct {
	From    string
	To      string
	Payload []string
}

func NewBusMessage(data []byte, err error) (*BusMessage, error) {
	if err != nil {
		return nil, err
	}

	rv := &BusMessage{}
	if err = json.Unmarshal(data, rv); err != nil {
		return nil, err
	}
	return rv, nil
}

func (b *BusMessage) Bytes() []byte {
	m, err := json.Marshal(b)
	if err != nil {
		log.Fatalf(`can't marshall bus message to json: %s`, err)
	}
	return m
}

type Friend struct {
	name     string
	msg      *BusMessage
	lastSeen time.Time
	mtx      *sync.Mutex
}

func NewFriend(name string) *Friend {
	return &Friend{name: name, mtx: &sync.Mutex{}, lastSeen: time.Now()}
}

func (f *Friend) String() string {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	return fmt.Sprintf("Name: %s, last message: %s, last seen: %s", f.name, f.msg.Payload, f.lastSeen)
}

func (f *Friend) HandleMessage(msg *BusMessage) {
	f.mtx.Lock()
	defer f.mtx.Unlock()

	f.msg = msg
	f.lastSeen = time.Now()
}

type PeerManager struct {
	d          *dht.DHT
	bus        *nn.BusSocket
	nick       string
	friends    map[string]*Friend
	oldfriends map[string]*Friend
	venue      string
	mtx        *sync.RWMutex
}

func NewPeerManager() *PeerManager {
	conf := dht.NewConfig()
	conf.Port = 55000
	conf.CleanupPeriod = 3 * time.Second

	pm := PeerManager{}

	var err error
	pm.bus, err = nn.NewBusSocket()
	if err != nil {
		log.Fatalf(`can't create BUS socket: %s`, err)
	}

	if pm.d, err = dht.New(conf); err != nil {
		log.Fatalf(`can't create DHT: %s`, err)
	}

	sum := sha1.Sum([]byte("LetsMeetHere"))
	pm.venue = hex.EncodeToString(sum[:])

	buf := make([]byte, 4)
	rand.Read(buf)
	pm.nick = hex.EncodeToString(buf)

	log.Printf(`My nickname is %s`, pm.nick)
	log.Printf(`I will meet my friends at %s`, pm.venue)

	pm.friends = make(map[string]*Friend)
	pm.oldfriends = make(map[string]*Friend)

	pm.mtx = &sync.RWMutex{}

	return &pm
}

func (pm *PeerManager) String() string {
	pm.mtx.RLock()
	defer pm.mtx.RUnlock()

	s := []string{
		fmt.Sprintf("DHT: Port: %d", pm.d.Port()),
		fmt.Sprintf("\r\nI am %s", pm.nick),
		fmt.Sprintf("I have %d friend(s)", len(pm.friends)),
		fmt.Sprintf("We're meeting at '%s'", pm.venue),
		"\r\nThese are my friends:\r\n",
	}

	for _, f := range pm.friends {
		s = append(s, f.String())
	}

	s = append(s, "\r\nI haven't heard from these guys in a while:\r\n")
	for _, f := range pm.oldfriends {
		s = append(s, f.String())
	}

	return strings.Join(s, "\r\n")
}

func (pm *PeerManager) Publish(dst string, payload []string) error {
	m := &BusMessage{From: pm.nick, To: dst, Payload: payload}
	_, err := pm.bus.Send(m.Bytes(), 0)
	return err
}

func (pm *PeerManager) Loop() {
	ih, err := dht.DecodeInfoHash(pm.venue)
	if err != nil {
		log.Printf(`can't decode infohash: %s`, err)
		return
	}

	if err := pm.d.Start(); err != nil {
		log.Printf(`can't start DHT: %s`, err)
		return
	}

	log.Printf(`DHT bound to port %d`, pm.d.Port())

	ep, err := pm.bus.Bind(fmt.Sprintf("tcp://*:%d", pm.d.Port()))
	if err != nil {
		log.Fatalf(`can't bind BUS socket: %s`, err)
	}
	log.Printf(`BUS endpoint: %s`, ep)

	go pm.drainPeers()

	go func() {
		for {
			time.Sleep(2 * time.Second)
			pm.mtx.Lock()
			for name, f := range pm.friends {
				f.mtx.Lock()
				if f.lastSeen.Add(10 * time.Second).Before(time.Now()) {
					log.Printf(`haven't heard from %s in a while`, name)
					pm.oldfriends[name] = f
					delete(pm.friends, name)
				}
				f.mtx.Unlock()
			}
			pm.mtx.Unlock()
		}
	}()

	go func() {
		for {
			/* Receive message */
			m, err := NewBusMessage(pm.bus.Recv(0))
			if err != nil {
				log.Printf(`can't receive message from bus: %s`, err)
				continue
			}

			/* Ignore messages from ourselves */
			if m.From == pm.nick {
				continue
			}

			/* Ignore messages not to 'everyone' or us */
			if m.To != pm.nick && m.To != "*" {
				continue
			}

			log.Printf(`tgt: %s, src: %s, msg: %v`, m.To, m.From, m.Payload)

			pm.mtx.Lock()
			f, ok := pm.friends[m.From]
			if !ok {
				f = NewFriend(m.From)
				pm.friends[m.From] = f
			}
			pm.mtx.Unlock()

			f.HandleMessage(m)

			if !ok {
				log.Printf(`%s is a new friend!`, m.From)
				pm.Publish(m.From, []string{"hello friend :)"})
			}
		}
	}()

	go func() {
		i := 0
		for {
			pm.Publish("*", []string{"I'm alive", fmt.Sprintf("%d", i)})
			i++
			time.Sleep(5 * time.Second)
		}
	}()

	for {
		pm.d.PeersRequest(string(ih), true)
		time.Sleep(5 * time.Second)
	}
}

func (pm *PeerManager) drainPeers() {
	log.Printf(`draining DHT`)
	seen := make(map[string]struct{})

	for r := range pm.d.PeersRequestResults {
		for _, peers := range r {
			for _, x := range peers {
				addr := dht.DecodePeerAddress(x)
				if _, ok := seen[addr]; !ok {
					pm.connectToPeer(addr)
				}
				seen[addr] = struct{}{}
			}
		}
	}
}

func (pm *PeerManager) connectToPeer(addr string) {
	if _, err := pm.bus.Connect(fmt.Sprintf("tcp://%s", addr)); err != nil {
		log.Printf(`can't connect SUB to %s: %s`, addr, err)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"os"
	"path/filepath"
)

type RC struct {
	path      string
	mtime     int64
	allowPath string
}

func eachDir(path string) (paths []string) {
	path, err := filepath.Abs(path)
	if err != nil {
		return
	}

	paths = []string{path}

	if path == "/" {
		return
	}

	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == os.PathSeparator {
			path = path[:i]
			if path == "" {
				path = "/"
			}
			paths = append(paths, path)
		}
	}

	return
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func fileMtime(path string) (int64, error) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return 0, err
	}

	return fileInfo.ModTime().Unix(), nil
}

func fileHash(path string) (string, error) {
	fd, err := os.Open(path)
	if err != nil {
		return "", err
	}

	hasher := sha256.New()
	io.Copy(hasher, fd)
	num := hasher.Sum(nil)

	return fmt.Sprintf("%x", num), nil
}

// Creates a file
func touch(path string) error {
	fd, err := os.Create(path)
	if err != nil {
		return err
	}
	//	fd.Write([]byte{})
	fd.Close()
	return nil
}

func findUp(searchDir string, fileName string) (path string) {
	for _, dir := range eachDir(searchDir) {
		path = filepath.Join(dir, fileName)
		if fileExists(path) {
			return
		}
	}
	return ""
}

func FindRC(wd string, allowDir string) *RC {
	rcPath := findUp(wd, ".envrc")
	if rcPath == "" {
		return nil
	}

	return RCFromPath(rcPath, allowDir)
}

func RCFromPath(path string, allowDir string) *RC {
	mtime, err := fileMtime(path)
	if err != nil {
		return nil
	}

	hash, err := fileHash(path)
	if err != nil {
		return nil
	}

	allowPath := filepath.Join(allowDir, hash)
	allowMtime, _ := fileMtime(allowPath)

	if allowMtime > mtime {
		mtime = allowMtime
	}

	return &RC{path, mtime, allowPath}
}

func RCFromEnv(path string, mtime int64) *RC {
	return &RC{path, mtime, ""}
}

func (self *RC) Allow() (err error) {
	if self.allowPath == "" {
		return fmt.Errorf("Cannot allow empty path")
	}
	if err = os.MkdirAll(filepath.Dir(self.allowPath), 0755); err != nil {
		return
	}
	if err = touch(self.allowPath); err != nil {
		return
	}
	self.mtime, err = fileMtime(self.allowPath)
	return
}

func (self *RC) Deny() error {
	return os.Remove(self.allowPath)
}

func (self *RC) Allowed() bool {
	_, err := os.Stat(self.allowPath)
	return err == nil
}

func (self *RC) RelTo(wd string) string {
	x, err := filepath.Rel(wd, self.path)
	if err != nil {
		panic(err)
	}
	return x
}

func (self *RC) Touch() error {
	return touch(self.path)
}
<commit_msg>Proper file touching<commit_after>package main

import (
	"crypto/sha256"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"time"
)

type RC struct {
	path      string
	mtime     int64
	allowPath string
}

func eachDir(path string) (paths []string) {
	path, err := filepath.Abs(path)
	if err != nil {
		return
	}

	paths = []string{path}

	if path == "/" {
		return
	}

	for i := len(path) - 1; i >= 0; i-- {
		if path[i] == os.PathSeparator {
			path = path[:i]
			if path == "" {
				path = "/"
			}
			paths = append(paths, path)
		}
	}

	return
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func fileMtime(path string) (int64, error) {
	fileInfo, err := os.Stat(path)
	if err != nil {
		return 0, err
	}

	return fileInfo.ModTime().Unix(), nil
}

func fileHash(path string) (string, error) {
	fd, err := os.Open(path)
	if err != nil {
		return "", err
	}

	hasher := sha256.New()
	io.Copy(hasher, fd)
	num := hasher.Sum(nil)

	return fmt.Sprintf("%x", num), nil
}

// Creates a file

func touch(path string) (err error) {
	file, err := os.OpenFile(path, os.O_CREATE, 0644)
	if err != nil {
		return
	}
	file.Close()

	t := time.Now()
	return os.Chtimes(path, t, t)
}

func findUp(searchDir string, fileName string) (path string) {
	for _, dir := range eachDir(searchDir) {
		path = filepath.Join(dir, fileName)
		if fileExists(path) {
			return
		}
	}
	return ""
}

func FindRC(wd string, allowDir string) *RC {
	rcPath := findUp(wd, ".envrc")
	if rcPath == "" {
		return nil
	}

	return RCFromPath(rcPath, allowDir)
}

func RCFromPath(path string, allowDir string) *RC {
	mtime, err := fileMtime(path)
	if err != nil {
		return nil
	}

	hash, err := fileHash(path)
	if err != nil {
		return nil
	}

	allowPath := filepath.Join(allowDir, hash)
	allowMtime, _ := fileMtime(allowPath)

	if allowMtime > mtime {
		mtime = allowMtime
	}

	return &RC{path, mtime, allowPath}
}

func RCFromEnv(path string, mtime int64) *RC {
	return &RC{path, mtime, ""}
}

func (self *RC) Allow() (err error) {
	if self.allowPath == "" {
		return fmt.Errorf("Cannot allow empty path")
	}
	if err = os.MkdirAll(filepath.Dir(self.allowPath), 0755); err != nil {
		return
	}
	if err = touch(self.allowPath); err != nil {
		return
	}
	self.mtime, err = fileMtime(self.allowPath)
	return
}

func (self *RC) Deny() error {
	return os.Remove(self.allowPath)
}

func (self *RC) Allowed() bool {
	_, err := os.Stat(self.allowPath)
	return err == nil
}

func (self *RC) RelTo(wd string) string {
	x, err := filepath.Rel(wd, self.path)
	if err != nil {
		panic(err)
	}
	return x
}

func (self *RC) Touch() error {
	return touch(self.path)
}
<|endoftext|>