<commit_before>package main

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/whitepages/terraform-provider-stingray/Godeps/_workspace/src/github.com/hashicorp/terraform/helper/schema"
	"github.com/whitepages/terraform-provider-stingray/Godeps/_workspace/src/github.com/whitepages/go-stingray"
)

func resourceMonitor() *schema.Resource {
	return &schema.Resource{
		Create: resourceMonitorCreate,
		Read:   resourceMonitorRead,
		Update: resourceMonitorUpdate,
		Delete: resourceMonitorDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"delay": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"failures": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"http_authentication": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_body_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_host_header": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_path": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
			},

			"http_status_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "^[234][0-9][0-9]$",
			},

			"note": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"script_arguments": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"description": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Default:  true,
						},
						"name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
					},
				},
				Set: hashScriptArguments,
			},

			"script_program": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"tcp_close_string": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"tcp_max_response_len": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  2048,
			},

			"tcp_response_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  ".+",
			},

			"tcp_write_string": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"timeout": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"type": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "ping",
			},

			"udp_accept_all": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},

			"use_ssl": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
		},
	}
}

func resourceMonitorCreate(d *schema.ResourceData, meta interface{}) error {
	err := resourceMonitorSet(d, meta)
	if err != nil {
		return err
	}

	return resourceMonitorRead(d, meta)
}

func resourceMonitorRead(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client

	r, resp, err := c.GetMonitor(d.Get("name").(string))
	if err != nil {
		if resp != nil && resp.StatusCode == 404 {
			// The resource doesn't exist anymore
			d.SetId("")

			return nil
		}

		return fmt.Errorf("Error reading resource: %s", err)
	}

	d.Set("delay", int(*r.Basic.Delay))
	d.Set("failures", int(*r.Basic.Failures))
	d.Set("http_authentication", string(*r.HTTP.Authentication))
	d.Set("http_body_regex", string(*r.HTTP.BodyRegex))
	d.Set("http_host_header", string(*r.HTTP.HostHeader))
	d.Set("http_path", string(*r.HTTP.Path))
	d.Set("http_status_regex", string(*r.HTTP.StatusRegex))
	d.Set("note", string(*r.Basic.Note))
	for i, arg := range *r.Script.Arguments {
		prefix := fmt.Sprintf("script_arguments.%d", i)
		d.Set(prefix+".description", arg.Description)
		d.Set(prefix+".name", arg.Value)
		d.Set(prefix+".value", arg.Value)
	}
	d.Set("script_program", string(*r.Script.Program))
	d.Set("tcp_close_string", string(*r.TCP.CloseString))
	d.Set("tcp_max_response_len", int(*r.TCP.MaxResponseLen))
	d.Set("tcp_response_regex", string(*r.TCP.ResponseRegex))
	d.Set("tcp_write_string", string(*r.TCP.WriteString))
	d.Set("timeout", int(*r.Basic.Timeout))
	d.Set("type", string(*r.Basic.Type))
	d.Set("udp_accept_all", bool(*r.UDP.AcceptAll))
	d.Set("use_ssl", bool(*r.Basic.UseSSL))

	return nil
}

func resourceMonitorUpdate(d *schema.ResourceData, meta interface{}) error {
	err := resourceMonitorSet(d, meta)
	if err != nil {
		return err
	}

	return resourceMonitorRead(d, meta)
}

func resourceMonitorDelete(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client
	r := stingray.NewMonitor(d.Id())

	_, err := c.Delete(r)
	if err != nil {
		return err
	}

	return nil
}

func resourceMonitorSet(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client
	r := stingray.NewMonitor(d.Get("name").(string))

	setInt(&r.Basic.Delay, d, "delay")
	setInt(&r.Basic.Failures, d, "failures")
	setString(&r.HTTP.Authentication, d, "http_authentication")
	setString(&r.HTTP.BodyRegex, d, "http_body_regex")
	setString(&r.HTTP.HostHeader, d, "http_host_header")
	setString(&r.HTTP.Path, d, "http_path")
	setString(&r.HTTP.StatusRegex, d, "http_status_regex")
	setString(&r.Basic.Note, d, "note")
	setScriptArgumentsTable(&r.Script.Arguments, d, "script_arguments")
	setString(&r.Script.Program, d, "script_program")
	setString(&r.TCP.CloseString, d, "tcp_close_string")
	setInt(&r.TCP.MaxResponseLen, d, "tcp_max_response_len")
	setString(&r.TCP.ResponseRegex, d, "tcp_response_regex")
	setString(&r.TCP.WriteString, d, "tcp_write_string")
	setInt(&r.Basic.Timeout, d, "timeout")
	setString(&r.Basic.Type, d, "type")
	setBool(&r.UDP.AcceptAll, d, "udp_accept_all")
	setBool(&r.Basic.UseSSL, d, "use_ssl")

	_, err := c.Set(r)
	if err != nil {
		return err
	}

	d.SetId(d.Get("name").(string))

	return nil
}

func setScriptArgumentsTable(target **stingray.ScriptArgumentsTable, d *schema.ResourceData, key string) {
	t := stingray.ScriptArgumentsTable{}
	count := d.Get(key + ".#").(int)
	for i := 0; i < count; i++ {
		a := stingray.ScriptArgument{}
		prefix := fmt.Sprintf("%s.%d", key, i)
		setString(&a.Description, d, prefix+".description")
		a.Name = stingray.String(d.Get(prefix + ".name").(string))
		a.Value = stingray.String(d.Get(prefix + ".value").(string))
		t = append(t, a)
	}
	*target = &t
}

func hashScriptArguments(v interface{}) int {
	m := v.(map[string]interface{})
	return hashcode.String(m["name"].(string))
}
<commit_msg>Use similar functions with script_arguments as server_host_mapping<commit_after>package main

import (
	"fmt"

	"github.com/hashicorp/terraform/helper/hashcode"
	"github.com/whitepages/terraform-provider-stingray/Godeps/_workspace/src/github.com/hashicorp/terraform/helper/schema"
	"github.com/whitepages/terraform-provider-stingray/Godeps/_workspace/src/github.com/whitepages/go-stingray"
)

func resourceMonitor() *schema.Resource {
	return &schema.Resource{
		Create: resourceMonitorCreate,
		Read:   resourceMonitorRead,
		Update: resourceMonitorUpdate,
		Delete: resourceMonitorDelete,

		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
				ForceNew: true,
			},

			"delay": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"failures": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"http_authentication": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_body_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_host_header": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"http_path": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "/",
			},

			"http_status_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "^[234][0-9][0-9]$",
			},

			"note": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"script_arguments": &schema.Schema{
				Type:     schema.TypeSet,
				Optional: true,
				Elem: &schema.Resource{
					Schema: map[string]*schema.Schema{
						"description": &schema.Schema{
							Type:     schema.TypeString,
							Optional: true,
							Default:  true,
						},
						"name": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
						"value": &schema.Schema{
							Type:     schema.TypeString,
							Required: true,
						},
					},
				},
				Set: hashScriptArguments,
			},

			"script_program": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"tcp_close_string": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"tcp_max_response_len": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  2048,
			},

			"tcp_response_regex": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  ".+",
			},

			"tcp_write_string": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "",
			},

			"timeout": &schema.Schema{
				Type:     schema.TypeInt,
				Optional: true,
				Default:  3,
			},

			"type": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "ping",
			},

			"udp_accept_all": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},

			"use_ssl": &schema.Schema{
				Type:     schema.TypeBool,
				Optional: true,
				Default:  false,
			},
		},
	}
}

func resourceMonitorCreate(d *schema.ResourceData, meta interface{}) error {
	err := resourceMonitorSet(d, meta)
	if err != nil {
		return err
	}

	return resourceMonitorRead(d, meta)
}

func resourceMonitorRead(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client

	r, resp, err := c.GetMonitor(d.Get("name").(string))
	if err != nil {
		if resp != nil && resp.StatusCode == 404 {
			// The resource doesn't exist anymore
			d.SetId("")

			return nil
		}

		return fmt.Errorf("Error reading resource: %s", err)
	}

	d.Set("delay", int(*r.Basic.Delay))
	d.Set("failures", int(*r.Basic.Failures))
	d.Set("http_authentication", string(*r.HTTP.Authentication))
	d.Set("http_body_regex", string(*r.HTTP.BodyRegex))
	d.Set("http_host_header", string(*r.HTTP.HostHeader))
	d.Set("http_path", string(*r.HTTP.Path))
	d.Set("http_status_regex", string(*r.HTTP.StatusRegex))
	d.Set("note", string(*r.Basic.Note))
	d.Set("script_arguments", flattenScriptArgumentsTable(*r.Script.Arguments))
	d.Set("script_program", string(*r.Script.Program))
	d.Set("tcp_close_string", string(*r.TCP.CloseString))
	d.Set("tcp_max_response_len", int(*r.TCP.MaxResponseLen))
	d.Set("tcp_response_regex", string(*r.TCP.ResponseRegex))
	d.Set("tcp_write_string", string(*r.TCP.WriteString))
	d.Set("timeout", int(*r.Basic.Timeout))
	d.Set("type", string(*r.Basic.Type))
	d.Set("udp_accept_all", bool(*r.UDP.AcceptAll))
	d.Set("use_ssl", bool(*r.Basic.UseSSL))

	return nil
}

func resourceMonitorUpdate(d *schema.ResourceData, meta interface{}) error {
	err := resourceMonitorSet(d, meta)
	if err != nil {
		return err
	}

	return resourceMonitorRead(d, meta)
}

func resourceMonitorDelete(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client
	r := stingray.NewMonitor(d.Id())

	_, err := c.Delete(r)
	if err != nil {
		return err
	}

	return nil
}

func resourceMonitorSet(d *schema.ResourceData, meta interface{}) error {
	c := meta.(*providerConfig).client
	r := stingray.NewMonitor(d.Get("name").(string))

	setInt(&r.Basic.Delay, d, "delay")
	setInt(&r.Basic.Failures, d, "failures")
	setString(&r.HTTP.Authentication, d, "http_authentication")
	setString(&r.HTTP.BodyRegex, d, "http_body_regex")
	setString(&r.HTTP.HostHeader, d, "http_host_header")
	setString(&r.HTTP.Path, d, "http_path")
	setString(&r.HTTP.StatusRegex, d, "http_status_regex")
	setString(&r.Basic.Note, d, "note")
	setScriptArgumentsTable(&r.Script.Arguments, d, "script_arguments")
	setString(&r.Script.Program, d, "script_program")
	setString(&r.TCP.CloseString, d, "tcp_close_string")
	setInt(&r.TCP.MaxResponseLen, d, "tcp_max_response_len")
	setString(&r.TCP.ResponseRegex, d, "tcp_response_regex")
	setString(&r.TCP.WriteString, d, "tcp_write_string")
	setInt(&r.Basic.Timeout, d, "timeout")
	setString(&r.Basic.Type, d, "type")
	setBool(&r.UDP.AcceptAll, d, "udp_accept_all")
	setBool(&r.Basic.UseSSL, d, "use_ssl")

	_, err := c.Set(r)
	if err != nil {
		return err
	}

	d.SetId(d.Get("name").(string))

	return nil
}

func setScriptArgumentsTable(target **stingray.ScriptArgumentsTable, d *schema.ResourceData, key string) {
	if _, ok := d.GetOk(key); ok {
		table := d.Get(key).(*schema.Set).List()
		*target, _ = expandScriptArgumentsTable(table)
	}
}

func expandScriptArgumentsTable(configured []interface{}) (*stingray.ScriptArgumentsTable, error) {
	table := make(stingray.ScriptArgumentsTable, 0, len(configured))

	for _, raw := range configured {
		data := raw.(map[string]interface{})

		s := stingray.ScriptArgument{
			Description: stingray.String(data["description"].(string)),
			Name:        stingray.String(data["name"].(string)),
			Value:       stingray.String(data["value"].(string)),
		}

		table = append(table, s)
	}

	return &table, nil
}

func flattenScriptArgumentsTable(list stingray.ScriptArgumentsTable) []map[string]interface{} {
	result := make([]map[string]interface{}, 0, len(list))
	for _, i := range list {
		s := map[string]interface{}{
			"description": *i.Description,
			"name":        *i.Name,
			"value":       *i.Value,
		}
		result = append(result, s)
	}

	return result
}

func hashScriptArguments(v interface{}) int {
	m := v.(map[string]interface{})
	return hashcode.String(m["name"].(string))
}
<|endoftext|>


<commit_before><commit_msg>fixing tests<commit_after><|endoftext|>


<commit_before><commit_msg>Remove the oldest old code<commit_after><|endoftext|>


<commit_before><commit_msg>Draw -- VISUAL -- correctly<commit_after><|endoftext|>


<commit_before>package bitesized

import "time"

type Retention map[string][]float64

func (b *Bitesized) Retention(e string, f, t time.Time, i Interval) ([]Retention, error) {
	if f.After(t) {
		return nil, ErrFromAfterTill
	}

	retentions := []Retention{}

	start := f
	for {
		end := start
		keyAggr := []string{}
		counts := []float64{}

		for {
			keyAggr = append(keyAggr, b.intervalkey(e, end, i))

			c, err := b.Operation(AND, keyAggr...)
			if err != nil {
				return nil, err
			}

			counts = append(counts, c)

			if end = end.Add(getDuration(end, i)); end.After(t) {
				break
			}
		}

		r := Retention{nearestInterval(start, i): counts}
		retentions = append(retentions, r)

		if start = start.Add(getDuration(start, i)); start.After(t) {
			break
		}
	}

	return retentions, nil
}

func (b *Bitesized) RetentionPercent(e string, f, t time.Time, i Interval) ([]Retention, error) {
	retentions, err := b.Retention(e, f, t, i)
	if err != nil {
		return nil, err
	}

	for _, rets := range retentions {
		for timekey, values := range rets {
			first := values[0]
			percents := []float64{first}

			for _, r := range values[1:] {
				var value float64
				if first != 0 {
					value = r / first
				}

				percents = append(percents, value*100)
			}

			rets[timekey] = percents
		}
	}

	return retentions, nil
}
<commit_msg>return 2 precision for retention percents<commit_after>package bitesized

import "time"

type Retention map[string][]float64

func (b *Bitesized) Retention(e string, f, t time.Time, i Interval) ([]Retention, error) {
	if f.After(t) {
		return nil, ErrFromAfterTill
	}

	retentions := []Retention{}

	start := f
	for {
		end := start
		keyAggr := []string{}
		counts := []float64{}

		for {
			keyAggr = append(keyAggr, b.intervalkey(e, end, i))

			c, err := b.Operation(AND, keyAggr...)
			if err != nil {
				return nil, err
			}

			counts = append(counts, c)

			if end = end.Add(getDuration(end, i)); end.After(t) {
				break
			}
		}

		r := Retention{nearestInterval(start, i): counts}
		retentions = append(retentions, r)

		if start = start.Add(getDuration(start, i)); start.After(t) {
			break
		}
	}

	return retentions, nil
}

func (b *Bitesized) RetentionPercent(e string, f, t time.Time, i Interval) ([]Retention, error) {
	retentions, err := b.Retention(e, f, t, i)
	if err != nil {
		return nil, err
	}

	for _, rets := range retentions {
		for timekey, values := range rets {
			first := values[0]
			percents := []float64{first}

			for _, r := range values[1:] {
				var value float64
				if first != 0 {
					value = r / first
				}

				value = (float64(int(value*100)) / 100)
				percents = append(percents, value)
			}

			rets[timekey] = percents
		}
	}

	return retentions, nil
}
<|endoftext|>


<commit_before>package rtm

import (
	"fmt"
)

type Event interface {
	Type() string
}

type BasicEvent struct {
	TypeStr  string
	RawEvent map[string]interface{}
}

func (e BasicEvent) Type() string {
	return e.TypeStr
}

type Hello struct{}

func (h Hello) Type() string { return "hello" }

type Message interface {
	Event
	Subtype() string
	Text() string
	User() string
	Channel() string
	Timestamp() string
}

type BasicMessage struct {
	channel   string
	user      string
	text      string
	timestamp string
}

func (m BasicMessage) Type() string    { return "message" }
func (m BasicMessage) Subtype() string { return "" }
func (m BasicMessage) Text() string {
	return m.text
}
func (m BasicMessage) User() string {
	return m.user
}
func (m BasicMessage) Channel() string {
	return m.channel
}
func (m BasicMessage) Timestamp() string {
	return m.timestamp
}

type ChannelCreated struct {
	ID      string
	Name    string
	Created int
	Creator string
}

func (c ChannelCreated) Type() string { return "channel_created" }

type ChannelRename struct {
	ID      string
	Name    string
	Created int
}

func (c ChannelRename) Type() string { return "channel_rename" }

func toEvent(rawEvent map[string]interface{}) Event {
	fmt.Println(rawEvent)
	rawEventType, ok := rawEvent["type"]
	if !ok {
		return BasicEvent{"invalid", rawEvent}
	}
	switch eventType := rawEventType.(string); eventType {
	case "hello":
		return Hello{}
	case "message":
		return BasicMessage{
			channel:   getStringField(rawEvent, "channel"),
			user:      getStringField(rawEvent, "user"),
			text:      getStringField(rawEvent, "text"),
			timestamp: getStringField(rawEvent, "ts"),
		}
	case "channel_created":
		channelInfo := rawEvent["channel"].(map[string]interface{})
		return ChannelCreated{
			ID:      channelInfo["id"].(string),
			Name:    channelInfo["name"].(string),
			Created: int(channelInfo["created"].(float64)),
			Creator: channelInfo["creator"].(string),
		}
	case "channel_rename":
		channelInfo := rawEvent["channel"].(map[string]interface{})
		return ChannelRename{
			ID:      channelInfo["id"].(string),
			Name:    channelInfo["name"].(string),
			Created: int(channelInfo["created"].(float64)),
		}
	default:
		return BasicEvent{eventType, rawEvent}
	}
}

func getStringField(m map[string]interface{}, key string) string {
	val := m[key]
	switch val.(type) {
	case string:
		return val.(string)
	default:
		return ""
	}
}
<commit_msg>support subtype for messages<commit_after>package rtm

import (
	"fmt"
)

type Event interface {
	Type() string
}

type BasicEvent struct {
	TypeStr  string
	RawEvent map[string]interface{}
}

func (e BasicEvent) Type() string {
	return e.TypeStr
}

type Hello struct{}

func (h Hello) Type() string { return "hello" }

type Message interface {
	Event
	Subtype() string
	Text() string
	User() string
	Channel() string
	Timestamp() string
}

type BasicMessage struct {
	channel   string
	user      string
	text      string
	timestamp string
	subtype   string
}

func (m BasicMessage) Type() string    { return "message" }
func (m BasicMessage) Subtype() string { return m.subtype }
func (m BasicMessage) Text() string {
	return m.text
}
func (m BasicMessage) User() string {
	return m.user
}
func (m BasicMessage) Channel() string {
	return m.channel
}
func (m BasicMessage) Timestamp() string {
	return m.timestamp
}

type ChannelCreated struct {
	ID      string
	Name    string
	Created int
	Creator string
}

func (c ChannelCreated) Type() string { return "channel_created" }

type ChannelRename struct {
	ID      string
	Name    string
	Created int
}

func (c ChannelRename) Type() string { return "channel_rename" }

func toEvent(rawEvent map[string]interface{}) Event {
	fmt.Println(rawEvent)
	rawEventType, ok := rawEvent["type"]
	if !ok {
		return BasicEvent{"invalid", rawEvent}
	}
	switch eventType := rawEventType.(string); eventType {
	case "hello":
		return Hello{}
	case "message":
		return BasicMessage{
			channel:   getStringField(rawEvent, "channel"),
			user:      getStringField(rawEvent, "user"),
			text:      getStringField(rawEvent, "text"),
			timestamp: getStringField(rawEvent, "ts"),
			subtype:   getStringField(rawEvent, "subtype"),
		}
	case "channel_created":
		channelInfo := rawEvent["channel"].(map[string]interface{})
		return ChannelCreated{
			ID:      channelInfo["id"].(string),
			Name:    channelInfo["name"].(string),
			Created: int(channelInfo["created"].(float64)),
			Creator: channelInfo["creator"].(string),
		}
	case "channel_rename":
		channelInfo := rawEvent["channel"].(map[string]interface{})
		return ChannelRename{
			ID:      channelInfo["id"].(string),
			Name:    channelInfo["name"].(string),
			Created: int(channelInfo["created"].(float64)),
		}
	default:
		return BasicEvent{eventType, rawEvent}
	}
}

func getStringField(m map[string]interface{}, key string) string {
	val := m[key]
	switch val.(type) {
	case string:
		return val.(string)
	default:
		return ""
	}
}
<|endoftext|>


<commit_before>package sanntid

import "testing"

func TestParseArrivalData(t *testing.T) {
	exampleContent := []byte(`[
	{
		"RecordedAtTime":"2015-02-27T12:29:41.618+01:00",
		"MonitoringRef":"3010536",
		"MonitoredVehicleJourney":{
			"LineRef":"20",
			"DirectionRef":"2",
			"FramedVehicleJourneyRef":{
				"DataFrameRef":
				"2015-02-27",
				"DatedVehicleJourneyRef":"5803"
			},
			"PublishedLineName":"20",
			"DirectionName":"2",
			"OperatorRef":"Unibuss",
			"OriginName":"Galgeberg (i Jordalgata)",
			"OriginRef":"3010640",
			"DestinationRef":3012501,
			"DestinationName":"Skøyen",
			"OriginAimedDepartureTime":"0001-01-01T00:00:00",
			"DestinationAimedArrivalTime":"0001-01-01T00:00:00",
			"Monitored":true,
			"InCongestion":false,
			"Delay":"PT85S",
			"TrainBlockPart":null,
			"BlockRef":"2010",
			"VehicleRef":"101047",
			"VehicleMode":0,
			"VehicleJourneyName":"20676",
			"MonitoredCall":{
				"VisitNumber":5,
				"VehicleAtStop":false,
				"DestinationDisplay":"Skøyen",
				"AimedArrivalTime":"2015-02-27T12:29:00+01:00",
				"ExpectedArrivalTime":"2015-02-27T12:30:25+01:00",
				"AimedDepartureTime":"2015-02-27T12:29:00+01:00",
				"ExpectedDepartureTime":"2015-02-27T12:30:25+01:00",
				"DeparturePlatformName":"2"
			},
			"VehicleFeatureRef":null
		},
		"Extensions":{
			"IsHub":false,
			"OccupancyData":{
				"OccupancyAvailable":true,
				"OccupancyPercentage":20
			},
			"Deviations":[],
			"LineColour":"E60000"
		}
	}
	]`)
	expected := sanntidArrivalData{
		sanntidMonitoredVehicleJourney{
			"Skøyen",
			sanntidMonitoredCall{
				"2015-02-27T12:30:25+01:00",
				"2",
				"Skøyen",
			},
			"20",
			0,
			2,
		},
	}

	result := parseArrivalData(exampleContent)[0]

	if result.MonitoredVehicleJourney.DestinationName != expected.MonitoredVehicleJourney.DestinationName {
		t.Errorf(
			"Expected MonitoredVehicleJourney.DestinationName == %q (got: %q)",
			expected.MonitoredVehicleJourney.DestinationName,
			result.MonitoredVehicleJourney.DestinationName)
	}
}
<commit_msg>Change test assertion to use deep equal validation<commit_after>package sanntid

import (
	"testing"
	"reflect"
)

func TestParseArrivalData(t *testing.T) {
	exampleContent := []byte(`[
	{
		"RecordedAtTime":"2015-02-27T12:29:41.618+01:00",
		"MonitoringRef":"3010536",
		"MonitoredVehicleJourney":{
			"LineRef":"20",
			"DirectionRef":"2",
			"FramedVehicleJourneyRef":{
				"DataFrameRef":
				"2015-02-27",
				"DatedVehicleJourneyRef":"5803"
			},
			"PublishedLineName":"20",
			"DirectionName":"2",
			"OperatorRef":"Unibuss",
			"OriginName":"Galgeberg (i Jordalgata)",
			"OriginRef":"3010640",
			"DestinationRef":3012501,
			"DestinationName":"Skøyen",
			"OriginAimedDepartureTime":"0001-01-01T00:00:00",
			"DestinationAimedArrivalTime":"0001-01-01T00:00:00",
			"Monitored":true,
			"InCongestion":false,
			"Delay":"PT85S",
			"TrainBlockPart":null,
			"BlockRef":"2010",
			"VehicleRef":"101047",
			"VehicleMode":0,
			"VehicleJourneyName":"20676",
			"MonitoredCall":{
				"VisitNumber":5,
				"VehicleAtStop":false,
				"DestinationDisplay":"Skøyen",
				"AimedArrivalTime":"2015-02-27T12:29:00+01:00",
				"ExpectedArrivalTime":"2015-02-27T12:30:25+01:00",
				"AimedDepartureTime":"2015-02-27T12:29:00+01:00",
				"ExpectedDepartureTime":"2015-02-27T12:30:25+01:00",
				"DeparturePlatformName":"2"
			},
			"VehicleFeatureRef":null
		},
		"Extensions":{
			"IsHub":false,
			"OccupancyData":{
				"OccupancyAvailable":true,
				"OccupancyPercentage":20
			},
			"Deviations":[],
			"LineColour":"E60000"
		}
	}
	]`)
	expected := sanntidArrivalData{
		sanntidMonitoredVehicleJourney{
			"Skøyen",
			sanntidMonitoredCall{
				"2015-02-27T12:30:25+01:00",
				"2",
				"Skøyen",
			},
			"20",
			0,
			2,
		},
	}

	result := parseArrivalData(exampleContent)[0]

	if !reflect.DeepEqual(expected, result) {
		t.Errorf(
			"Expected result == %q (got: %q)",
			expected,
			result)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"

	"time"

	"flag"

	"github.com/aws/aws-sdk-go/service/s3"

	"html/template"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/defaults"
	"github.com/aws/aws-sdk-go/aws/session"
)

const (
	Algorithm = "AWS4-HMAC-SHA256"
)

var awsSession *session.Session
var SecretAccessKey string
var AccessKeyID string
var S3BucketURL string
var S3 *s3.S3

var Bucket string
var S3Path string
var Verbose bool
var Port int

func logger(v ...interface{}) {
	if Verbose {
		log.Println(v...)
	}
}

func init() {
	awsSession = AWSSession()
	cs, err := awsSession.Config.Credentials.Get()
	if err != nil {
		panic(err)
	}

	SecretAccessKey = cs.SecretAccessKey
	AccessKeyID = cs.AccessKeyID

	flag.Usage = func() {
		fmt.Fprintf(os.Stdout,
			"Usage: %s \n",
			os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&Bucket, "bucket", "public", "set s3 bucket name")
	flag.StringVar(&S3Path, "s3path", "tmp/upload/", "set s3 path prefix")
	flag.BoolVar(&Verbose, "verbose", false, "Make the operation more talkative")
	flag.IntVar(&Port, "port", 8000, "set listen port")
	flag.Parse()

	if !strings.HasSuffix(S3Path, "/") {
		S3Path += "/"
	}

	S3 = s3.New(awsSession)
	S3BucketURL = fmt.Sprintf("%s/%s", S3.Endpoint, Bucket)
	logger("s3 bucket url:", S3BucketURL)
}

func AWSSession() *session.Session {
	var once sync.Once
	if awsSession == nil {
		once.Do(func() {
			cfg := defaults.Config().WithRegion("cn-north-1").WithCredentials(nil).WithMaxRetries(3)
			if Verbose {
				cfg.WithLogLevel(aws.LogDebugWithHTTPBody)
			}
			awsSession = session.New(cfg)
			log.Println("Init aws session.")
		})
	}
	return awsSession
}

func makeHmac(key []byte, data []byte) []byte {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return hash.Sum(nil)
}

func sign(stringToSign string) string {
	date := makeHmac([]byte("AWS4"+SecretAccessKey), []byte(time.Now().UTC().Format("20060102")))
	region := makeHmac(date, []byte(*awsSession.Config.Region))
	service := makeHmac(region, []byte(S3.ServiceName))
	credentials := makeHmac(service, []byte("aws4_request"))
	signature := makeHmac(credentials, []byte(stringToSign))
	return hex.EncodeToString(signature)
}

func credential() string {
	return strings.Join([]string{
		AccessKeyID,
		time.Now().UTC().Format("20060102"),
		*awsSession.Config.Region,
		S3.ServiceName,
		"aws4_request",
	}, "/")
}

//
func policy(uuid, credential, amzDate string) string {
	expiration := time.Now().Add(time.Minute * 15).UTC().Format(time.RFC3339)
	s := `{
    "expiration": "` + expiration + `",
    "conditions": [
        {"x-amz-meta-uuid":"` + uuid + `"},
        {"acl": "public-read"},
        {"x-amz-date":"` + amzDate + `"},
        {"bucket": "` + Bucket + `"},
        {"x-amz-algorithm":"` + Algorithm + `"},
        ["starts-with","$key","` + S3Path + `"],
        ["starts-with", "$Content-Type", "image/"],
        ["starts-with","$Cache-Control",""],
        ["starts-with","$x-amz-credential","` + credential + `"],
        ["content-length-range", 100, 10485760],
        ["starts-with","$success_action_redirect","https://"]
    ]
}`
	return s
}

func UUID() string {
	b := make([]byte, 16)
	rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x",
		b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

type Form struct {
	Action string
	Params map[string]string
}

func preSignForm(w http.ResponseWriter, r *http.Request) {
	t, err := template.New("main").Parse(formPage1)

	if err != nil {
		io.WriteString(w, err.Error())
		return
	}

	form := &Form{
		Params: make(map[string]string),
	}
	uuid := UUID()
	credential := credential()
	amzDate := time.Now().UTC().Format("20060102T150405Z")

	b64policy := base64.StdEncoding.EncodeToString([]byte(policy(uuid, credential, amzDate)))
	form.Action = S3BucketURL
	form.Params["acl"] = "public-read"
	form.Params["key"] = S3Path + "${filename}"
	form.Params["Content-Type"] = "image/jpeg"
	form.Params["Cache-Control"] = "max-age=864000"
	form.Params["x-amz-meta-uuid"] = uuid
	form.Params["x-amz-algorithm"] = Algorithm
	form.Params["x-amz-credential"] = credential
	form.Params["x-amz-date"] = amzDate
	form.Params["x-amz-signature"] = sign(b64policy)
	form.Params["policy"] = b64policy
	form.Params["success_action_redirect"] = form.Action + fmt.Sprintf("/%s${filename}", S3Path)

	t.Execute(w, form)

}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", preSignForm)
	http.ListenAndServe(":"+Port, mux)
}

var formPage1 string = `
<html>
    <head>
        <title>s3 uploader</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="{{.Action}}" method="post" enctype="multipart/form-data">
        {{range $k,$v := .Params}}<input type="hidden" name="{{$k}}" value="{{$v}}"/>
        {{end}}
        Upload Picture: <input type="file" name="file"/>
        <input type="submit"/>
        </form>
    </body>
</html>
`
<commit_msg>change Port<commit_after>package main

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"strings"
	"sync"

	"time"

	"flag"

	"github.com/aws/aws-sdk-go/service/s3"

	"html/template"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/defaults"
	"github.com/aws/aws-sdk-go/aws/session"
)

const (
	Algorithm = "AWS4-HMAC-SHA256"
)

var awsSession *session.Session
var SecretAccessKey string
var AccessKeyID string
var S3BucketURL string
var S3 *s3.S3

var Bucket string
var S3Path string
var Verbose bool
var Port int

func logger(v ...interface{}) {
	if Verbose {
		log.Println(v...)
	}
}

func init() {
	awsSession = AWSSession()
	cs, err := awsSession.Config.Credentials.Get()
	if err != nil {
		panic(err)
	}

	SecretAccessKey = cs.SecretAccessKey
	AccessKeyID = cs.AccessKeyID

	flag.Usage = func() {
		fmt.Fprintf(os.Stdout,
			"Usage: %s \n",
			os.Args[0])
		flag.PrintDefaults()
	}

	flag.StringVar(&Bucket, "bucket", "public", "set s3 bucket name")
	flag.StringVar(&S3Path, "s3path", "tmp/upload/", "set s3 path prefix")
	flag.BoolVar(&Verbose, "verbose", false, "Make the operation more talkative")
	flag.IntVar(&Port, "port", 8000, "set listen port")
	flag.Parse()

	if !strings.HasSuffix(S3Path, "/") {
		S3Path += "/"
	}

	S3 = s3.New(awsSession)
	S3BucketURL = fmt.Sprintf("%s/%s", S3.Endpoint, Bucket)
	logger("s3 bucket url:", S3BucketURL)
}

func AWSSession() *session.Session {
	var once sync.Once
	if awsSession == nil {
		once.Do(func() {
			cfg := defaults.Config().WithRegion("cn-north-1").WithCredentials(nil).WithMaxRetries(3)
			if Verbose {
				cfg.WithLogLevel(aws.LogDebugWithHTTPBody)
			}
			awsSession = session.New(cfg)
			log.Println("Init aws session.")
		})
	}
	return awsSession
}

func makeHmac(key []byte, data []byte) []byte {
	hash := hmac.New(sha256.New, key)
	hash.Write(data)
	return hash.Sum(nil)
}

func sign(stringToSign string) string {
	date := makeHmac([]byte("AWS4"+SecretAccessKey), []byte(time.Now().UTC().Format("20060102")))
	region := makeHmac(date, []byte(*awsSession.Config.Region))
	service := makeHmac(region, []byte(S3.ServiceName))
	credentials := makeHmac(service, []byte("aws4_request"))
	signature := makeHmac(credentials, []byte(stringToSign))
	return hex.EncodeToString(signature)
}

func credential() string {
	return strings.Join([]string{
		AccessKeyID,
		time.Now().UTC().Format("20060102"),
		*awsSession.Config.Region,
		S3.ServiceName,
		"aws4_request",
	}, "/")
}

//
func policy(uuid, credential, amzDate string) string {
	expiration := time.Now().Add(time.Minute * 15).UTC().Format(time.RFC3339)
	s := `{
    "expiration": "` + expiration + `",
    "conditions": [
        {"x-amz-meta-uuid":"` + uuid + `"},
        {"acl": "public-read"},
        {"x-amz-date":"` + amzDate + `"},
        {"bucket": "` + Bucket + `"},
        {"x-amz-algorithm":"` + Algorithm + `"},
        ["starts-with","$key","` + S3Path + `"],
        ["starts-with", "$Content-Type", "image/"],
        ["starts-with","$Cache-Control",""],
        ["starts-with","$x-amz-credential","` + credential + `"],
        ["content-length-range", 100, 10485760],
        ["starts-with","$success_action_redirect","https://"]
    ]
}`
	return s
}

func UUID() string {
	b := make([]byte, 16)
	rand.Read(b)
	b[6] = (b[6] & 0x0f) | 0x40
	b[8] = (b[8] & 0x3f) | 0x80
	return fmt.Sprintf("%x-%x-%x-%x-%x",
		b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
}

type Form struct {
	Action string
	Params map[string]string
}

func preSignForm(w http.ResponseWriter, r *http.Request) {
	t, err := template.New("main").Parse(formPage1)

	if err != nil {
		io.WriteString(w, err.Error())
		return
	}

	form := &Form{
		Params: make(map[string]string),
	}
	uuid := UUID()
	credential := credential()
	amzDate := time.Now().UTC().Format("20060102T150405Z")

	b64policy := base64.StdEncoding.EncodeToString([]byte(policy(uuid, credential, amzDate)))
	form.Action = S3BucketURL
	form.Params["acl"] = "public-read"
	form.Params["key"] = S3Path + "${filename}"
	form.Params["Content-Type"] = "image/jpeg"
	form.Params["Cache-Control"] = "max-age=864000"
	form.Params["x-amz-meta-uuid"] = uuid
	form.Params["x-amz-algorithm"] = Algorithm
	form.Params["x-amz-credential"] = credential
	form.Params["x-amz-date"] = amzDate
	form.Params["x-amz-signature"] = sign(b64policy)
	form.Params["policy"] = b64policy
	form.Params["success_action_redirect"] = form.Action + fmt.Sprintf("/%s${filename}", S3Path)

	t.Execute(w, form)

}

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", preSignForm)
	http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", Port), mux)
}

var formPage1 string = `
<html>
    <head>
        <title>s3 uploader</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    </head>
    <body>
        <form action="{{.Action}}" method="post" enctype="multipart/form-data">
        {{range $k,$v := .Params}}<input type="hidden" name="{{$k}}" value="{{$v}}"/>
        {{end}}
        Upload Picture: <input type="file" name="file"/>
        <input type="submit"/>
        </form>
    </body>
</html>
`
<|endoftext|>


<commit_before>// Package safecookie provides secure encoding and decoding for cookies which
// provides both confidentiality and authenticity against both active and
// passive attackers.
//
// It does so by encrypting a cookie's value with AES-GCM using a canonicalized
// form of the cookie's attributes (minus the cookie's value) as the
// authenticated data. This canonicalized form is also used during the
// decryption process, which will fail if any part of the cookie's value or
// other attributes have been changed.
//
// This provides some important guarantees:
//
//     - No one who does not have the secret key can read the cookie's plaintext
//       value.
//
//     - No one who does not have the secret key can create a cookie which will
//       be considered valid.
//
//     - Any cookie which has had any of its attributes modified, including its
//       value, will be considered invalid.
package safecookie

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"net/http"
)

var (
	// ErrInvalidCookie is returned if the cookie is invalid.
	ErrInvalidCookie = errors.New("invalid cookie")
)

// SafeCookie seals cookies and opens them.
type SafeCookie struct {
	gcm cipher.AEAD
}

// New returns a new SafeCookie instance given a 128-, 192-, or 256-bit key.
func New(key []byte) (*SafeCookie, error) {
	b, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	gcm, err := cipher.NewGCM(b)
	if err != nil {
		return nil, err
	}

	return &SafeCookie{gcm: gcm}, nil
}

// Seal encrypts the given cookie's value with the given AEAD, using a
// canonicalized version of the cookie's other attributes as authenticated data,
// and encoding the result as Base64.
func (sc *SafeCookie) Seal(c *http.Cookie) error {
	nonce := make([]byte, sc.gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return err
	}

	ciphertext := sc.gcm.Seal(nonce, nonce, []byte(c.Value), canonicalize(c))

	c.Value = base64.URLEncoding.EncodeToString(ciphertext)

	return nil
}

// Open decrypts the given cookie's value with the given AEAD and authenticates
// the cookie's other attributes.
func (sc *SafeCookie) Open(c *http.Cookie) error {
	b, err := base64.URLEncoding.DecodeString(c.Value)
	if err != nil {
		return ErrInvalidCookie
	}

	nonce := b[:sc.gcm.NonceSize()]
	ciphertext := b[sc.gcm.NonceSize():]

	b, err = sc.gcm.Open(nil, nonce, ciphertext, canonicalize(c))
	if err != nil {
		return ErrInvalidCookie
	}

	c.Value = string(b)

	return nil
}

// canonicalize returns a canonical bitstring of the cookie, minus its value.
func canonicalize(c *http.Cookie) []byte {
	e := *c
	e.Value = ""
	return []byte(e.String())
}
<commit_msg>Docfix<commit_after>// Package safecookie provides secure encoding and decoding for cookies which
// provides both confidentiality and authenticity against both active and
// passive attackers.
//
// It does so by encrypting a cookie's value with AES-GCM using a canonicalized
// form of the cookie's attributes (minus the cookie's value) as the
// authenticated data. This canonicalized form is also used during the
// decryption process, which will fail if any part of the cookie's value or
// other attributes have been changed.
//
// This provides some important guarantees:
//
//     - No one who does not have the secret key can read the cookie's plaintext
//       value.
//
//     - No one who does not have the secret key can create a cookie which will
//       be considered valid.
//
//     - Any cookie which has had any of its attributes modified, including its
//       value, will be considered invalid.
package safecookie

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"net/http"
)

var (
	// ErrInvalidCookie is returned if the cookie is invalid.
	ErrInvalidCookie = errors.New("invalid cookie")
)

// SafeCookie seals cookies and opens them.
type SafeCookie struct {
	gcm cipher.AEAD
}

// New returns a new SafeCookie instance given a 128-, 192-, or 256-bit key.
func New(key []byte) (*SafeCookie, error) {
	b, err := aes.NewCipher(key)
	if err != nil {
		return nil, err
	}

	gcm, err := cipher.NewGCM(b)
	if err != nil {
		return nil, err
	}

	return &SafeCookie{gcm: gcm}, nil
}

// Seal encrypts the given cookie's value, using a canonicalized version of the
// cookie's other attributes as authenticated data, and encoding the result as
// Base64.
func (sc *SafeCookie) Seal(c *http.Cookie) error {
	nonce := make([]byte, sc.gcm.NonceSize())
	if _, err := rand.Read(nonce); err != nil {
		return err
	}

	ciphertext := sc.gcm.Seal(nonce, nonce, []byte(c.Value), canonicalize(c))

	c.Value = base64.URLEncoding.EncodeToString(ciphertext)

	return nil
}

// Open decrypts the given cookie's value and authenticates the cookie's other
// attributes.
func (sc *SafeCookie) Open(c *http.Cookie) error {
	b, err := base64.URLEncoding.DecodeString(c.Value)
	if err != nil {
		return ErrInvalidCookie
	}

	nonce := b[:sc.gcm.NonceSize()]
	ciphertext := b[sc.gcm.NonceSize():]

	b, err = sc.gcm.Open(nil, nonce, ciphertext, canonicalize(c))
	if err != nil {
		return ErrInvalidCookie
	}

	c.Value = string(b)

	return nil
}

// canonicalize returns a canonical bitstring of the cookie, minus its value.
func canonicalize(c *http.Cookie) []byte {
	e := *c
	e.Value = ""
	return []byte(e.String())
}
<|endoftext|>


<commit_before>package irckit

import (
	"errors"
	"html"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

type SlackInfo struct {
	Token      string
	sc         *slack.Client
	rtm        *slack.RTM
	sinfo      *slack.Info
	susers     map[string]slack.User
	connected  bool
	inprogress bool
	sync.RWMutex
}

func (u *User) loginToSlack() (*slack.Client, error) {
	u.sc = slack.New(u.Token)
	u.rtm = u.sc.NewRTM()
	u.Lock()
	u.susers = make(map[string]slack.User)
	u.Unlock()
	go u.rtm.ManageConnection()
	//time.Sleep(time.Second * 2)
	u.sinfo = u.rtm.GetInfo()
	count := 0
	for u.sinfo == nil {
		time.Sleep(time.Millisecond * 500)
		logger.Debug("still waiting for sinfo")
		u.sinfo = u.rtm.GetInfo()
		count++
		if count == 20 {
			return nil, errors.New("couldn't connect in 10 seconds. Check your credentials")
		}
	}
	go u.handleSlack()
	u.addSlackUsersToChannels()
	u.connected = true
	return u.sc, nil
}

func (u *User) logoutFromSlack() error {
	logger.Debug("calling logout from slack")
	err := u.rtm.Disconnect()
	if err != nil {
		logger.Debug("logoutfrom slack", err)
		return err
	}
	u.Srv.Logout(u)
	u.sc = nil
	logger.Info("logout succeeded")
	u.connected = false
	return nil
}

func (u *User) createSlackUser(slackuser *slack.User) *User {
	if slackuser == nil {
		return nil
	}
	if ghost, ok := u.Srv.HasUser(slackuser.Name); ok {
		return ghost
	}
	ghost := &User{Nick: slackuser.Name, User: slackuser.ID, Real: slackuser.RealName, Host: "host", Roles: "", channels: map[Channel]struct{}{}, DisplayName: slackuser.Profile.DisplayName}
	ghost.MmGhostUser = true
	u.Srv.Add(ghost)
	return ghost
}

func (u *User) addSlackUserToChannel(user *slack.User, channel string, channelId string) {
	if user == nil {
		return
	}
	ghost := u.createSlackUser(user)
	if ghost == nil {
		logger.Warnf("Cannot join %v into %s", user, channel)
		return
	}
	logger.Debugf("adding %s to %s (%s)", ghost.Nick, channel, channelId)
	ch := u.Srv.Channel(channelId)
	logger.Debugf("channel: %#v %#v", ch.String(), ch.ID())
	ch.Join(ghost)
}

func (u *User) addSlackUsersToChannels() {
	srv := u.Srv
	throttle := time.Tick(time.Millisecond * 100)
	logger.Debug("in addUsersToChannels()")
	// add all users, also who are not on channels
	ch := srv.Channel("&users")
	users, _ := u.sc.GetUsers()
	for _, mmuser := range users {
		// do not add our own nick
		if mmuser.ID == u.sinfo.User.ID {
			continue
		}
		u.createSlackUser(&mmuser)
		u.addSlackUserToChannel(&mmuser, "&users", "&users")
		u.Lock()
		u.susers[mmuser.ID] = mmuser
		u.Unlock()
	}
	ch.Join(u)

	channels := make(chan interface{}, 10)
	for i := 0; i < 10; i++ {
		go u.addSlackUserToChannelWorker(channels, throttle)
	}
	groups, _ := u.sc.GetGroups(true)
	mmchannels, _ := u.sc.GetChannels(true)
	for _, mmchannel := range mmchannels {
		if mmchannel.IsMember {
			logger.Debug("Adding channel", mmchannel)
			channels <- mmchannel
		}
	}
	for _, mmchannel := range groups {
		logger.Debug("Adding private channel", mmchannel)
		channels <- mmchannel
	}
	close(channels)
}

func (u *User) addSlackUserToChannelWorker(channels <-chan interface{}, throttle <-chan time.Time) {
	var ID, name string
	for {
		mmchannel, ok := <-channels
		if !ok {
			logger.Debug("Done adding user to channels")
			return
		}
		<-throttle
		switch mmchannel.(type) {
		case slack.Channel:
			ID = mmchannel.(slack.Channel).ID
			name = mmchannel.(slack.Channel).Name
			u.syncSlackChannel(ID, name)
		case slack.Group:
			ID = mmchannel.(slack.Group).ID
			name = mmchannel.(slack.Group).Name
			logger.Debugf("GROUP %#v", mmchannel.(slack.Group))
			u.syncSlackGroup(ID, name)

		}
		// exclude direct messages
		//var spoof func(string, string)
		//ch := u.Srv.Channel(mmchannel.ID)
		// post everything to the channel you haven't seen yet
	}
}

func (u *User) handleSlack() {
	for {
		/*
			if u.mc.WsQuit {
				logger.Debug("exiting handleWsMessage")
				return
			}
		*/
		logger.Debug("in handleSlack")
		for msg := range u.rtm.IncomingEvents {
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				u.handleSlackActionPost(ev)
			case *slack.DisconnectedEvent:
				logger.Debug("disconnected event received, we should reconnect now..")
				//return
			}
		}
	}
	/*
			logger.Debugf("MMUser WsReceiver: %#v", message.Raw)
			// check if we have the users/channels in our cache. If not update
			u.checkWsActionMessage(message.Raw, updateChannelsThrottle)
			switch message.Raw.Event {
			case model.WEBSOCKET_EVENT_POSTED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_POST_EDITED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_USER_REMOVED:
				u.handleWsActionUserRemoved(message.Raw)
			case model.WEBSOCKET_EVENT_USER_ADDED:
				u.handleWsActionUserAdded(message.Raw)
			}
		}
	*/
}

func (u *User) handleSlackActionPost(rmsg *slack.MessageEvent) {
	var ch Channel
	logger.Debugf("handleSlackActionPost() receiving msg %#v", rmsg)
	if len(rmsg.Attachments) > 0 {
		// skip messages we made ourselves
		if rmsg.Attachments[0].CallbackID == "matterircd" {
			return
		}
	}

	user, err := u.rtm.GetUserInfo(rmsg.User)
	if err != nil {
		return
	}

	// handle bot messages
	botname := ""
	if rmsg.User == "" && rmsg.BotID != "" {
		bot, _ := u.rtm.GetBotInfo(rmsg.BotID)
		if bot.Name != "" {
			botname = bot.Name
		} else if rmsg.Username != "" {
			botname = rmsg.Username
		}
	}

	// create new "ghost" user
	ghost := u.createSlackUser(user)

	spoofUsername := user.ID
	if ghost != nil {
		spoofUsername = ghost.Nick
		if ghost.DisplayName != "" {
			spoofUsername = "|"
			//	spoofUsername = ghost.DisplayName
		}
	}

	// if we have a botname, use it
	if botname != "" {
		spoofUsername = botname
	}

	msgs := strings.Split(rmsg.Text, "\n")
	// direct message

	if ghost != nil {
		ch = u.Srv.Channel(rmsg.Channel)
		// join if not in channel
		if !ch.HasUser(ghost) {
			ch.Join(ghost)
		}
	}

	for _, m := range msgs {
		// cleanup the message
		m = u.replaceMention(m)
		m = u.replaceVariable(m)
		m = u.replaceChannel(m)
		m = u.replaceURL(m)
		m = html.UnescapeString(m)

		// look in attachments if we have no text
		if m == "" {
			for _, attach := range rmsg.Attachments {
				if attach.Text != "" {
					m = attach.Text
				} else {
					m = attach.Fallback
				}
			}
		}

		// still no text, ignore this message
		if m == "" {
			continue
		}

		if strings.HasPrefix(rmsg.Channel, "D") {
			u.MsgSpoofUser(spoofUsername, m)
		} else {
			if ghost.DisplayName != "" {
				m = "<" + ghost.DisplayName + "> " + m
			}
			ch.SpoofMessage(spoofUsername, m)
		}
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackChannel(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetChannelInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}
	// before joining ourself
	for _, user := range info.Members {
		// join all the channels we're on on MM
		if user == u.sinfo.User.ID {
			ch := srv.Channel(id)
			// only join when we're not yet on the channel
			if !ch.HasUser(u) {
				logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
				ch.Join(u)
				//ch.Topic(u, u.mc.GetChannelHeader(id))
			}
			break
		}
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackGroup(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetGroupInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}
	// before joining ourself
	for _, user := range info.Members {
		// join all the channels we're on on MM
		if user == u.sinfo.User.ID {
			ch := srv.Channel(id)
			// only join when we're not yet on the channel
			if !ch.HasUser(u) {
				logger.Debugf("syncSlackgroup adding myself to %s (id: %s)", name, id)
				ch.Join(u)
				//ch.Topic(u, u.mc.GetChannelHeader(id))
			}
			break
		}
	}
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+u.userName(r[1]), -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceChannel(text string) string {
	results := regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], "#"+r[1], -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#variables
func (u *User) replaceVariable(text string) string {
	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		if r[2] != "" {
			text = strings.Replace(text, r[0], "@"+r[2], -1)
		} else {
			text = strings.Replace(text, r[0], "@"+r[1], -1)
		}
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_urls
func (u *User) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}

func (u *User) getSlackUser(name string) *slack.User {
	u.RLock()
	defer u.RUnlock()
	if user, ok := u.susers[name]; ok {
		return &user
	}
	return nil
}

func (u *User) userName(id string) string {
	u.RLock()
	defer u.RUnlock()
	// TODO dynamically update when new users are joining slack
	for _, us := range u.susers {
		if us.ID == id {
			if us.Profile.DisplayName != "" {
				return us.Profile.DisplayName
			}
			return us.Name
		}
	}
	if id == u.sinfo.User.ID {
		return u.sinfo.User.Name
	}
	return ""
}

func (u *User) isConnected() bool {
	return u.connected
}
<commit_msg>Always add yourself to channels. Fixes issue with channel > 500 members<commit_after>package irckit

import (
	"errors"
	"html"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/nlopes/slack"
)

type SlackInfo struct {
	Token      string
	sc         *slack.Client
	rtm        *slack.RTM
	sinfo      *slack.Info
	susers     map[string]slack.User
	connected  bool
	inprogress bool
	sync.RWMutex
}

func (u *User) loginToSlack() (*slack.Client, error) {
	u.sc = slack.New(u.Token)
	u.rtm = u.sc.NewRTM()
	u.Lock()
	u.susers = make(map[string]slack.User)
	u.Unlock()
	go u.rtm.ManageConnection()
	//time.Sleep(time.Second * 2)
	u.sinfo = u.rtm.GetInfo()
	count := 0
	for u.sinfo == nil {
		time.Sleep(time.Millisecond * 500)
		logger.Debug("still waiting for sinfo")
		u.sinfo = u.rtm.GetInfo()
		count++
		if count == 20 {
			return nil, errors.New("couldn't connect in 10 seconds. Check your credentials")
		}
	}
	go u.handleSlack()
	u.addSlackUsersToChannels()
	u.connected = true
	return u.sc, nil
}

func (u *User) logoutFromSlack() error {
	logger.Debug("calling logout from slack")
	err := u.rtm.Disconnect()
	if err != nil {
		logger.Debug("logoutfrom slack", err)
		return err
	}
	u.Srv.Logout(u)
	u.sc = nil
	logger.Info("logout succeeded")
	u.connected = false
	return nil
}

func (u *User) createSlackUser(slackuser *slack.User) *User {
	if slackuser == nil {
		return nil
	}
	if ghost, ok := u.Srv.HasUser(slackuser.Name); ok {
		return ghost
	}
	ghost := &User{Nick: slackuser.Name, User: slackuser.ID, Real: slackuser.RealName, Host: "host", Roles: "", channels: map[Channel]struct{}{}, DisplayName: slackuser.Profile.DisplayName}
	ghost.MmGhostUser = true
	u.Srv.Add(ghost)
	return ghost
}

func (u *User) addSlackUserToChannel(user *slack.User, channel string, channelId string) {
	if user == nil {
		return
	}
	ghost := u.createSlackUser(user)
	if ghost == nil {
		logger.Warnf("Cannot join %v into %s", user, channel)
		return
	}
	logger.Debugf("adding %s to %s (%s)", ghost.Nick, channel, channelId)
	ch := u.Srv.Channel(channelId)
	logger.Debugf("channel: %#v %#v", ch.String(), ch.ID())
	ch.Join(ghost)
}

func (u *User) addSlackUsersToChannels() {
	srv := u.Srv
	throttle := time.Tick(time.Millisecond * 100)
	logger.Debug("in addUsersToChannels()")
	// add all users, also who are not on channels
	ch := srv.Channel("&users")
	users, _ := u.sc.GetUsers()
	for _, mmuser := range users {
		// do not add our own nick
		if mmuser.ID == u.sinfo.User.ID {
			continue
		}
		u.createSlackUser(&mmuser)
		u.addSlackUserToChannel(&mmuser, "&users", "&users")
		u.Lock()
		u.susers[mmuser.ID] = mmuser
		u.Unlock()
	}
	ch.Join(u)

	channels := make(chan interface{}, 10)
	for i := 0; i < 10; i++ {
		go u.addSlackUserToChannelWorker(channels, throttle)
	}
	groups, _ := u.sc.GetGroups(true)
	mmchannels, _ := u.sc.GetChannels(true)
	for _, mmchannel := range mmchannels {
		if mmchannel.IsMember {
			logger.Debug("Adding channel", mmchannel)
			channels <- mmchannel
		}
	}
	for _, mmchannel := range groups {
		logger.Debug("Adding private channel", mmchannel)
		channels <- mmchannel
	}
	close(channels)
}

func (u *User) addSlackUserToChannelWorker(channels <-chan interface{}, throttle <-chan time.Time) {
	var ID, name string
	for {
		mmchannel, ok := <-channels
		if !ok {
			logger.Debug("Done adding user to channels")
			return
		}
		<-throttle
		switch mmchannel.(type) {
		case slack.Channel:
			ID = mmchannel.(slack.Channel).ID
			name = mmchannel.(slack.Channel).Name
			u.syncSlackChannel(ID, name)
		case slack.Group:
			ID = mmchannel.(slack.Group).ID
			name = mmchannel.(slack.Group).Name
			logger.Debugf("GROUP %#v", mmchannel.(slack.Group))
			u.syncSlackGroup(ID, name)

		}
		// exclude direct messages
		//var spoof func(string, string)
		//ch := u.Srv.Channel(mmchannel.ID)
		// post everything to the channel you haven't seen yet
	}
}

func (u *User) handleSlack() {
	for {
		/*
			if u.mc.WsQuit {
				logger.Debug("exiting handleWsMessage")
				return
			}
		*/
		logger.Debug("in handleSlack")
		for msg := range u.rtm.IncomingEvents {
			switch ev := msg.Data.(type) {
			case *slack.MessageEvent:
				u.handleSlackActionPost(ev)
			case *slack.DisconnectedEvent:
				logger.Debug("disconnected event received, we should reconnect now..")
				//return
			}
		}
	}
	/*
			logger.Debugf("MMUser WsReceiver: %#v", message.Raw)
			// check if we have the users/channels in our cache. If not update
			u.checkWsActionMessage(message.Raw, updateChannelsThrottle)
			switch message.Raw.Event {
			case model.WEBSOCKET_EVENT_POSTED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_POST_EDITED:
				u.handleWsActionPost(message.Raw)
			case model.WEBSOCKET_EVENT_USER_REMOVED:
				u.handleWsActionUserRemoved(message.Raw)
			case model.WEBSOCKET_EVENT_USER_ADDED:
				u.handleWsActionUserAdded(message.Raw)
			}
		}
	*/
}

func (u *User) handleSlackActionPost(rmsg *slack.MessageEvent) {
	var ch Channel
	logger.Debugf("handleSlackActionPost() receiving msg %#v", rmsg)
	if len(rmsg.Attachments) > 0 {
		// skip messages we made ourselves
		if rmsg.Attachments[0].CallbackID == "matterircd" {
			return
		}
	}

	user, err := u.rtm.GetUserInfo(rmsg.User)
	if err != nil {
		return
	}

	// handle bot messages
	botname := ""
	if rmsg.User == "" && rmsg.BotID != "" {
		bot, _ := u.rtm.GetBotInfo(rmsg.BotID)
		if bot.Name != "" {
			botname = bot.Name
		} else if rmsg.Username != "" {
			botname = rmsg.Username
		}
	}

	// create new "ghost" user
	ghost := u.createSlackUser(user)

	spoofUsername := user.ID
	if ghost != nil {
		spoofUsername = ghost.Nick
		if ghost.DisplayName != "" {
			spoofUsername = "|"
			//	spoofUsername = ghost.DisplayName
		}
	}

	// if we have a botname, use it
	if botname != "" {
		spoofUsername = botname
	}

	msgs := strings.Split(rmsg.Text, "\n")
	// direct message

	if ghost != nil {
		ch = u.Srv.Channel(rmsg.Channel)
		// join if not in channel
		if !ch.HasUser(ghost) {
			ch.Join(ghost)
		}
	}

	for _, m := range msgs {
		// cleanup the message
		m = u.replaceMention(m)
		m = u.replaceVariable(m)
		m = u.replaceChannel(m)
		m = u.replaceURL(m)
		m = html.UnescapeString(m)

		// look in attachments if we have no text
		if m == "" {
			for _, attach := range rmsg.Attachments {
				if attach.Text != "" {
					m = attach.Text
				} else {
					m = attach.Fallback
				}
			}
		}

		// still no text, ignore this message
		if m == "" {
			continue
		}

		if strings.HasPrefix(rmsg.Channel, "D") {
			u.MsgSpoofUser(spoofUsername, m)
		} else {
			if ghost.DisplayName != "" {
				m = "<" + ghost.DisplayName + "> " + m
			}
			ch.SpoofMessage(spoofUsername, m)
		}
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackChannel(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetChannelInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// sync IRC with mattermost channel state
func (u *User) syncSlackGroup(id string, name string) {
	srv := u.Srv
	info, err := u.sc.GetGroupInfo(id)
	if err != nil {
		logger.Info(err)
	}

	for _, user := range info.Members {
		if u.sinfo.User.ID != user {
			//slackuser, _ := u.sc.GetUserInfo(user)
			slackuser := u.getSlackUser(user)
			if slackuser != nil {
				u.addSlackUserToChannel(slackuser, "#"+name, id)
			}
		}
	}

	ch := srv.Channel(id)
	ch.Topic(u, info.Topic.Value)
	if !ch.HasUser(u) {
		logger.Debugf("syncSlackchannel adding myself to %s (id: %s)", name, id)
		ch.Join(u)
	}
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+u.userName(r[1]), -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_channels_and_users
func (u *User) replaceChannel(text string) string {
	results := regexp.MustCompile(`<#[a-zA-Z0-9]+\|(.+?)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], "#"+r[1], -1)
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#variables
func (u *User) replaceVariable(text string) string {
	results := regexp.MustCompile(`<!((?:subteam\^)?[a-zA-Z0-9]+)(?:\|@?(.+?))?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		if r[2] != "" {
			text = strings.Replace(text, r[0], "@"+r[2], -1)
		} else {
			text = strings.Replace(text, r[0], "@"+r[1], -1)
		}
	}
	return text
}

// @see https://api.slack.com/docs/message-formatting#linking_to_urls
func (u *User) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)(\|.*?)?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}

func (u *User) getSlackUser(name string) *slack.User {
	u.RLock()
	defer u.RUnlock()
	if user, ok := u.susers[name]; ok {
		return &user
	}
	return nil
}

func (u *User) userName(id string) string {
	u.RLock()
	defer u.RUnlock()
	// TODO dynamically update when new users are joining slack
	for _, us := range u.susers {
		if us.ID == id {
			if us.Profile.DisplayName != "" {
				return us.Profile.DisplayName
			}
			return us.Name
		}
	}
	if id == u.sinfo.User.ID {
		return u.sinfo.User.Name
	}
	return ""
}

func (u *User) isConnected() bool {
	return u.connected
}
<|endoftext|>


<commit_before>package router551

import (
	"github.com/go51/container551"
	"github.com/go51/string551"
	"regexp"
	"runtime"
)

type routerMethod int

const (
	GET     routerMethod = 1
	POST    routerMethod = 2
	PUT     routerMethod = 4
	DELETE  routerMethod = 8
	COMMAND routerMethod = 16
	UNKNOWN routerMethod = 32
)

func (rm routerMethod) String() string {
	switch rm {
	case GET:
		return "GET"
	case POST:
		return "POST"
	case PUT:
		return "PUT"
	case DELETE:
		return "DELETE"
	case COMMAND:
		return "COMMAND"
	}
	return "UNKNOWN"
}

type ActionFunc func(c *container551.Container) interface{}

type Router struct {
	get     map[string]*route
	post    map[string]*route
	put     map[string]*route
	delete  map[string]*route
	command map[string]*route
}

var routerInstance *Router

func Load() *Router {
	if routerInstance != nil {
		return routerInstance
	}

	routerInstance = &Router{
		get:     make(map[string]*route, 0),
		post:    make(map[string]*route, 0),
		put:     make(map[string]*route, 0),
		delete:  make(map[string]*route, 0),
		command: make(map[string]*route, 0),
	}

	return routerInstance
}

type route struct {
	name        string
	pattern     string
	regex       *regexp.Regexp
	packageName string
	action      ActionFunc
	keys        []string
}

func (r *route) Name() string {
	return r.name
}

func (r *route) Keys() []string {
	return r.keys
}

func (r *route) Action() ActionFunc {
	return r.action
}

func (r *route) PackageName() string {
	return r.packageName
}

func (r *Router) Add(packageName string, method routerMethod, name, pattern string, action ActionFunc) {

	route := &route{
		name:        name,
		pattern:     pattern,
		regex:       nil,
		packageName: packageName,
		action:      action,
		keys:        []string{},
	}

	if method != COMMAND {
		route.keys = r.getKeys(pattern)
		regexPattern := pattern
		for i := 0; i < len(route.keys); i++ {
			regexPattern = string551.Replace(regexPattern, ":"+route.keys[i]+":", ".*")
		}
		route.regex = regexp.MustCompile("^" + regexPattern + "$")
	}

	if method&GET == GET {
		r.addGet(name, route)
	}
	if method&POST == POST {
		r.addPost(name, route)
	}
	if method&PUT == PUT {
		r.addPut(name, route)
	}
	if method&DELETE == DELETE {
		r.addDelete(name, route)
	}
	if method&COMMAND == COMMAND {
		r.addCommand(name, route)
	}
}

func (r *Router) addGet(name string, route *route) {
	r.get[name] = route
}

func (r *Router) addPost(name string, route *route) {
	r.post[name] = route
}

func (r *Router) addPut(name string, route *route) {
	r.put[name] = route
}

func (r *Router) addDelete(name string, route *route) {
	r.delete[name] = route
}

func (r *Router) addCommand(name string, route *route) {
	r.command[name] = route
}

func (r *Router) getKeys(pattern string) []string {
	keys := []string{}

	patternBytes := string551.StringToBytes(pattern)
	coron := false

	keyBytes := []byte{}

	for i := 0; i < len(patternBytes); i++ {
		if patternBytes[i] == 0x3A {
			// 0x3A => ":"
			if !coron {
				keyBytes = []byte{}
				coron = true
			} else {
				keys = append(keys, string551.BytesToString(keyBytes))
				coron = false
			}
		} else {
			if coron {
				keyBytes = append(keyBytes, patternBytes[i])
			}
		}
	}

	return keys
}

var regexpControllerPattern *regexp.Regexp

func (r *Router) getPackageName(path string) string {
	if regexpControllerPattern == nil {
		regexpControllerPattern = regexp.MustCompile(`\/\w+\.`)
	}

	packageName := regexpControllerPattern.FindString(path)

	return packageName[1 : len(packageName)-1]
}

func (r *Router) FindRouteByName(method, name string) *route {
	if method == GET.String() {
		return r.get[name]
	}
	if method == POST.String() {
		return r.post[name]
	}
	if method == PUT.String() {
		return r.put[name]
	}
	if method == DELETE.String() {
		return r.delete[name]
	}
	if method == COMMAND.String() {
		return r.command[name]
	}

	return nil
}

func (r *Router) FindRouteByPathMatch(method, path string) *route {
	routes := r.getRoutes(method)

	for _, route := range routes {
		if route.pattern == path {
			return route
		}
	}

	for _, route := range routes {
		if route.pattern == "/" {
			continue
		}
		if route.regex.MatchString(path) {
			return route
		}
	}

	return nil
}

func (r *Router) getRoutes(method string) map[string]*route {
	switch method {
	case GET.String():
		return r.get
	case POST.String():
		return r.post
	case PUT.String():
		return r.put
	case DELETE.String():
		return r.delete
	case COMMAND.String():
		return r.command
	}

	return make(map[string]*route)
}

func (r *Router) Url(name string, parameter ...string) string {

	var route *route = nil

	if r.get[name] != nil {
		route = r.get[name]
	} else if r.post[name] != nil {
		route = r.post[name]
	} else if r.put[name] != nil {
		route = r.put[name]
	} else if r.delete[name] != nil {
		route = r.delete[name]
	}

	if route == nil {
		// Not Found
		return "#"
	}

	if len(parameter) != 0 && len(parameter) != len(route.keys) {
		// No Match
		return "#"
	}

	//----------------------------------------
	// Match
	//----------------------------------------
	if len(parameter) == 0 {
		// No paramter
		return route.pattern
	}

	url := route.pattern
	keys := route.keys
	for i := 0; i < len(parameter); i++ {
		url = string551.Replace(url, ":"+keys[i]+":", parameter[i])

	}

	return url
}
<commit_msg>refs #24 不要なインポートを削除<commit_after>package router551

import (
	"github.com/go51/container551"
	"github.com/go51/string551"
	"regexp"
)

type routerMethod int

const (
	GET     routerMethod = 1
	POST    routerMethod = 2
	PUT     routerMethod = 4
	DELETE  routerMethod = 8
	COMMAND routerMethod = 16
	UNKNOWN routerMethod = 32
)

func (rm routerMethod) String() string {
	switch rm {
	case GET:
		return "GET"
	case POST:
		return "POST"
	case PUT:
		return "PUT"
	case DELETE:
		return "DELETE"
	case COMMAND:
		return "COMMAND"
	}
	return "UNKNOWN"
}

type ActionFunc func(c *container551.Container) interface{}

type Router struct {
	get     map[string]*route
	post    map[string]*route
	put     map[string]*route
	delete  map[string]*route
	command map[string]*route
}

var routerInstance *Router

func Load() *Router {
	if routerInstance != nil {
		return routerInstance
	}

	routerInstance = &Router{
		get:     make(map[string]*route, 0),
		post:    make(map[string]*route, 0),
		put:     make(map[string]*route, 0),
		delete:  make(map[string]*route, 0),
		command: make(map[string]*route, 0),
	}

	return routerInstance
}

type route struct {
	name        string
	pattern     string
	regex       *regexp.Regexp
	packageName string
	action      ActionFunc
	keys        []string
}

func (r *route) Name() string {
	return r.name
}

func (r *route) Keys() []string {
	return r.keys
}

func (r *route) Action() ActionFunc {
	return r.action
}

func (r *route) PackageName() string {
	return r.packageName
}

func (r *Router) Add(packageName string, method routerMethod, name, pattern string, action ActionFunc) {

	route := &route{
		name:        name,
		pattern:     pattern,
		regex:       nil,
		packageName: packageName,
		action:      action,
		keys:        []string{},
	}

	if method != COMMAND {
		route.keys = r.getKeys(pattern)
		regexPattern := pattern
		for i := 0; i < len(route.keys); i++ {
			regexPattern = string551.Replace(regexPattern, ":"+route.keys[i]+":", ".*")
		}
		route.regex = regexp.MustCompile("^" + regexPattern + "$")
	}

	if method&GET == GET {
		r.addGet(name, route)
	}
	if method&POST == POST {
		r.addPost(name, route)
	}
	if method&PUT == PUT {
		r.addPut(name, route)
	}
	if method&DELETE == DELETE {
		r.addDelete(name, route)
	}
	if method&COMMAND == COMMAND {
		r.addCommand(name, route)
	}
}

func (r *Router) addGet(name string, route *route) {
	r.get[name] = route
}

func (r *Router) addPost(name string, route *route) {
	r.post[name] = route
}

func (r *Router) addPut(name string, route *route) {
	r.put[name] = route
}

func (r *Router) addDelete(name string, route *route) {
	r.delete[name] = route
}

func (r *Router) addCommand(name string, route *route) {
	r.command[name] = route
}

func (r *Router) getKeys(pattern string) []string {
	keys := []string{}

	patternBytes := string551.StringToBytes(pattern)
	coron := false

	keyBytes := []byte{}

	for i := 0; i < len(patternBytes); i++ {
		if patternBytes[i] == 0x3A {
			// 0x3A => ":"
			if !coron {
				keyBytes = []byte{}
				coron = true
			} else {
				keys = append(keys, string551.BytesToString(keyBytes))
				coron = false
			}
		} else {
			if coron {
				keyBytes = append(keyBytes, patternBytes[i])
			}
		}
	}

	return keys
}

var regexpControllerPattern *regexp.Regexp

func (r *Router) getPackageName(path string) string {
	if regexpControllerPattern == nil {
		regexpControllerPattern = regexp.MustCompile(`\/\w+\.`)
	}

	packageName := regexpControllerPattern.FindString(path)

	return packageName[1 : len(packageName)-1]
}

func (r *Router) FindRouteByName(method, name string) *route {
	if method == GET.String() {
		return r.get[name]
	}
	if method == POST.String() {
		return r.post[name]
	}
	if method == PUT.String() {
		return r.put[name]
	}
	if method == DELETE.String() {
		return r.delete[name]
	}
	if method == COMMAND.String() {
		return r.command[name]
	}

	return nil
}

func (r *Router) FindRouteByPathMatch(method, path string) *route {
	routes := r.getRoutes(method)

	for _, route := range routes {
		if route.pattern == path {
			return route
		}
	}

	for _, route := range routes {
		if route.pattern == "/" {
			continue
		}
		if route.regex.MatchString(path) {
			return route
		}
	}

	return nil
}

func (r *Router) getRoutes(method string) map[string]*route {
	switch method {
	case GET.String():
		return r.get
	case POST.String():
		return r.post
	case PUT.String():
		return r.put
	case DELETE.String():
		return r.delete
	case COMMAND.String():
		return r.command
	}

	return make(map[string]*route)
}

func (r *Router) Url(name string, parameter ...string) string {

	var route *route = nil

	if r.get[name] != nil {
		route = r.get[name]
	} else if r.post[name] != nil {
		route = r.post[name]
	} else if r.put[name] != nil {
		route = r.put[name]
	} else if r.delete[name] != nil {
		route = r.delete[name]
	}

	if route == nil {
		// Not Found
		return "#"
	}

	if len(parameter) != 0 && len(parameter) != len(route.keys) {
		// No Match
		return "#"
	}

	//----------------------------------------
	// Match
	//----------------------------------------
	if len(parameter) == 0 {
		// No paramter
		return route.pattern
	}

	url := route.pattern
	keys := route.keys
	for i := 0; i < len(parameter); i++ {
		url = string551.Replace(url, ":"+keys[i]+":", parameter[i])

	}

	return url
}
<|endoftext|>


<commit_before>/*
RpcClient for Go RPC Servers
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package rpcclient

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"reflect"
	"strings"
	"sync"
	"syscall"
	"time"
)

const (
	JSON_RPC       = "json"
	JSON_HTTP      = "http_jsonrpc"
	GOB_RPC        = "gob"
	INTERNAL_RPC   = "*internal"
	POOL_FIRST     = "first"
	POOL_RANDOM    = "random"
	POOL_NEXT      = "next"
	POOL_BROADCAST = "broadcast"
)

var (
	ErrReqUnsynchronized       = errors.New("REQ_UNSYNCHRONIZED")
	ErrUnsupporteServiceMethod = errors.New("UNSUPPORTED_SERVICE_METHOD")
	ErrWrongArgsType           = errors.New("WRONG_ARGS_TYPE")
	ErrWrongReplyType          = errors.New("WRONG_REPLY_TYPE")
	ErrDisconnected            = errors.New("DISCONNECTED")
	ErrReplyTimeout            = errors.New("REPLY_TIMEOUT")
	ErrFailedReconnect         = errors.New("FAILED_RECONNECT")
	ErrInternallyDisconnected  = errors.New("INTERNALLY_DISCONNECTED")
	ErrUnsupportedCodec        = errors.New("UNSUPPORTED_CODEC")
	logger                     *syslog.Writer
)

func init() {
	logger, _ = syslog.New(syslog.LOG_INFO, "RPCClient") // If we need to report anything to syslog
}

// successive Fibonacci numbers.
func Fib() func() time.Duration {
	a, b := 0, 1
	return func() time.Duration {
		a, b = b, a+b
		return time.Duration(a*10) * time.Millisecond
	}
}

func NewRpcClient(transport, addr string, connectAttempts, reconnects int, connTimeout, replyTimeout time.Duration, codec string, internalConn RpcClientConnection, lazyConnect bool) (rpcClient *RpcClient, err error) {
	if codec != INTERNAL_RPC && codec != JSON_RPC && codec != JSON_HTTP && codec != GOB_RPC {
		return nil, ErrUnsupportedCodec
	}
	if codec == INTERNAL_RPC && reflect.ValueOf(internalConn).IsNil() {
		return nil, ErrInternallyDisconnected
	}
	rpcClient = &RpcClient{transport: transport, address: addr, reconnects: reconnects,
		connTimeout: connTimeout, replyTimeout: replyTimeout, codec: codec, connection: internalConn}
	if lazyConnect {
		return
	}
	delay := Fib()
	for i := 0; i < connectAttempts; i++ {
		err = rpcClient.connect()
		if err == nil { //Connected so no need to reiterate
			break
		}
		time.Sleep(delay())
	}
	return rpcClient, err
}

type RpcClient struct {
	transport    string
	address      string
	reconnects   int
	connTimeout  time.Duration
	replyTimeout time.Duration
	codec        string // JSON_RPC or GOB_RPC
	connection   RpcClientConnection
	connMux      sync.RWMutex // protects connection
}

func (self *RpcClient) connect() (err error) {
	self.connMux.Lock()
	defer self.connMux.Unlock()
	if self.codec == INTERNAL_RPC {
		if self.connection == nil {
			return ErrDisconnected
		}
		return
	} else if self.codec == JSON_HTTP {
		self.connection = &HttpJsonRpcClient{httpClient: new(http.Client), url: self.address}
		return
	}
	// RPC compliant connections here, manually create connection to timeout
	netconn, err := net.DialTimeout(self.transport, self.address, self.connTimeout)
	if err != nil {
		self.connection = nil // So we don't wrap nil into the interface
		return err
	}
	if self.codec == JSON_RPC {
		self.connection = jsonrpc.NewClient(netconn)
	} else {
		self.connection = rpc.NewClient(netconn)
	}
	return
}

func (self *RpcClient) isConnected() bool {
	self.connMux.RLock()
	defer self.connMux.RUnlock()
	return self.connection != nil
}

func (self *RpcClient) disconnect() (err error) {
	switch self.codec {
	case INTERNAL_RPC, JSON_HTTP:
	default:
		self.connMux.Lock()
		if self.connection != nil {
			self.connection.(*rpc.Client).Close()
			self.connection = nil
		}
		self.connMux.Unlock()
	}
	return nil
}

func (self *RpcClient) reconnect() (err error) {
	self.disconnect()            // make sure we have cleared the connection so it can be garbage collected
	if self.codec == JSON_HTTP { // http client has automatic reconnects in place
		return self.connect()
	}
	i := 0
	delay := Fib()
	for {
		i++
		if self.reconnects != -1 && i > self.reconnects { // Maximum reconnects reached, -1 for infinite reconnects
			break
		}
		if err = self.connect(); err == nil { // No error on connect, succcess
			return nil
		}
		time.Sleep(delay()) // Cound not reconnect, retry
	}
	return ErrFailedReconnect
}

func (self *RpcClient) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	if args == nil {
		return fmt.Errorf("nil rpc in argument method: %s in: %v out: %v", serviceMethod, args, reply)
	}
	errChan := make(chan error, 1)
	go func(serviceMethod string, args interface{}, reply interface{}) {
		self.connMux.RLock()
		if self.connection == nil {
			errChan <- ErrDisconnected
		} else {
			errChan <- self.connection.Call(serviceMethod, args, reply)
		}
		self.connMux.RUnlock()
	}(serviceMethod, args, reply)
	select {
	case err = <-errChan:
	case <-time.After(self.replyTimeout):
		err = ErrReplyTimeout
	}
	if isNetworkError(err) && err != ErrReplyTimeout && self.reconnects != 0 { // ReplyTimeout should not reconnect since it creates loop
		if errReconnect := self.reconnect(); errReconnect != nil {
			return err
		} else { // Run command after reconnect
			self.connMux.RLock()
			defer self.connMux.RUnlock()
			if self.connection == nil {
				err = ErrDisconnected
			} else {
				return self.connection.Call(serviceMethod, args, reply)
			}
		}
	}
	return err
}

// Connection used in RpcClient, as interface so we can combine the rpc.RpcClient with http one or websocket
type RpcClientConnection interface {
	Call(string, interface{}, interface{}) error
}

// Response received for
type JsonRpcResponse struct {
	Id     uint64
	Result *json.RawMessage
	Error  interface{}
}

type HttpJsonRpcClient struct {
	httpClient *http.Client
	id         uint64
	url        string
}

func (self *HttpJsonRpcClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
	self.id += 1
	id := self.id
	data, err := json.Marshal(map[string]interface{}{
		"method": serviceMethod,
		"id":     self.id,
		"params": [1]interface{}{args},
	})
	if err != nil {
		return err
	}
	resp, err := self.httpClient.Post(self.url, "application/json", ioutil.NopCloser(strings.NewReader(string(data)))) // Closer so we automatically have close after response
	if err != nil {
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var jsonRsp JsonRpcResponse
	err = json.Unmarshal(body, &jsonRsp)
	if err != nil {
		return err
	}
	if jsonRsp.Id != id {
		return ErrReqUnsynchronized
	}
	if jsonRsp.Error != nil || jsonRsp.Result == nil {
		x, ok := jsonRsp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", jsonRsp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return errors.New(x)
	}
	return json.Unmarshal(*jsonRsp.Result, reply)
}

type RpcClientPool struct {
	transmissionType string
	connections      []RpcClientConnection
	counter          int
	replyTimeout     time.Duration
}

func NewRpcClientPool(transmissionType string, replyTimeout time.Duration) *RpcClientPool {
	return &RpcClientPool{transmissionType: transmissionType, replyTimeout: replyTimeout}
}

func (pool *RpcClientPool) AddClient(rcc RpcClientConnection) {
	if rcc != nil && !reflect.ValueOf(rcc).IsNil() {
		pool.connections = append(pool.connections, rcc)
	}
}

func (pool *RpcClientPool) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	switch pool.transmissionType {
	case POOL_BROADCAST:
		replyChan := make(chan *rpcReplyError, len(pool.connections))
		for _, rc := range pool.connections {
			go func(conn RpcClientConnection) {
				// make a new pointer of the same type
				rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface()))
				err := conn.Call(serviceMethod, args, rpl.Interface())
				if !isNetworkError(err) {
					replyChan <- &rpcReplyError{reply: rpl.Interface(), err: err}
				}
			}(rc)
		}
		//get first response with timeout
		var re *rpcReplyError
		select {
		case re = <-replyChan:
		case <-time.After(pool.replyTimeout):
			return ErrReplyTimeout
		}
		// put received value in the orig reply
		reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(re.reply).Elem())
		return re.err
	case POOL_FIRST:
		for _, rc := range pool.connections {
			err = rc.Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_NEXT:
		ln := len(pool.connections)
		rrIndexes := roundIndex(int(math.Mod(float64(pool.counter), float64(ln))), ln)
		pool.counter++
		for _, index := range rrIndexes {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_RANDOM:
		rand.Seed(time.Now().UnixNano())
		randomIndex := rand.Perm(len(pool.connections))
		for _, index := range randomIndex {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	}
	return
}

type rpcReplyError struct {
	reply interface{}
	err   error
}

// generates round robin indexes for a slice of length max
// starting from index start
func roundIndex(start, max int) []int {
	if start < 0 {
		start = 0
	}
	result := make([]int, max)
	for i := 0; i < max; i++ {
		if start+i < max {
			result[i] = start + i
		} else {
			result[i] = int(math.Abs(float64(max - (start + i))))
		}
	}
	return result
}

func isNetworkError(err error) bool {
	if err == nil {
		return false
	}
	if operr, ok := err.(*net.OpError); ok && strings.HasSuffix(operr.Err.Error(), syscall.ECONNRESET.Error()) { // connection reset
		return true
	}
	return err == rpc.ErrShutdown ||
		err == ErrReqUnsynchronized ||
		err == ErrDisconnected ||
		err == ErrReplyTimeout ||
		strings.HasPrefix(err.Error(), "rpc: can't find service")
}
<commit_msg>Failover on ErrSessionNotFound<commit_after>/*
RpcClient for Go RPC Servers
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

package rpcclient

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"log/syslog"
	"math"
	"math/rand"
	"net"
	"net/http"
	"net/rpc"
	"net/rpc/jsonrpc"
	"reflect"
	"strings"
	"sync"
	"syscall"
	"time"
)

const (
	JSON_RPC       = "json"
	JSON_HTTP      = "http_jsonrpc"
	GOB_RPC        = "gob"
	INTERNAL_RPC   = "*internal"
	POOL_FIRST     = "first"
	POOL_RANDOM    = "random"
	POOL_NEXT      = "next"
	POOL_BROADCAST = "broadcast"
)

var (
	ErrReqUnsynchronized       = errors.New("REQ_UNSYNCHRONIZED")
	ErrUnsupporteServiceMethod = errors.New("UNSUPPORTED_SERVICE_METHOD")
	ErrWrongArgsType           = errors.New("WRONG_ARGS_TYPE")
	ErrWrongReplyType          = errors.New("WRONG_REPLY_TYPE")
	ErrDisconnected            = errors.New("DISCONNECTED")
	ErrReplyTimeout            = errors.New("REPLY_TIMEOUT")
	ErrFailedReconnect         = errors.New("FAILED_RECONNECT")
	ErrInternallyDisconnected  = errors.New("INTERNALLY_DISCONNECTED")
	ErrUnsupportedCodec        = errors.New("UNSUPPORTED_CODEC")
	ErrSessionNotFound         = errors.New("SESSION_NOT_FOUND")
	logger                     *syslog.Writer
)

func init() {
	logger, _ = syslog.New(syslog.LOG_INFO, "RPCClient") // If we need to report anything to syslog
}

// successive Fibonacci numbers.
func Fib() func() time.Duration {
	a, b := 0, 1
	return func() time.Duration {
		a, b = b, a+b
		return time.Duration(a*10) * time.Millisecond
	}
}

func NewRpcClient(transport, addr string, connectAttempts, reconnects int, connTimeout, replyTimeout time.Duration, codec string, internalConn RpcClientConnection, lazyConnect bool) (rpcClient *RpcClient, err error) {
	if codec != INTERNAL_RPC && codec != JSON_RPC && codec != JSON_HTTP && codec != GOB_RPC {
		return nil, ErrUnsupportedCodec
	}
	if codec == INTERNAL_RPC && reflect.ValueOf(internalConn).IsNil() {
		return nil, ErrInternallyDisconnected
	}
	rpcClient = &RpcClient{transport: transport, address: addr, reconnects: reconnects,
		connTimeout: connTimeout, replyTimeout: replyTimeout, codec: codec, connection: internalConn}
	if lazyConnect {
		return
	}
	delay := Fib()
	for i := 0; i < connectAttempts; i++ {
		err = rpcClient.connect()
		if err == nil { //Connected so no need to reiterate
			break
		}
		time.Sleep(delay())
	}
	return rpcClient, err
}

type RpcClient struct {
	transport    string
	address      string
	reconnects   int
	connTimeout  time.Duration
	replyTimeout time.Duration
	codec        string // JSON_RPC or GOB_RPC
	connection   RpcClientConnection
	connMux      sync.RWMutex // protects connection
}

func (self *RpcClient) connect() (err error) {
	self.connMux.Lock()
	defer self.connMux.Unlock()
	if self.codec == INTERNAL_RPC {
		if self.connection == nil {
			return ErrDisconnected
		}
		return
	} else if self.codec == JSON_HTTP {
		self.connection = &HttpJsonRpcClient{httpClient: new(http.Client), url: self.address}
		return
	}
	// RPC compliant connections here, manually create connection to timeout
	netconn, err := net.DialTimeout(self.transport, self.address, self.connTimeout)
	if err != nil {
		self.connection = nil // So we don't wrap nil into the interface
		return err
	}
	if self.codec == JSON_RPC {
		self.connection = jsonrpc.NewClient(netconn)
	} else {
		self.connection = rpc.NewClient(netconn)
	}
	return
}

func (self *RpcClient) isConnected() bool {
	self.connMux.RLock()
	defer self.connMux.RUnlock()
	return self.connection != nil
}

func (self *RpcClient) disconnect() (err error) {
	switch self.codec {
	case INTERNAL_RPC, JSON_HTTP:
	default:
		self.connMux.Lock()
		if self.connection != nil {
			self.connection.(*rpc.Client).Close()
			self.connection = nil
		}
		self.connMux.Unlock()
	}
	return nil
}

func (self *RpcClient) reconnect() (err error) {
	self.disconnect()            // make sure we have cleared the connection so it can be garbage collected
	if self.codec == JSON_HTTP { // http client has automatic reconnects in place
		return self.connect()
	}
	i := 0
	delay := Fib()
	for {
		i++
		if self.reconnects != -1 && i > self.reconnects { // Maximum reconnects reached, -1 for infinite reconnects
			break
		}
		if err = self.connect(); err == nil { // No error on connect, succcess
			return nil
		}
		time.Sleep(delay()) // Cound not reconnect, retry
	}
	return ErrFailedReconnect
}

func (self *RpcClient) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	if args == nil {
		return fmt.Errorf("nil rpc in argument method: %s in: %v out: %v", serviceMethod, args, reply)
	}
	errChan := make(chan error, 1)
	go func(serviceMethod string, args interface{}, reply interface{}) {
		self.connMux.RLock()
		if self.connection == nil {
			errChan <- ErrDisconnected
		} else {
			errChan <- self.connection.Call(serviceMethod, args, reply)
		}
		self.connMux.RUnlock()
	}(serviceMethod, args, reply)
	select {
	case err = <-errChan:
	case <-time.After(self.replyTimeout):
		err = ErrReplyTimeout
	}
	if isNetworkError(err) && err != ErrReplyTimeout &&
		err.Error() != ErrSessionNotFound.Error() &&
		self.reconnects != 0 { // ReplyTimeout should not reconnect since it creates loop
		if errReconnect := self.reconnect(); errReconnect != nil {
			return err
		} else { // Run command after reconnect
			self.connMux.RLock()
			defer self.connMux.RUnlock()
			if self.connection == nil {
				err = ErrDisconnected
			} else {
				return self.connection.Call(serviceMethod, args, reply)
			}
		}
	}
	return err
}

// Connection used in RpcClient, as interface so we can combine the rpc.RpcClient with http one or websocket
type RpcClientConnection interface {
	Call(string, interface{}, interface{}) error
}

// Response received for
type JsonRpcResponse struct {
	Id     uint64
	Result *json.RawMessage
	Error  interface{}
}

type HttpJsonRpcClient struct {
	httpClient *http.Client
	id         uint64
	url        string
}

func (self *HttpJsonRpcClient) Call(serviceMethod string, args interface{}, reply interface{}) error {
	self.id += 1
	id := self.id
	data, err := json.Marshal(map[string]interface{}{
		"method": serviceMethod,
		"id":     self.id,
		"params": [1]interface{}{args},
	})
	if err != nil {
		return err
	}
	resp, err := self.httpClient.Post(self.url, "application/json", ioutil.NopCloser(strings.NewReader(string(data)))) // Closer so we automatically have close after response
	if err != nil {
		return err
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var jsonRsp JsonRpcResponse
	err = json.Unmarshal(body, &jsonRsp)
	if err != nil {
		return err
	}
	if jsonRsp.Id != id {
		return ErrReqUnsynchronized
	}
	if jsonRsp.Error != nil || jsonRsp.Result == nil {
		x, ok := jsonRsp.Error.(string)
		if !ok {
			return fmt.Errorf("invalid error %v", jsonRsp.Error)
		}
		if x == "" {
			x = "unspecified error"
		}
		return errors.New(x)
	}
	return json.Unmarshal(*jsonRsp.Result, reply)
}

type RpcClientPool struct {
	transmissionType string
	connections      []RpcClientConnection
	counter          int
	replyTimeout     time.Duration
}

func NewRpcClientPool(transmissionType string, replyTimeout time.Duration) *RpcClientPool {
	return &RpcClientPool{transmissionType: transmissionType, replyTimeout: replyTimeout}
}

func (pool *RpcClientPool) AddClient(rcc RpcClientConnection) {
	if rcc != nil && !reflect.ValueOf(rcc).IsNil() {
		pool.connections = append(pool.connections, rcc)
	}
}

func (pool *RpcClientPool) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
	switch pool.transmissionType {
	case POOL_BROADCAST:
		replyChan := make(chan *rpcReplyError, len(pool.connections))
		for _, rc := range pool.connections {
			go func(conn RpcClientConnection) {
				// make a new pointer of the same type
				rpl := reflect.New(reflect.TypeOf(reflect.ValueOf(reply).Elem().Interface()))
				err := conn.Call(serviceMethod, args, rpl.Interface())
				if !isNetworkError(err) {
					replyChan <- &rpcReplyError{reply: rpl.Interface(), err: err}
				}
			}(rc)
		}
		//get first response with timeout
		var re *rpcReplyError
		select {
		case re = <-replyChan:
		case <-time.After(pool.replyTimeout):
			return ErrReplyTimeout
		}
		// put received value in the orig reply
		reflect.ValueOf(reply).Elem().Set(reflect.ValueOf(re.reply).Elem())
		return re.err
	case POOL_FIRST:
		for _, rc := range pool.connections {
			err = rc.Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_NEXT:
		ln := len(pool.connections)
		rrIndexes := roundIndex(int(math.Mod(float64(pool.counter), float64(ln))), ln)
		pool.counter++
		for _, index := range rrIndexes {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	case POOL_RANDOM:
		rand.Seed(time.Now().UnixNano())
		randomIndex := rand.Perm(len(pool.connections))
		for _, index := range randomIndex {
			err = pool.connections[index].Call(serviceMethod, args, reply)
			if isNetworkError(err) {
				continue
			}
			return
		}
	}
	return
}

type rpcReplyError struct {
	reply interface{}
	err   error
}

// generates round robin indexes for a slice of length max
// starting from index start
func roundIndex(start, max int) []int {
	if start < 0 {
		start = 0
	}
	result := make([]int, max)
	for i := 0; i < max; i++ {
		if start+i < max {
			result[i] = start + i
		} else {
			result[i] = int(math.Abs(float64(max - (start + i))))
		}
	}
	return result
}

func isNetworkError(err error) bool {
	if err == nil {
		return false
	}
	if operr, ok := err.(*net.OpError); ok && strings.HasSuffix(operr.Err.Error(), syscall.ECONNRESET.Error()) { // connection reset
		return true
	}
	return err == rpc.ErrShutdown ||
		err == ErrReqUnsynchronized ||
		err == ErrDisconnected ||
		err == ErrReplyTimeout ||
		err.Error() == ErrSessionNotFound.Error() ||
		strings.HasPrefix(err.Error(), "rpc: can't find service")
}
<|endoftext|>