<commit_before>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"github.com/keybase/client/go/libkb"
)

// LoginCurrentDevice is an engine that tries to login using the
// current device, if there is an existing provisioned device.
type LoginCurrentDevice struct {
	libkb.Contextified
	username string
}

// NewLoginCurrentDevice creates a LoginCurrentDevice engine.
func NewLoginCurrentDevice(g *libkb.GlobalContext, username string) *LoginCurrentDevice {
	return &LoginCurrentDevice{
		username:     username,
		Contextified: libkb.NewContextified(g),
	}
}

// Name is the unique engine name.
func (e *LoginCurrentDevice) Name() string {
	return "LoginCurrentDevice"
}

// GetPrereqs returns the engine prereqs.
func (e *LoginCurrentDevice) Prereqs() Prereqs {
	return Prereqs{}
}

// RequiredUIs returns the required UIs.
func (e *LoginCurrentDevice) RequiredUIs() []libkb.UIKind {
	return []libkb.UIKind{}
}

// SubConsumers returns the other UI consumers for this engine.
func (e *LoginCurrentDevice) SubConsumers() []libkb.UIConsumer {
	return nil
}

// Run starts the engine.
func (e *LoginCurrentDevice) Run(ctx *Context) error {
	// already logged in?
	in, err := e.G().LoginState().LoggedInProvisionedLoad()
	if err == nil && in {
		return nil
	}

	var config *libkb.UserConfig
	if len(e.username) == 0 {
		config, err = e.G().Env.GetConfig().GetUserConfig()
	} else {
		nu := libkb.NewNormalizedUsername(e.username)
		config, err = e.G().Env.GetConfig().GetUserConfigForUsername(nu)
	}
	if err != nil {
		e.G().Log.Debug("error getting user config: %s (%T)", err, err)
		return errNoConfig
	}
	if config == nil {
		e.G().Log.Debug("user config is nil")
		return errNoConfig
	}
	if config.GetDeviceID().IsNil() {
		e.G().Log.Debug("no device in user config")
		return errNoDevice
	}

	// at this point, there is a user config either for the current user or for e.username
	// and it has a device id, so this should be a provisioned device.  Thus, they should
	// just login normally.

	var afterLogin = func(lctx libkb.LoginContext) error {
		if err := lctx.LocalSession().SetDeviceProvisioned(e.G().Env.GetDeviceID()); err != nil {
			// not a fatal error, session will stay in memory
			e.G().Log.Warning("error saving session file: %s", err)
		}
		return nil
	}
	return e.G().LoginState().LoginWithPrompt(e.username, ctx.LoginUI, ctx.SecretUI, afterLogin)
}
<commit_msg>Fix login w/ username arg when logged in as different user<commit_after>// Copyright 2015 Keybase, Inc. All rights reserved. Use of
// this source code is governed by the included BSD license.

package engine

import (
	"github.com/keybase/client/go/libkb"
)

// LoginCurrentDevice is an engine that tries to login using the
// current device, if there is an existing provisioned device.
type LoginCurrentDevice struct {
	libkb.Contextified
	username string
}

// NewLoginCurrentDevice creates a LoginCurrentDevice engine.
func NewLoginCurrentDevice(g *libkb.GlobalContext, username string) *LoginCurrentDevice {
	return &LoginCurrentDevice{
		username:     username,
		Contextified: libkb.NewContextified(g),
	}
}

// Name is the unique engine name.
func (e *LoginCurrentDevice) Name() string {
	return "LoginCurrentDevice"
}

// GetPrereqs returns the engine prereqs.
func (e *LoginCurrentDevice) Prereqs() Prereqs {
	return Prereqs{}
}

// RequiredUIs returns the required UIs.
func (e *LoginCurrentDevice) RequiredUIs() []libkb.UIKind {
	return []libkb.UIKind{}
}

// SubConsumers returns the other UI consumers for this engine.
func (e *LoginCurrentDevice) SubConsumers() []libkb.UIConsumer {
	return nil
}

// Run starts the engine.
func (e *LoginCurrentDevice) Run(ctx *Context) error {
	// already logged in?
	in, err := e.G().LoginState().LoggedInProvisionedLoad()
	if err == nil && in {
		if len(e.username) == 0 || e.G().Env.GetUsername() == libkb.NewNormalizedUsername(e.username) {
			return nil
		}
	}

	var config *libkb.UserConfig
	if len(e.username) == 0 {
		config, err = e.G().Env.GetConfig().GetUserConfig()
	} else {
		nu := libkb.NewNormalizedUsername(e.username)
		config, err = e.G().Env.GetConfig().GetUserConfigForUsername(nu)
	}
	if err != nil {
		e.G().Log.Debug("error getting user config: %s (%T)", err, err)
		return errNoConfig
	}
	if config == nil {
		e.G().Log.Debug("user config is nil")
		return errNoConfig
	}
	if config.GetDeviceID().IsNil() {
		e.G().Log.Debug("no device in user config")
		return errNoDevice
	}

	// at this point, there is a user config either for the current user or for e.username
	// and it has a device id, so this should be a provisioned device.  Thus, they should
	// just login normally.

	var afterLogin = func(lctx libkb.LoginContext) error {
		if err := lctx.LocalSession().SetDeviceProvisioned(e.G().Env.GetDeviceID()); err != nil {
			// not a fatal error, session will stay in memory
			e.G().Log.Warning("error saving session file: %s", err)
		}
		return nil
	}
	return e.G().LoginState().LoginWithPrompt(e.username, ctx.LoginUI, ctx.SecretUI, afterLogin)
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"os"
	"strconv"

	"github.com/koding/multiconfig"
)

var conf *Config

// Returns config, if it is nil, panics
func MustGet() *Config {
	if conf == nil {
		panic("config is not set, please call Config.MustRead(pathToConfFile)")
	}

	return conf
}

// MustRead takes a relative file path
// and tries to open and read it into Config struct
// If file is not there or file is not given, it panics
// If the given file is not formatted well, panics
func MustRead(path string) *Config {
	conf = &Config{}

	d := &multiconfig.DefaultLoader{
		Loader: multiconfig.MultiLoader(
			&multiconfig.TOMLLoader{Path: path},
			&multiconfig.EnvironmentLoader{Prefix: "KONFIG_SOCIALAPI"},
		),
	}

	d.MustLoad(conf)

	return conf
}

func getStringEnvVar(field *string, envVarName string) {
	env := os.Getenv(envVarName)
	if env != "" {
		*field = env
	}
}

func getIntEnvVar(field *int, envVarName string) {
	env := os.Getenv(envVarName)
	if env != "" {
		value, err := strconv.Atoi(env)
		if err != nil {
			panic(fmt.Errorf("couldn't parse env variable: %s", envVarName))
		}
		*field = value
	}
}
<commit_msg>Social: add support for reading only one key from the env variables<commit_after>package config

import (
	"encoding/json"
	"os"

	"github.com/koding/multiconfig"
)

var conf *Config

// Returns config, if it is nil, panics
func MustGet() *Config {
	if conf == nil {
		panic("config is not set, please call Config.MustRead(pathToConfFile)")
	}

	return conf
}

// MustRead takes a relative file path
// and tries to open and read it into Config struct
// If file is not there or file is not given, it panics
// If the given file is not formatted well, panics
func MustRead(path string) *Config {
	conf = &Config{}

	d := &multiconfig.DefaultLoader{
		Loader: multiconfig.MultiLoader(
			&multiconfig.TOMLLoader{Path: path},
			&multiconfig.EnvironmentLoader{Prefix: "KONFIG_SOCIALAPI"},
		),
	}

	d.MustLoad(conf)

	return conf
}

type EnvJSONLoader struct {
	Name string
}

func (e *EnvJSONLoader) Load(s interface{}) error {
	v := os.Getenv(e.Name)
	if v == "" {
		// we are ignoring this case when the config is not set
		// reading a Specific Env key is not mandatory
		return nil
	}

	return json.Unmarshal([]byte{v}, s)
}
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"
)

func GetComputeForwardingRuleCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := replaceVars(d, config, "//compute.googleapis.com/projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetComputeForwardingRuleApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "compute.googleapis.com/ForwardingRule",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
				DiscoveryName:        "ForwardingRule",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetComputeForwardingRuleApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	descriptionProp, err := expandComputeForwardingRuleDescription(d.Get("description"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
		obj["description"] = descriptionProp
	}
	IPAddressProp, err := expandComputeForwardingRuleIPAddress(d.Get("ip_address"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_address"); !isEmptyValue(reflect.ValueOf(IPAddressProp)) && (ok || !reflect.DeepEqual(v, IPAddressProp)) {
		obj["IPAddress"] = IPAddressProp
	}
	IPProtocolProp, err := expandComputeForwardingRuleIPProtocol(d.Get("ip_protocol"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_protocol"); !isEmptyValue(reflect.ValueOf(IPProtocolProp)) && (ok || !reflect.DeepEqual(v, IPProtocolProp)) {
		obj["IPProtocol"] = IPProtocolProp
	}
	backendServiceProp, err := expandComputeForwardingRuleBackendService(d.Get("backend_service"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("backend_service"); !isEmptyValue(reflect.ValueOf(backendServiceProp)) && (ok || !reflect.DeepEqual(v, backendServiceProp)) {
		obj["backendService"] = backendServiceProp
	}
	ipVersionProp, err := expandComputeForwardingRuleIpVersion(d.Get("ip_version"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_version"); !isEmptyValue(reflect.ValueOf(ipVersionProp)) && (ok || !reflect.DeepEqual(v, ipVersionProp)) {
		obj["ipVersion"] = ipVersionProp
	}
	loadBalancingSchemeProp, err := expandComputeForwardingRuleLoadBalancingScheme(d.Get("load_balancing_scheme"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("load_balancing_scheme"); !isEmptyValue(reflect.ValueOf(loadBalancingSchemeProp)) && (ok || !reflect.DeepEqual(v, loadBalancingSchemeProp)) {
		obj["loadBalancingScheme"] = loadBalancingSchemeProp
	}
	nameProp, err := expandComputeForwardingRuleName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	networkProp, err := expandComputeForwardingRuleNetwork(d.Get("network"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network"); !isEmptyValue(reflect.ValueOf(networkProp)) && (ok || !reflect.DeepEqual(v, networkProp)) {
		obj["network"] = networkProp
	}
	portRangeProp, err := expandComputeForwardingRulePortRange(d.Get("port_range"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("port_range"); !isEmptyValue(reflect.ValueOf(portRangeProp)) && (ok || !reflect.DeepEqual(v, portRangeProp)) {
		obj["portRange"] = portRangeProp
	}
	portsProp, err := expandComputeForwardingRulePorts(d.Get("ports"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ports"); !isEmptyValue(reflect.ValueOf(portsProp)) && (ok || !reflect.DeepEqual(v, portsProp)) {
		obj["ports"] = portsProp
	}
	subnetworkProp, err := expandComputeForwardingRuleSubnetwork(d.Get("subnetwork"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("subnetwork"); !isEmptyValue(reflect.ValueOf(subnetworkProp)) && (ok || !reflect.DeepEqual(v, subnetworkProp)) {
		obj["subnetwork"] = subnetworkProp
	}
	targetProp, err := expandComputeForwardingRuleTarget(d.Get("target"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target"); !isEmptyValue(reflect.ValueOf(targetProp)) && (ok || !reflect.DeepEqual(v, targetProp)) {
		obj["target"] = targetProp
	}
	networkTierProp, err := expandComputeForwardingRuleNetworkTier(d.Get("network_tier"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network_tier"); !isEmptyValue(reflect.ValueOf(networkTierProp)) && (ok || !reflect.DeepEqual(v, networkTierProp)) {
		obj["networkTier"] = networkTierProp
	}
	regionProp, err := expandComputeForwardingRuleRegion(d.Get("region"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("region"); !isEmptyValue(reflect.ValueOf(regionProp)) && (ok || !reflect.DeepEqual(v, regionProp)) {
		obj["region"] = regionProp
	}

	return obj, nil
}

func expandComputeForwardingRuleDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleIPAddress(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleIPProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleBackendService(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	// This method returns a full self link from a partial self link.
	if v == nil || v.(string) == "" {
		// It does not try to construct anything from empty.
		return "", nil
	} else if strings.HasPrefix(v.(string), "https://") {
		// Anything that starts with a URL scheme is assumed to be a self link worth using.
		return v, nil
	} else if strings.HasPrefix(v.(string), "projects/") {
		// If the self link references a project, we'll just stuck the compute v1 prefix on it.
		return "https://www.googleapis.com/compute/v1/" + v.(string), nil
	} else if strings.HasPrefix(v.(string), "regions/") || strings.HasPrefix(v.(string), "zones/") {
		// For regional or zonal resources which include their region or zone, just put the project in front.
		url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/")
		if err != nil {
			return nil, err
		}
		return url + v.(string), nil
	}
	// Anything else is assumed to be a regional resource, with a partial link that begins with the resource name.
	// This isn't very likely - it's a last-ditch effort to extract something useful here.  We can do a better job
	// as soon as MultiResourceRefs are working since we'll know the types that this field is supposed to point to.
	url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/regions/{{region}}/")
	if err != nil {
		return nil, err
	}
	return url + v.(string), nil
}

func expandComputeForwardingRuleIpVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleLoadBalancingScheme(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleNetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("networks", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for network: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeForwardingRulePortRange(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRulePorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v.(*schema.Set).List(), nil
}

func expandComputeForwardingRuleSubnetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseRegionalFieldValue("subnetworks", v.(string), "project", "region", "zone", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for subnetwork: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeForwardingRuleTarget(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	// This method returns a full self link from a partial self link.
	if v == nil || v.(string) == "" {
		// It does not try to construct anything from empty.
		return "", nil
	} else if strings.HasPrefix(v.(string), "https://") {
		// Anything that starts with a URL scheme is assumed to be a self link worth using.
		return v, nil
	} else if strings.HasPrefix(v.(string), "projects/") {
		// If the self link references a project, we'll just stuck the compute v1 prefix on it.
		return "https://www.googleapis.com/compute/v1/" + v.(string), nil
	} else if strings.HasPrefix(v.(string), "regions/") || strings.HasPrefix(v.(string), "zones/") {
		// For regional or zonal resources which include their region or zone, just put the project in front.
		url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/")
		if err != nil {
			return nil, err
		}
		return url + v.(string), nil
	}
	// Anything else is assumed to be a regional resource, with a partial link that begins with the resource name.
	// This isn't very likely - it's a last-ditch effort to extract something useful here.  We can do a better job
	// as soon as MultiResourceRefs are working since we'll know the types that this field is supposed to point to.
	url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/regions/{{region}}/")
	if err != nil {
		return nil, err
	}
	return url + v.(string), nil
}

func expandComputeForwardingRuleNetworkTier(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleRegion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("regions", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for region: %s", err)
	}
	return f.RelativeLink(), nil
}
<commit_msg>Mark Forwarding Rule allPorts as GA (#32)<commit_after>// ----------------------------------------------------------------------------
//
//     ***     AUTO GENERATED CODE    ***    AUTO GENERATED CODE     ***
//
// ----------------------------------------------------------------------------
//
//     This file is automatically generated by Magic Modules and manual
//     changes will be clobbered when the file is regenerated.
//
//     Please read more about how to change this file in
//     .github/CONTRIBUTING.md.
//
// ----------------------------------------------------------------------------

package google

import (
	"fmt"
	"reflect"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"
)

func GetComputeForwardingRuleCaiObject(d TerraformResourceData, config *Config) (Asset, error) {
	name, err := replaceVars(d, config, "//compute.googleapis.com/projects/{{project}}/regions/{{region}}/forwardingRules/{{name}}")
	if err != nil {
		return Asset{}, err
	}
	if obj, err := GetComputeForwardingRuleApiObject(d, config); err == nil {
		return Asset{
			Name: name,
			Type: "compute.googleapis.com/ForwardingRule",
			Resource: &AssetResource{
				Version:              "v1",
				DiscoveryDocumentURI: "https://www.googleapis.com/discovery/v1/apis/compute/v1/rest",
				DiscoveryName:        "ForwardingRule",
				Data:                 obj,
			},
		}, nil
	} else {
		return Asset{}, err
	}
}

func GetComputeForwardingRuleApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) {
	obj := make(map[string]interface{})
	descriptionProp, err := expandComputeForwardingRuleDescription(d.Get("description"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
		obj["description"] = descriptionProp
	}
	IPAddressProp, err := expandComputeForwardingRuleIPAddress(d.Get("ip_address"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_address"); !isEmptyValue(reflect.ValueOf(IPAddressProp)) && (ok || !reflect.DeepEqual(v, IPAddressProp)) {
		obj["IPAddress"] = IPAddressProp
	}
	IPProtocolProp, err := expandComputeForwardingRuleIPProtocol(d.Get("ip_protocol"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_protocol"); !isEmptyValue(reflect.ValueOf(IPProtocolProp)) && (ok || !reflect.DeepEqual(v, IPProtocolProp)) {
		obj["IPProtocol"] = IPProtocolProp
	}
	backendServiceProp, err := expandComputeForwardingRuleBackendService(d.Get("backend_service"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("backend_service"); !isEmptyValue(reflect.ValueOf(backendServiceProp)) && (ok || !reflect.DeepEqual(v, backendServiceProp)) {
		obj["backendService"] = backendServiceProp
	}
	ipVersionProp, err := expandComputeForwardingRuleIpVersion(d.Get("ip_version"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ip_version"); !isEmptyValue(reflect.ValueOf(ipVersionProp)) && (ok || !reflect.DeepEqual(v, ipVersionProp)) {
		obj["ipVersion"] = ipVersionProp
	}
	loadBalancingSchemeProp, err := expandComputeForwardingRuleLoadBalancingScheme(d.Get("load_balancing_scheme"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("load_balancing_scheme"); !isEmptyValue(reflect.ValueOf(loadBalancingSchemeProp)) && (ok || !reflect.DeepEqual(v, loadBalancingSchemeProp)) {
		obj["loadBalancingScheme"] = loadBalancingSchemeProp
	}
	nameProp, err := expandComputeForwardingRuleName(d.Get("name"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
		obj["name"] = nameProp
	}
	networkProp, err := expandComputeForwardingRuleNetwork(d.Get("network"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network"); !isEmptyValue(reflect.ValueOf(networkProp)) && (ok || !reflect.DeepEqual(v, networkProp)) {
		obj["network"] = networkProp
	}
	portRangeProp, err := expandComputeForwardingRulePortRange(d.Get("port_range"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("port_range"); !isEmptyValue(reflect.ValueOf(portRangeProp)) && (ok || !reflect.DeepEqual(v, portRangeProp)) {
		obj["portRange"] = portRangeProp
	}
	portsProp, err := expandComputeForwardingRulePorts(d.Get("ports"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("ports"); !isEmptyValue(reflect.ValueOf(portsProp)) && (ok || !reflect.DeepEqual(v, portsProp)) {
		obj["ports"] = portsProp
	}
	subnetworkProp, err := expandComputeForwardingRuleSubnetwork(d.Get("subnetwork"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("subnetwork"); !isEmptyValue(reflect.ValueOf(subnetworkProp)) && (ok || !reflect.DeepEqual(v, subnetworkProp)) {
		obj["subnetwork"] = subnetworkProp
	}
	targetProp, err := expandComputeForwardingRuleTarget(d.Get("target"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("target"); !isEmptyValue(reflect.ValueOf(targetProp)) && (ok || !reflect.DeepEqual(v, targetProp)) {
		obj["target"] = targetProp
	}
	allPortsProp, err := expandComputeForwardingRuleAllPorts(d.Get("all_ports"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("all_ports"); !isEmptyValue(reflect.ValueOf(allPortsProp)) && (ok || !reflect.DeepEqual(v, allPortsProp)) {
		obj["allPorts"] = allPortsProp
	}
	networkTierProp, err := expandComputeForwardingRuleNetworkTier(d.Get("network_tier"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("network_tier"); !isEmptyValue(reflect.ValueOf(networkTierProp)) && (ok || !reflect.DeepEqual(v, networkTierProp)) {
		obj["networkTier"] = networkTierProp
	}
	regionProp, err := expandComputeForwardingRuleRegion(d.Get("region"), d, config)
	if err != nil {
		return nil, err
	} else if v, ok := d.GetOkExists("region"); !isEmptyValue(reflect.ValueOf(regionProp)) && (ok || !reflect.DeepEqual(v, regionProp)) {
		obj["region"] = regionProp
	}

	return obj, nil
}

func expandComputeForwardingRuleDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleIPAddress(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleIPProtocol(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleBackendService(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	// This method returns a full self link from a partial self link.
	if v == nil || v.(string) == "" {
		// It does not try to construct anything from empty.
		return "", nil
	} else if strings.HasPrefix(v.(string), "https://") {
		// Anything that starts with a URL scheme is assumed to be a self link worth using.
		return v, nil
	} else if strings.HasPrefix(v.(string), "projects/") {
		// If the self link references a project, we'll just stuck the compute v1 prefix on it.
		return "https://www.googleapis.com/compute/v1/" + v.(string), nil
	} else if strings.HasPrefix(v.(string), "regions/") || strings.HasPrefix(v.(string), "zones/") {
		// For regional or zonal resources which include their region or zone, just put the project in front.
		url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/")
		if err != nil {
			return nil, err
		}
		return url + v.(string), nil
	}
	// Anything else is assumed to be a regional resource, with a partial link that begins with the resource name.
	// This isn't very likely - it's a last-ditch effort to extract something useful here.  We can do a better job
	// as soon as MultiResourceRefs are working since we'll know the types that this field is supposed to point to.
	url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/regions/{{region}}/")
	if err != nil {
		return nil, err
	}
	return url + v.(string), nil
}

func expandComputeForwardingRuleIpVersion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleLoadBalancingScheme(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleNetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("networks", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for network: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeForwardingRulePortRange(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRulePorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v.(*schema.Set).List(), nil
}

func expandComputeForwardingRuleSubnetwork(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseRegionalFieldValue("subnetworks", v.(string), "project", "region", "zone", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for subnetwork: %s", err)
	}
	return f.RelativeLink(), nil
}

func expandComputeForwardingRuleTarget(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	// This method returns a full self link from a partial self link.
	if v == nil || v.(string) == "" {
		// It does not try to construct anything from empty.
		return "", nil
	} else if strings.HasPrefix(v.(string), "https://") {
		// Anything that starts with a URL scheme is assumed to be a self link worth using.
		return v, nil
	} else if strings.HasPrefix(v.(string), "projects/") {
		// If the self link references a project, we'll just stuck the compute v1 prefix on it.
		return "https://www.googleapis.com/compute/v1/" + v.(string), nil
	} else if strings.HasPrefix(v.(string), "regions/") || strings.HasPrefix(v.(string), "zones/") {
		// For regional or zonal resources which include their region or zone, just put the project in front.
		url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/")
		if err != nil {
			return nil, err
		}
		return url + v.(string), nil
	}
	// Anything else is assumed to be a regional resource, with a partial link that begins with the resource name.
	// This isn't very likely - it's a last-ditch effort to extract something useful here.  We can do a better job
	// as soon as MultiResourceRefs are working since we'll know the types that this field is supposed to point to.
	url, err := replaceVars(d, config, "https://www.googleapis.com/compute/v1/projects/{{project}}/regions/{{region}}/")
	if err != nil {
		return nil, err
	}
	return url + v.(string), nil
}

func expandComputeForwardingRuleAllPorts(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleNetworkTier(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	return v, nil
}

func expandComputeForwardingRuleRegion(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
	f, err := parseGlobalFieldValue("regions", v.(string), "project", d, config, true)
	if err != nil {
		return nil, fmt.Errorf("Invalid value for region: %s", err)
	}
	return f.RelativeLink(), nil
}
<|endoftext|>


<commit_before>package account

import (
	"net/url"
	"time"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/prefixer"
)

// Account holds configuration information for an account
type Account struct {
	DocID       string                 `json:"_id,omitempty"`
	DocRev      string                 `json:"_rev,omitempty"`
	Name        string                 `json:"name"`
	AccountType string                 `json:"account_type"`
	FolderPath  string                 `json:"folderPath,omitempty"`
	Basic       *BasicInfo             `json:"auth,omitempty"`
	Oauth       *OauthInfo             `json:"oauth,omitempty"`
	Extras      map[string]interface{} `json:"oauth_callback_results,omitempty"`

	// When an account is deleted, the stack cleans the triggers and calls its
	// konnector to clean the account remotely (when available). It is done via
	// a hook on deletion, but when the konnector is removed, this cleaning is
	// done manually before uninstalling the konnector, and this flag is used
	// to not try doing the cleaning in the hook as it is already too late (the
	// konnector is no longer available).
	ManualCleaning bool `json:"manual_cleaning,omitempty"`
}

// OauthInfo holds configuration information for an oauth account
type OauthInfo struct {
	AccessToken  string      `json:"access_token,omitempty"`
	TokenType    string      `json:"token_type,omitempty"`
	ExpiresAt    time.Time   `json:"expires_at,omitempty"`
	RefreshToken string      `json:"refresh_token,omitempty"`
	ClientID     string      `json:"client_id,omitempty"`
	ClientSecret string      `json:"client_secret,omitempty"`
	Query        *url.Values `json:"query,omitempty"`
}

// BasicInfo holds configuration information for an user/pass account
type BasicInfo struct {
	Login      string `json:"login,omitempty"`
	Password   string `json:"password,omitempty"`
	FolderPath string `json:"folderPath,omitempty"`
}

// ID is used to implement the couchdb.Doc interface
func (ac *Account) ID() string { return ac.DocID }

// Rev is used to implement the couchdb.Doc interface
func (ac *Account) Rev() string { return ac.DocRev }

// SetID is used to implement the couchdb.Doc interface
func (ac *Account) SetID(id string) { ac.DocID = id }

// SetRev is used to implement the couchdb.Doc interface
func (ac *Account) SetRev(rev string) { ac.DocRev = rev }

// DocType implements couchdb.Doc
func (ac *Account) DocType() string { return consts.Accounts }

// Clone implements couchdb.Doc
func (ac *Account) Clone() couchdb.Doc {
	cloned := *ac
	if ac.Oauth != nil {
		tmp := *ac.Oauth
		cloned.Oauth = &tmp
	}
	if ac.Basic != nil {
		tmp := *ac.Basic
		cloned.Basic = &tmp
	}
	cloned.Extras = make(map[string]interface{})
	for k, v := range ac.Extras {
		cloned.Extras[k] = v
	}
	return &cloned
}

// Fetch implements permission.Fetcher
func (ac *Account) Fetch(field string) []string {
	switch field {
	case "account_type":
		return []string{ac.AccountType}
	}
	return nil
}

// GetTriggers returns the list of triggers associated with the given
// accountID. In particular, the the stack will need to remove them when the
// account is deleted.
func GetTriggers(jobsSystem job.JobSystem, db prefixer.Prefixer, accountID string) ([]job.Trigger, error) {
	triggers, err := jobsSystem.GetAllTriggers(db)
	if err != nil {
		return nil, err
	}

	var toDelete []job.Trigger
	for _, t := range triggers {
		if t.Infos().WorkerType != "konnector" {
			continue
		}

		var msg struct {
			Account string `json:"account"`
		}
		err := t.Infos().Message.Unmarshal(&msg)
		if err == nil && msg.Account == accountID {
			toDelete = append(toDelete, t)
		}
	}
	return toDelete, nil
}

// CleanEntry is a struct with an account and its associated trigger.
type CleanEntry struct {
	Account          *Account
	Trigger          job.Trigger
	ManifestOnDelete bool // the manifest of the konnector has a field "on_delete_account"
	Slug             string
}

// CleanAndWait deletes the accounts. If an account is for a konnector with
// "on_delete_account", a job is pushed and it waits for the job success to
// continue. Finally, the associated trigger can be deleted.
func CleanAndWait(inst *instance.Instance, toClean []CleanEntry) error {
	jobsSystem := job.System()
	for _, entry := range toClean {
		acc := entry.Account
		acc.ManualCleaning = true
		oldRev := acc.Rev() // The deletion job needs the rev just before the deletion
		if err := couchdb.DeleteDoc(inst, acc); err != nil {
			return err
		}
		// If the konnector has a field "on_delete_account", we need to execute a job
		// for this konnector to clean the account on the remote API, and
		// wait for this job to be done before uninstalling the konnector.
		if entry.ManifestOnDelete {
			j, err := PushAccountDeletedJob(jobsSystem, inst, acc.ID(), oldRev, entry.Slug)
			if err != nil {
				return err
			}
			err = j.WaitUntilDone(inst)
			if err != nil {
				return err
			}
		}
		if entry.Trigger != nil {
			err := jobsSystem.DeleteTrigger(inst, entry.Trigger.ID())
			if err != nil {
				inst.Logger().WithField("nspace", "accounts").
					Errorf("Cannot delete the trigger: %v", err)
			}
		}
	}
	return nil
}

// PushAccountDeletedJob adds a job for the given account and konnector with
// the AccountDeleted flag, to allow the konnector to clear the account
// remotely.
func PushAccountDeletedJob(jobsSystem job.JobSystem, db prefixer.Prefixer, accountID, accountRev, konnector string) (*job.Job, error) {
	logger.WithDomain(db.DomainName()).
		WithField("account_id", accountID).
		WithField("account_rev", accountRev).
		WithField("konnector", konnector).
		Info("Pushing job for konnector on_delete")

	msg, err := job.NewMessage(struct {
		Account        string `json:"account"`
		AccountRev     string `json:"account_rev"`
		Konnector      string `json:"konnector"`
		AccountDeleted bool   `json:"account_deleted"`
	}{
		Account:        accountID,
		AccountRev:     accountRev,
		Konnector:      konnector,
		AccountDeleted: true,
	})
	if err != nil {
		return nil, err
	}
	return jobsSystem.PushJob(db, &job.JobRequest{
		WorkerType: "konnector",
		Message:    msg,
		Manual:     true, // Select high-priority for these jobs
	})
}

func init() {
	couchdb.AddHook(consts.Accounts, couchdb.EventDelete,
		func(db prefixer.Prefixer, doc couchdb.Doc, old couchdb.Doc) error {
			manualCleaning := false
			switch v := doc.(type) {
			case *Account:
				manualCleaning = v.ManualCleaning
			case *couchdb.JSONDoc:
				manualCleaning, _ = v.M["manual_cleaning"].(bool)
			}
			if manualCleaning {
				return nil
			}

			jobsSystem := job.System()
			triggers, err := GetTriggers(jobsSystem, db, doc.ID())
			if err != nil {
				logger.WithDomain(db.DomainName()).Error(
					"Failed to fetch triggers after account deletion: ", err)
				return err
			}
			for _, t := range triggers {
				if err := jobsSystem.DeleteTrigger(db, t.ID()); err != nil {
					logger.WithDomain(db.DomainName()).Errorln("failed to delete orphan trigger", err)
				}
			}

			// When an account is deleted, we need to push a new job in order to
			// delete possible data associated with this account. This is done via
			// this hook.
			//
			// This may require additionnal specifications to allow konnectors to
			// define more explicitly when and how they want to be called in order to
			// cleanup or update their associated content. For now we make this
			// process really specific to the deletion of an account, which is our
			// only detailed usecase.
			if old == nil {
				return nil
			}
			var konnector string
			switch v := old.(type) {
			case *Account:
				konnector = v.AccountType
			case *couchdb.JSONDoc:
				konnector, _ = v.M["account_type"].(string)
			}
			if konnector == "" {
				logger.WithDomain(db.DomainName()).
					WithField("account_id", old.ID()).
					WithField("account_rev", old.Rev()).
					Info("No associated konnector for account: cannot create on_delete job")
				return nil
			}
			_, err = PushAccountDeletedJob(jobsSystem, db, old.ID(), old.Rev(), konnector)
			return err
		})
}

var _ permission.Fetcher = &Account{}
<commit_msg>Parallelize account.CleanAndWait<commit_after>package account

import (
	"net/url"
	"time"

	"github.com/cozy/cozy-stack/model/instance"
	"github.com/cozy/cozy-stack/model/job"
	"github.com/cozy/cozy-stack/model/permission"
	"github.com/cozy/cozy-stack/pkg/consts"
	"github.com/cozy/cozy-stack/pkg/couchdb"
	"github.com/cozy/cozy-stack/pkg/logger"
	"github.com/cozy/cozy-stack/pkg/prefixer"
	"github.com/hashicorp/go-multierror"
)

// Account holds configuration information for an account
type Account struct {
	DocID       string                 `json:"_id,omitempty"`
	DocRev      string                 `json:"_rev,omitempty"`
	Name        string                 `json:"name"`
	AccountType string                 `json:"account_type"`
	FolderPath  string                 `json:"folderPath,omitempty"`
	Basic       *BasicInfo             `json:"auth,omitempty"`
	Oauth       *OauthInfo             `json:"oauth,omitempty"`
	Extras      map[string]interface{} `json:"oauth_callback_results,omitempty"`

	// When an account is deleted, the stack cleans the triggers and calls its
	// konnector to clean the account remotely (when available). It is done via
	// a hook on deletion, but when the konnector is removed, this cleaning is
	// done manually before uninstalling the konnector, and this flag is used
	// to not try doing the cleaning in the hook as it is already too late (the
	// konnector is no longer available).
	ManualCleaning bool `json:"manual_cleaning,omitempty"`
}

// OauthInfo holds configuration information for an oauth account
type OauthInfo struct {
	AccessToken  string      `json:"access_token,omitempty"`
	TokenType    string      `json:"token_type,omitempty"`
	ExpiresAt    time.Time   `json:"expires_at,omitempty"`
	RefreshToken string      `json:"refresh_token,omitempty"`
	ClientID     string      `json:"client_id,omitempty"`
	ClientSecret string      `json:"client_secret,omitempty"`
	Query        *url.Values `json:"query,omitempty"`
}

// BasicInfo holds configuration information for an user/pass account
type BasicInfo struct {
	Login      string `json:"login,omitempty"`
	Password   string `json:"password,omitempty"`
	FolderPath string `json:"folderPath,omitempty"`
}

// ID is used to implement the couchdb.Doc interface
func (ac *Account) ID() string { return ac.DocID }

// Rev is used to implement the couchdb.Doc interface
func (ac *Account) Rev() string { return ac.DocRev }

// SetID is used to implement the couchdb.Doc interface
func (ac *Account) SetID(id string) { ac.DocID = id }

// SetRev is used to implement the couchdb.Doc interface
func (ac *Account) SetRev(rev string) { ac.DocRev = rev }

// DocType implements couchdb.Doc
func (ac *Account) DocType() string { return consts.Accounts }

// Clone implements couchdb.Doc
func (ac *Account) Clone() couchdb.Doc {
	cloned := *ac
	if ac.Oauth != nil {
		tmp := *ac.Oauth
		cloned.Oauth = &tmp
	}
	if ac.Basic != nil {
		tmp := *ac.Basic
		cloned.Basic = &tmp
	}
	cloned.Extras = make(map[string]interface{})
	for k, v := range ac.Extras {
		cloned.Extras[k] = v
	}
	return &cloned
}

// Fetch implements permission.Fetcher
func (ac *Account) Fetch(field string) []string {
	switch field {
	case "account_type":
		return []string{ac.AccountType}
	}
	return nil
}

// GetTriggers returns the list of triggers associated with the given
// accountID. In particular, the the stack will need to remove them when the
// account is deleted.
func GetTriggers(jobsSystem job.JobSystem, db prefixer.Prefixer, accountID string) ([]job.Trigger, error) {
	triggers, err := jobsSystem.GetAllTriggers(db)
	if err != nil {
		return nil, err
	}

	var toDelete []job.Trigger
	for _, t := range triggers {
		if t.Infos().WorkerType != "konnector" {
			continue
		}

		var msg struct {
			Account string `json:"account"`
		}
		err := t.Infos().Message.Unmarshal(&msg)
		if err == nil && msg.Account == accountID {
			toDelete = append(toDelete, t)
		}
	}
	return toDelete, nil
}

// CleanEntry is a struct with an account and its associated trigger.
type CleanEntry struct {
	Account          *Account
	Trigger          job.Trigger
	ManifestOnDelete bool // the manifest of the konnector has a field "on_delete_account"
	Slug             string
}

// CleanAndWait deletes the accounts. If an account is for a konnector with
// "on_delete_account", a job is pushed and it waits for the job success to
// continue. Finally, the associated trigger can be deleted.
func CleanAndWait(inst *instance.Instance, toClean []CleanEntry) error {
	ch := make(chan error)
	for i := range toClean {
		go func(entry CleanEntry) {
			ch <- cleanAndWaitSingle(inst, entry)
		}(toClean[i])
	}
	var errm error
	for range toClean {
		if err := <-ch; err != nil {
			errm = multierror.Append(errm, err)
		}
	}
	return errm
}

func cleanAndWaitSingle(inst *instance.Instance, entry CleanEntry) error {
	jobsSystem := job.System()
	acc := entry.Account
	acc.ManualCleaning = true
	oldRev := acc.Rev() // The deletion job needs the rev just before the deletion
	if err := couchdb.DeleteDoc(inst, acc); err != nil {
		return err
	}
	// If the konnector has a field "on_delete_account", we need to execute a job
	// for this konnector to clean the account on the remote API, and
	// wait for this job to be done before uninstalling the konnector.
	if entry.ManifestOnDelete {
		j, err := PushAccountDeletedJob(jobsSystem, inst, acc.ID(), oldRev, entry.Slug)
		if err != nil {
			return err
		}
		err = j.WaitUntilDone(inst)
		if err != nil {
			return err
		}
	}
	if entry.Trigger != nil {
		err := jobsSystem.DeleteTrigger(inst, entry.Trigger.ID())
		if err != nil {
			inst.Logger().WithField("nspace", "accounts").
				Errorf("Cannot delete the trigger: %v", err)
		}
	}
	return nil
}

// PushAccountDeletedJob adds a job for the given account and konnector with
// the AccountDeleted flag, to allow the konnector to clear the account
// remotely.
func PushAccountDeletedJob(jobsSystem job.JobSystem, db prefixer.Prefixer, accountID, accountRev, konnector string) (*job.Job, error) {
	logger.WithDomain(db.DomainName()).
		WithField("account_id", accountID).
		WithField("account_rev", accountRev).
		WithField("konnector", konnector).
		Info("Pushing job for konnector on_delete")

	msg, err := job.NewMessage(struct {
		Account        string `json:"account"`
		AccountRev     string `json:"account_rev"`
		Konnector      string `json:"konnector"`
		AccountDeleted bool   `json:"account_deleted"`
	}{
		Account:        accountID,
		AccountRev:     accountRev,
		Konnector:      konnector,
		AccountDeleted: true,
	})
	if err != nil {
		return nil, err
	}
	return jobsSystem.PushJob(db, &job.JobRequest{
		WorkerType: "konnector",
		Message:    msg,
		Manual:     true, // Select high-priority for these jobs
	})
}

func init() {
	couchdb.AddHook(consts.Accounts, couchdb.EventDelete,
		func(db prefixer.Prefixer, doc couchdb.Doc, old couchdb.Doc) error {
			manualCleaning := false
			switch v := doc.(type) {
			case *Account:
				manualCleaning = v.ManualCleaning
			case *couchdb.JSONDoc:
				manualCleaning, _ = v.M["manual_cleaning"].(bool)
			}
			if manualCleaning {
				return nil
			}

			jobsSystem := job.System()
			triggers, err := GetTriggers(jobsSystem, db, doc.ID())
			if err != nil {
				logger.WithDomain(db.DomainName()).Error(
					"Failed to fetch triggers after account deletion: ", err)
				return err
			}
			for _, t := range triggers {
				if err := jobsSystem.DeleteTrigger(db, t.ID()); err != nil {
					logger.WithDomain(db.DomainName()).Errorln("failed to delete orphan trigger", err)
				}
			}

			// When an account is deleted, we need to push a new job in order to
			// delete possible data associated with this account. This is done via
			// this hook.
			//
			// This may require additionnal specifications to allow konnectors to
			// define more explicitly when and how they want to be called in order to
			// cleanup or update their associated content. For now we make this
			// process really specific to the deletion of an account, which is our
			// only detailed usecase.
			if old == nil {
				return nil
			}
			var konnector string
			switch v := old.(type) {
			case *Account:
				konnector = v.AccountType
			case *couchdb.JSONDoc:
				konnector, _ = v.M["account_type"].(string)
			}
			if konnector == "" {
				logger.WithDomain(db.DomainName()).
					WithField("account_id", old.ID()).
					WithField("account_rev", old.Rev()).
					Info("No associated konnector for account: cannot create on_delete job")
				return nil
			}
			_, err = PushAccountDeletedJob(jobsSystem, db, old.ID(), old.Rev(), konnector)
			return err
		})
}

var _ permission.Fetcher = &Account{}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/simulatedsimian/flotilla/dock"
	"github.com/tarm/serial"
)

func exitOnError(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

type LifeBoard [8]byte

func (l *LifeBoard) Set(x, y, v int) {
	x = x & 7
	y = y & 7

	if v == 0 {
		l[x] = l[x] & ^(1 << uint(y))
	} else {
		l[x] = l[x] | (1 << uint(y))
	}
}

func (l *LifeBoard) Get(x, y int) int {
	x = x & 7
	y = y & 7

	if l[x]&(1<<uint(y)) != 0 {
		return 1
	}
	return 0
}

func (l *LifeBoard) writeBoard(port int, d *dock.Dock) error {
	return d.SetModuleData(port, dock.Matrix, int(l[0]), int(l[1]), int(l[2]), int(l[3]),
		int(l[4]), int(l[5]), int(l[6]), int(l[7]), 32)
}

func (l *LifeBoard) randomPopulation(rng *rand.Rand) {
	for i := range l {
		l[i] = byte(rng.Uint32())
	}
}

func (l *LifeBoard) clear() {
	for i := range l {
		l[i] = 0
	}
}

func (l *LifeBoard) fill() {
	for i := range l {
		l[i] = 255
	}
}

func (l *LifeBoard) makeGlider() {
	l.clear()
	l.Set(3, 3, 1)
	l.Set(4, 4, 1)
	l.Set(4, 5, 1)
	l.Set(3, 5, 1)
	l.Set(2, 5, 1)
}

func (l *LifeBoard) countNeighbours(xx, yy int) int {
	cnt := 0
	for y := -1; y <= 1; y++ {
		for x := -1; x <= 1; x++ {
			if x != 0 || y != 0 {
				cnt += l.Get(x+xx, y+yy)
			}
		}
	}
	return cnt
}

func (l *LifeBoard) generation() {
	var dest LifeBoard
	for y := 0; y < 8; y++ {
		for x := 0; x < 8; x++ {
			cnt := l.countNeighbours(x, y)

			if l.Get(x, y) == 1 {
				if cnt < 2 || cnt > 3 {
					dest.Set(x, y, 0)
				} else {
					dest.Set(x, y, 1)
				}
			} else {
				if cnt == 3 {
					dest.Set(x, y, 1)
				}
			}
		}
	}
	*l = dest
}

func main() {
	var board LifeBoard

	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	board.randomPopulation(rng)

	serialcfg := serial.Config{Name: "/dev/ttyACM0", Baud: 9600}
	port, err := serial.OpenPort(&serialcfg)

	exitOnError(err)

	log.Println("connecting to dock")
	d := dock.ConnectDock(port)

	time.Sleep(200 * time.Millisecond)
	d.SendDockCommand('e')

	matrixIdx := -1

	for {
		select {
		case ev := <-d.Events:
			if ev.ModuleType == dock.Matrix {
				if ev.EventType == dock.Connected {
					matrixIdx = ev.Port
				}
				if ev.EventType == dock.Disconnected {
					matrixIdx = -1
				}
			}
			if ev.ModuleType == dock.Touch && ev.EventType == dock.Update {
				if ev.Params[0] == 1 {
					board.randomPopulation(rng)
					board.writeBoard(matrixIdx, d)
				}
				if ev.Params[1] == 1 {
					board.makeGlider()
					board.writeBoard(matrixIdx, d)
				}
			}

			fmt.Println(ev)
			exitOnError(ev.Error)

		case <-time.After(100 * time.Millisecond):
			if matrixIdx != -1 {
				err := board.writeBoard(matrixIdx, d)
				exitOnError(err)
				board.generation()
			}
		}
	}
}
<commit_msg>added generation counter<commit_after>package main

import (
	"fmt"
	"log"
	"math/rand"
	"os"
	"time"

	"github.com/simulatedsimian/flotilla/dock"
	"github.com/tarm/serial"
)

func exitOnError(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

type LifeBoard [8]byte

func (l *LifeBoard) Set(x, y, v int) {
	x = x & 7
	y = y & 7

	if v == 0 {
		l[x] = l[x] & ^(1 << uint(y))
	} else {
		l[x] = l[x] | (1 << uint(y))
	}
}

func (l *LifeBoard) Get(x, y int) int {
	x = x & 7
	y = y & 7

	if l[x]&(1<<uint(y)) != 0 {
		return 1
	}
	return 0
}

func (l *LifeBoard) writeBoard(port int, d *dock.Dock) error {
	return d.SetModuleData(port, dock.Matrix, int(l[0]), int(l[1]), int(l[2]), int(l[3]),
		int(l[4]), int(l[5]), int(l[6]), int(l[7]), 32)
}

func (l *LifeBoard) randomPopulation(rng *rand.Rand) {
	for i := range l {
		l[i] = byte(rng.Uint32())
	}
}

func (l *LifeBoard) clear() {
	for i := range l {
		l[i] = 0
	}
}

func (l *LifeBoard) fill() {
	for i := range l {
		l[i] = 255
	}
}

func (l *LifeBoard) makeGlider() {
	l.clear()
	l.Set(3, 3, 1)
	l.Set(4, 4, 1)
	l.Set(4, 5, 1)
	l.Set(3, 5, 1)
	l.Set(2, 5, 1)
}

func (l *LifeBoard) countNeighbours(xx, yy int) int {
	cnt := 0
	for y := -1; y <= 1; y++ {
		for x := -1; x <= 1; x++ {
			if x != 0 || y != 0 {
				cnt += l.Get(x+xx, y+yy)
			}
		}
	}
	return cnt
}

func (l *LifeBoard) generation() {
	var dest LifeBoard
	for y := 0; y < 8; y++ {
		for x := 0; x < 8; x++ {
			cnt := l.countNeighbours(x, y)

			if l.Get(x, y) == 1 {
				if cnt < 2 || cnt > 3 {
					dest.Set(x, y, 0)
				} else {
					dest.Set(x, y, 1)
				}
			} else {
				if cnt == 3 {
					dest.Set(x, y, 1)
				}
			}
		}
	}
	*l = dest
}

func main() {
	var board LifeBoard

	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	board.randomPopulation(rng)

	serialcfg := serial.Config{Name: "/dev/ttyACM0", Baud: 9600}
	port, err := serial.OpenPort(&serialcfg)

	exitOnError(err)

	log.Println("connecting to dock")
	d := dock.ConnectDock(port)

	time.Sleep(200 * time.Millisecond)
	d.SendDockCommand('e')

	matrixIdx := -1
	numberIdx := -1

	gen := 0

	for {
		select {
		case ev := <-d.Events:
			if ev.ModuleType == dock.Matrix {
				if ev.EventType == dock.Connected {
					matrixIdx = ev.Port
				}
				if ev.EventType == dock.Disconnected {
					matrixIdx = -1
				}
			}

			if ev.ModuleType == dock.Number {
				if ev.EventType == dock.Connected {
					numberIdx = ev.Port
				}
				if ev.EventType == dock.Disconnected {
					numberIdx = -1
				}
			}

			if ev.ModuleType == dock.Touch && ev.EventType == dock.Update {
				if ev.Params[0] == 1 {
					board.randomPopulation(rng)
					board.writeBoard(matrixIdx, d)
					gen = 0
				}
				if ev.Params[1] == 1 {
					board.makeGlider()
					board.writeBoard(matrixIdx, d)
					gen = 0
				}
			}

			fmt.Println(ev)
			exitOnError(ev.Error)

		case <-time.After(100 * time.Millisecond):
			if matrixIdx != -1 {
				if numberIdx != -1 {
					err := d.SetModuleData(numberIdx, dock.Number,
						dock.GetDigitPattern((gen/1000)%10, false),
						dock.GetDigitPattern((gen/100)%10, false),
						dock.GetDigitPattern((gen/10)%10, false),
						dock.GetDigitPattern(gen%10, false))
					exitOnError(err)
				}

				err := board.writeBoard(matrixIdx, d)
				exitOnError(err)
				board.generation()
				gen++
			}
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package bootstrap_test

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

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/bootstrap"
	"launchpad.net/juju-core/environs/storage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/testing/testbase"
	"launchpad.net/juju-core/utils"
)

type StateSuite struct {
	testbase.LoggingSuite
}

var _ = gc.Suite(&StateSuite{})

func (suite *StateSuite) newStorageWithDataDir(c *gc.C) (storage.Storage, string) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	suite.AddCleanup(func(*gc.C) { closer.Close() })
	envtesting.UploadFakeTools(c, stor)
	return stor, dataDir
}

func (suite *StateSuite) newStorage(c *gc.C) storage.Storage {
	stor, _ := suite.newStorageWithDataDir(c)
	return stor
}

// testingHTTPServer creates a tempdir backed https server with internal
// self-signed certs that will not be accepted as valid.
func (suite *StateSuite) testingHTTPSServer(c *gc.C) (string, string) {
	dataDir := c.MkDir()
	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(dataDir)))
	server := httptest.NewTLSServer(mux)
	suite.AddCleanup(func(*gc.C) { server.Close() })
	return server.URL, dataDir
}

func (suite *StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
	stor := suite.newStorage(c)

	url, err := bootstrap.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)

	reader, err := storage.Get(stor, bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadAll(reader)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, "")
	c.Assert(url, gc.NotNil)
	expectedURL, err := stor.URL(bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	c.Check(url, gc.Equals, expectedURL)
}

func (suite *StateSuite) TestDeleteStateFile(c *gc.C) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	defer closer.Close()

	err := bootstrap.DeleteStateFile(stor)
	c.Assert(err, gc.IsNil) // doesn't exist, juju don't care

	_, err = bootstrap.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)
	_, err = os.Stat(filepath.Join(dataDir, bootstrap.StateFile))
	c.Assert(err, gc.IsNil)

	err = bootstrap.DeleteStateFile(stor)
	c.Assert(err, gc.IsNil)
	_, err = os.Stat(filepath.Join(dataDir, bootstrap.StateFile))
	c.Assert(err, jc.Satisfies, os.IsNotExist)
}

func (suite *StateSuite) TestSaveStateWritesStateFile(c *gc.C) {
	stor := suite.newStorage(c)
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	marshaledState, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)

	err = bootstrap.SaveState(stor, &state)
	c.Assert(err, gc.IsNil)

	loadedState, err := storage.Get(stor, bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	content, err := ioutil.ReadAll(loadedState)
	c.Assert(err, gc.IsNil)
	c.Check(content, gc.DeepEquals, marshaledState)
}

func (suite *StateSuite) setUpSavedState(c *gc.C, dataDir string) bootstrap.BootstrapState {
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	content, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)
	err = ioutil.WriteFile(filepath.Join(dataDir, bootstrap.StateFile), []byte(content), 0644)
	c.Assert(err, gc.IsNil)
	return state
}

func (suite *StateSuite) TestLoadStateReadsStateFile(c *gc.C) {
	storage, dataDir := suite.newStorageWithDataDir(c)
	state := suite.setUpSavedState(c, dataDir)
	storedState, err := bootstrap.LoadState(storage)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateMissingFile(c *gc.C) {
	stor := suite.newStorage(c)
	_, err := bootstrap.LoadState(stor)
	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
}

func (suite *StateSuite) TestLoadStateIntegratesWithSaveState(c *gc.C) {
	storage := suite.newStorage(c)
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	err := bootstrap.SaveState(storage, &state)
	c.Assert(err, gc.IsNil)
	storedState, err := bootstrap.LoadState(storage)
	c.Assert(err, gc.IsNil)

	c.Check(*storedState, gc.DeepEquals, state)
}
<commit_msg>Fix compile error<commit_after>// Copyright 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package bootstrap_test

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

	jc "github.com/juju/testing/checkers"
	gc "launchpad.net/gocheck"
	"launchpad.net/goyaml"

	"launchpad.net/juju-core/environs"
	"launchpad.net/juju-core/environs/bootstrap"
	"launchpad.net/juju-core/environs/storage"
	envtesting "launchpad.net/juju-core/environs/testing"
	"launchpad.net/juju-core/instance"
	"launchpad.net/juju-core/testing/testbase"
)

type StateSuite struct {
	testbase.LoggingSuite
}

var _ = gc.Suite(&StateSuite{})

func (suite *StateSuite) newStorageWithDataDir(c *gc.C) (storage.Storage, string) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	suite.AddCleanup(func(*gc.C) { closer.Close() })
	envtesting.UploadFakeTools(c, stor)
	return stor, dataDir
}

func (suite *StateSuite) newStorage(c *gc.C) storage.Storage {
	stor, _ := suite.newStorageWithDataDir(c)
	return stor
}

// testingHTTPServer creates a tempdir backed https server with internal
// self-signed certs that will not be accepted as valid.
func (suite *StateSuite) testingHTTPSServer(c *gc.C) (string, string) {
	dataDir := c.MkDir()
	mux := http.NewServeMux()
	mux.Handle("/", http.FileServer(http.Dir(dataDir)))
	server := httptest.NewTLSServer(mux)
	suite.AddCleanup(func(*gc.C) { server.Close() })
	return server.URL, dataDir
}

func (suite *StateSuite) TestCreateStateFileWritesEmptyStateFile(c *gc.C) {
	stor := suite.newStorage(c)

	url, err := bootstrap.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)

	reader, err := storage.Get(stor, bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	data, err := ioutil.ReadAll(reader)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, "")
	c.Assert(url, gc.NotNil)
	expectedURL, err := stor.URL(bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	c.Check(url, gc.Equals, expectedURL)
}

func (suite *StateSuite) TestDeleteStateFile(c *gc.C) {
	closer, stor, dataDir := envtesting.CreateLocalTestStorage(c)
	defer closer.Close()

	err := bootstrap.DeleteStateFile(stor)
	c.Assert(err, gc.IsNil) // doesn't exist, juju don't care

	_, err = bootstrap.CreateStateFile(stor)
	c.Assert(err, gc.IsNil)
	_, err = os.Stat(filepath.Join(dataDir, bootstrap.StateFile))
	c.Assert(err, gc.IsNil)

	err = bootstrap.DeleteStateFile(stor)
	c.Assert(err, gc.IsNil)
	_, err = os.Stat(filepath.Join(dataDir, bootstrap.StateFile))
	c.Assert(err, jc.Satisfies, os.IsNotExist)
}

func (suite *StateSuite) TestSaveStateWritesStateFile(c *gc.C) {
	stor := suite.newStorage(c)
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	marshaledState, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)

	err = bootstrap.SaveState(stor, &state)
	c.Assert(err, gc.IsNil)

	loadedState, err := storage.Get(stor, bootstrap.StateFile)
	c.Assert(err, gc.IsNil)
	content, err := ioutil.ReadAll(loadedState)
	c.Assert(err, gc.IsNil)
	c.Check(content, gc.DeepEquals, marshaledState)
}

func (suite *StateSuite) setUpSavedState(c *gc.C, dataDir string) bootstrap.BootstrapState {
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	content, err := goyaml.Marshal(state)
	c.Assert(err, gc.IsNil)
	err = ioutil.WriteFile(filepath.Join(dataDir, bootstrap.StateFile), []byte(content), 0644)
	c.Assert(err, gc.IsNil)
	return state
}

func (suite *StateSuite) TestLoadStateReadsStateFile(c *gc.C) {
	storage, dataDir := suite.newStorageWithDataDir(c)
	state := suite.setUpSavedState(c, dataDir)
	storedState, err := bootstrap.LoadState(storage)
	c.Assert(err, gc.IsNil)
	c.Check(*storedState, gc.DeepEquals, state)
}

func (suite *StateSuite) TestLoadStateMissingFile(c *gc.C) {
	stor := suite.newStorage(c)
	_, err := bootstrap.LoadState(stor)
	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
}

func (suite *StateSuite) TestLoadStateIntegratesWithSaveState(c *gc.C) {
	storage := suite.newStorage(c)
	arch := "amd64"
	state := bootstrap.BootstrapState{
		StateInstances:  []instance.Id{instance.Id("an-instance-id")},
		Characteristics: []instance.HardwareCharacteristics{{Arch: &arch}}}
	err := bootstrap.SaveState(storage, &state)
	c.Assert(err, gc.IsNil)
	storedState, err := bootstrap.LoadState(storage)
	c.Assert(err, gc.IsNil)

	c.Check(*storedState, gc.DeepEquals, state)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014-2015 Ludovic Fauvet
// Licensed under the MIT license

package database

import (
	"errors"
	"fmt"
	. "github.com/etix/mirrorbits/config"
	"github.com/etix/mirrorbits/core"
	"github.com/garyburd/redigo/redis"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	redisConnectionTimeout = 200 * time.Millisecond
	redisReadWriteTimeout  = 300 * time.Second
	RedisMinimumVersion    = "2.8.12"
)

var (
	ErrUnreachable     = errors.New("redis endpoint unreachable")
	ErrUpgradeRequired = errors.New("unsupported Redis version")
)

type RedisPool interface {
	Get() redis.Conn
	Close() error
}

type Redis struct {
	pool         RedisPool
	Pubsub       *Pubsub
	failure      bool
	failureState sync.RWMutex
	knownMaster  string
}

func NewRedis() *Redis {
	return NewRedisCustomPool(nil)
}

func NewRedisCustomPool(pool RedisPool) *Redis {
	r := &Redis{}

	if pool != nil {
		r.pool = pool
	} else {
		r.pool = &redis.Pool{
			MaxIdle:     10,
			IdleTimeout: 240 * time.Second,
			Dial: func() (redis.Conn, error) {
				conn, err := r.Connect()

				switch err {
				case ErrUnreachable:
					r.setFailureState(true)
				case nil:
					r.setFailureState(false)
				}

				return conn, err
			},
			TestOnBorrow: func(c redis.Conn, t time.Time) error {
				_, err := c.Do("PING")
				if RedisIsLoading(err) {
					return nil
				}
				return err
			},
		}
	}

	return r
}

func (r *Redis) Get() redis.Conn {
	return r.pool.Get()
}

func (r *Redis) Close() {
	log.Debug("Closing databases connections")
	r.Pubsub.Close()
	r.pool.Close()
}

func (r *Redis) ConnectPubsub() {
	if r.Pubsub == nil {
		r.Pubsub = NewPubsub(r)
	}
}

func (r *Redis) CheckVersion() error {
	c := r.Get()
	defer c.Close()
	info, err := parseInfo(c.Do("INFO", "server"))
	if err == nil {
		if parseVersion(info["redis_version"]) < parseVersion(RedisMinimumVersion) {
			return ErrUpgradeRequired
		}
	}
	return err
}

func (r *Redis) Connect() (redis.Conn, error) {
	sentinels := GetConfig().RedisSentinels

	if len(sentinels) > 0 {

		if len(GetConfig().RedisSentinelMasterName) == 0 {
			r.logError("Config: RedisSentinelMasterName cannot be empty!")
			goto single
		}

		for _, s := range sentinels {
			log.Debug("Connecting to redis sentinel %s", s.Host)
			var master []string
			var masterhost string
			var cm redis.Conn

			c, err := r.connectTo(s.Host)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				continue
			}
			//AUTH?
			role, err := r.askRole(c)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}
			if role != "sentinel" {
				r.logError("Sentinel: %s is not a sentinel but a %s", s.Host, role)
				goto closeSentinel
			}

			master, err = redis.Strings(c.Do("SENTINEL", "get-master-addr-by-name", GetConfig().RedisSentinelMasterName))
			if err == redis.ErrNil {
				r.logError("Sentinel: %s doesn't know the master-name %s", s.Host, GetConfig().RedisSentinelMasterName)
				goto closeSentinel
			} else if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}

			masterhost = fmt.Sprintf("%s:%s", master[0], master[1])

			cm, err = r.connectTo(masterhost)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeSentinel
			}

			if r.auth(cm) != nil {
				r.logError("Redis master: auth failed")
				goto closeMaster
			}
			if err = r.selectDB(cm); err != nil {
				c.Close()
				return nil, err
			}

			role, err = r.askRole(cm)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeMaster
			}
			if role != "master" {
				r.logError("Redis master: %s is not a master but a %s", masterhost, role)
				goto closeMaster
			}

			// Close the connection to the sentinel
			c.Close()

			r.printConnectedMaster(masterhost)
			return cm, nil

		closeMaster:
			cm.Close()

		closeSentinel:
			c.Close()
		}
	}

single:

	if len(GetConfig().RedisAddress) == 0 {
		if len(sentinels) == 0 {
			log.Error("No redis master available")
		}
		return nil, ErrUnreachable
	}

	if len(sentinels) > 0 && r.getFailureState() == false {
		log.Warning("No redis master available, trying using the configured RedisAddress as fallback")
	}

	c, err := r.connectTo(GetConfig().RedisAddress)
	if err != nil {
		return nil, err
	}
	if err = r.auth(c); err != nil {
		c.Close()
		return nil, err
	}
	if err = r.selectDB(c); err != nil {
		c.Close()
		return nil, err
	}
	role, err := r.askRole(c)
	if err != nil {
		r.logError("Redis master: %s", err.Error())
		return nil, ErrUnreachable
	}
	if role != "master" {
		r.logError("Redis master: %s is not a master but a %s", GetConfig().RedisAddress, role)
		return nil, ErrUnreachable
	}
	r.printConnectedMaster(GetConfig().RedisAddress)
	return c, err

}

func (r *Redis) connectTo(address string) (redis.Conn, error) {
	return redis.DialTimeout("tcp", address, redisConnectionTimeout, redisReadWriteTimeout, redisReadWriteTimeout)
}

func (r *Redis) askRole(c redis.Conn) (string, error) {
	roleReply, err := redis.Values(c.Do("ROLE"))
	if err != nil {
		return "", err
	}
	role, err := redis.String(roleReply[0], err)
	return role, err
}

func (r *Redis) auth(c redis.Conn) (err error) {
	if GetConfig().RedisPassword != "" {
		_, err = c.Do("AUTH", GetConfig().RedisPassword)
	}
	return
}

func (r *Redis) selectDB(c redis.Conn) (err error) {
	_, err = c.Do("SELECT", GetConfig().RedisDB)
	return
}

func (r *Redis) logError(format string, args ...interface{}) {
	if r.getFailureState() == true {
		log.Debug(format, args...)
	} else {
		log.Error(format, args...)
	}
}

func (r *Redis) printConnectedMaster(address string) {
	if address != r.knownMaster && core.Daemon {
		r.knownMaster = address
		log.Info("Connected to redis master %s", address)
	} else {
		log.Debug("Connected to redis master %s", address)
	}
}

func (r *Redis) setFailureState(failure bool) {
	r.failureState.Lock()
	r.failure = failure
	r.failureState.Unlock()
}

func (r *Redis) getFailureState() bool {
	r.failureState.RLock()
	defer r.failureState.RUnlock()
	return r.failure
}

// RedisIsLoading returns true if the error is of type LOADING
func RedisIsLoading(err error) bool {
	// PARSING: "LOADING Redis is loading the dataset in memory"
	if err != nil && err.Error()[:7] == "LOADING" {
		return true
	}
	return false
}

func parseVersion(version string) int64 {
	s := strings.Split(version, ".")
	format := fmt.Sprintf("%%s%%0%ds", 2)

	var v string
	for _, value := range s {
		v = fmt.Sprintf(format, v, value)
	}

	var result int64
	var err error
	if result, err = strconv.ParseInt(v, 10, 64); err != nil {
		return -1
	}
	return result
}

func parseInfo(i interface{}, err error) (map[string]string, error) {
	v, err := redis.String(i, err)
	if err != nil {
		return nil, err
	}

	m := make(map[string]string)
	lines := strings.Split(v, "\r\n")

	for _, l := range lines {
		if strings.HasPrefix(l, "#") {
			continue
		}

		kv := strings.SplitN(l, ":", 2)
		if len(kv) < 2 {
			continue
		}

		m[kv[0]] = kv[1]
	}

	return m, nil
}
<commit_msg>redis: avoid a possible crash while parsing the LOADING state<commit_after>// Copyright (c) 2014-2015 Ludovic Fauvet
// Licensed under the MIT license

package database

import (
	"errors"
	"fmt"
	. "github.com/etix/mirrorbits/config"
	"github.com/etix/mirrorbits/core"
	"github.com/garyburd/redigo/redis"
	"strconv"
	"strings"
	"sync"
	"time"
)

const (
	redisConnectionTimeout = 200 * time.Millisecond
	redisReadWriteTimeout  = 300 * time.Second
	RedisMinimumVersion    = "2.8.12"
)

var (
	ErrUnreachable     = errors.New("redis endpoint unreachable")
	ErrUpgradeRequired = errors.New("unsupported Redis version")
)

type RedisPool interface {
	Get() redis.Conn
	Close() error
}

type Redis struct {
	pool         RedisPool
	Pubsub       *Pubsub
	failure      bool
	failureState sync.RWMutex
	knownMaster  string
}

func NewRedis() *Redis {
	return NewRedisCustomPool(nil)
}

func NewRedisCustomPool(pool RedisPool) *Redis {
	r := &Redis{}

	if pool != nil {
		r.pool = pool
	} else {
		r.pool = &redis.Pool{
			MaxIdle:     10,
			IdleTimeout: 240 * time.Second,
			Dial: func() (redis.Conn, error) {
				conn, err := r.Connect()

				switch err {
				case ErrUnreachable:
					r.setFailureState(true)
				case nil:
					r.setFailureState(false)
				}

				return conn, err
			},
			TestOnBorrow: func(c redis.Conn, t time.Time) error {
				_, err := c.Do("PING")
				if RedisIsLoading(err) {
					return nil
				}
				return err
			},
		}
	}

	return r
}

func (r *Redis) Get() redis.Conn {
	return r.pool.Get()
}

func (r *Redis) Close() {
	log.Debug("Closing databases connections")
	r.Pubsub.Close()
	r.pool.Close()
}

func (r *Redis) ConnectPubsub() {
	if r.Pubsub == nil {
		r.Pubsub = NewPubsub(r)
	}
}

func (r *Redis) CheckVersion() error {
	c := r.Get()
	defer c.Close()
	info, err := parseInfo(c.Do("INFO", "server"))
	if err == nil {
		if parseVersion(info["redis_version"]) < parseVersion(RedisMinimumVersion) {
			return ErrUpgradeRequired
		}
	}
	return err
}

func (r *Redis) Connect() (redis.Conn, error) {
	sentinels := GetConfig().RedisSentinels

	if len(sentinels) > 0 {

		if len(GetConfig().RedisSentinelMasterName) == 0 {
			r.logError("Config: RedisSentinelMasterName cannot be empty!")
			goto single
		}

		for _, s := range sentinels {
			log.Debug("Connecting to redis sentinel %s", s.Host)
			var master []string
			var masterhost string
			var cm redis.Conn

			c, err := r.connectTo(s.Host)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				continue
			}
			//AUTH?
			role, err := r.askRole(c)
			if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}
			if role != "sentinel" {
				r.logError("Sentinel: %s is not a sentinel but a %s", s.Host, role)
				goto closeSentinel
			}

			master, err = redis.Strings(c.Do("SENTINEL", "get-master-addr-by-name", GetConfig().RedisSentinelMasterName))
			if err == redis.ErrNil {
				r.logError("Sentinel: %s doesn't know the master-name %s", s.Host, GetConfig().RedisSentinelMasterName)
				goto closeSentinel
			} else if err != nil {
				r.logError("Sentinel: %s", err.Error())
				goto closeSentinel
			}

			masterhost = fmt.Sprintf("%s:%s", master[0], master[1])

			cm, err = r.connectTo(masterhost)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeSentinel
			}

			if r.auth(cm) != nil {
				r.logError("Redis master: auth failed")
				goto closeMaster
			}
			if err = r.selectDB(cm); err != nil {
				c.Close()
				return nil, err
			}

			role, err = r.askRole(cm)
			if err != nil {
				r.logError("Redis master: %s", err.Error())
				goto closeMaster
			}
			if role != "master" {
				r.logError("Redis master: %s is not a master but a %s", masterhost, role)
				goto closeMaster
			}

			// Close the connection to the sentinel
			c.Close()

			r.printConnectedMaster(masterhost)
			return cm, nil

		closeMaster:
			cm.Close()

		closeSentinel:
			c.Close()
		}
	}

single:

	if len(GetConfig().RedisAddress) == 0 {
		if len(sentinels) == 0 {
			log.Error("No redis master available")
		}
		return nil, ErrUnreachable
	}

	if len(sentinels) > 0 && r.getFailureState() == false {
		log.Warning("No redis master available, trying using the configured RedisAddress as fallback")
	}

	c, err := r.connectTo(GetConfig().RedisAddress)
	if err != nil {
		return nil, err
	}
	if err = r.auth(c); err != nil {
		c.Close()
		return nil, err
	}
	if err = r.selectDB(c); err != nil {
		c.Close()
		return nil, err
	}
	role, err := r.askRole(c)
	if err != nil {
		r.logError("Redis master: %s", err.Error())
		return nil, ErrUnreachable
	}
	if role != "master" {
		r.logError("Redis master: %s is not a master but a %s", GetConfig().RedisAddress, role)
		return nil, ErrUnreachable
	}
	r.printConnectedMaster(GetConfig().RedisAddress)
	return c, err

}

func (r *Redis) connectTo(address string) (redis.Conn, error) {
	return redis.DialTimeout("tcp", address, redisConnectionTimeout, redisReadWriteTimeout, redisReadWriteTimeout)
}

func (r *Redis) askRole(c redis.Conn) (string, error) {
	roleReply, err := redis.Values(c.Do("ROLE"))
	if err != nil {
		return "", err
	}
	role, err := redis.String(roleReply[0], err)
	return role, err
}

func (r *Redis) auth(c redis.Conn) (err error) {
	if GetConfig().RedisPassword != "" {
		_, err = c.Do("AUTH", GetConfig().RedisPassword)
	}
	return
}

func (r *Redis) selectDB(c redis.Conn) (err error) {
	_, err = c.Do("SELECT", GetConfig().RedisDB)
	return
}

func (r *Redis) logError(format string, args ...interface{}) {
	if r.getFailureState() == true {
		log.Debug(format, args...)
	} else {
		log.Error(format, args...)
	}
}

func (r *Redis) printConnectedMaster(address string) {
	if address != r.knownMaster && core.Daemon {
		r.knownMaster = address
		log.Info("Connected to redis master %s", address)
	} else {
		log.Debug("Connected to redis master %s", address)
	}
}

func (r *Redis) setFailureState(failure bool) {
	r.failureState.Lock()
	r.failure = failure
	r.failureState.Unlock()
}

func (r *Redis) getFailureState() bool {
	r.failureState.RLock()
	defer r.failureState.RUnlock()
	return r.failure
}

// RedisIsLoading returns true if the error is of type LOADING
func RedisIsLoading(err error) bool {
	// PARSING: "LOADING Redis is loading the dataset in memory"
	if err != nil && strings.HasPrefix(err.Error(), "LOADING") {
		return true
	}
	return false
}

func parseVersion(version string) int64 {
	s := strings.Split(version, ".")
	format := fmt.Sprintf("%%s%%0%ds", 2)

	var v string
	for _, value := range s {
		v = fmt.Sprintf(format, v, value)
	}

	var result int64
	var err error
	if result, err = strconv.ParseInt(v, 10, 64); err != nil {
		return -1
	}
	return result
}

func parseInfo(i interface{}, err error) (map[string]string, error) {
	v, err := redis.String(i, err)
	if err != nil {
		return nil, err
	}

	m := make(map[string]string)
	lines := strings.Split(v, "\r\n")

	for _, l := range lines {
		if strings.HasPrefix(l, "#") {
			continue
		}

		kv := strings.SplitN(l, ":", 2)
		if len(kv) < 2 {
			continue
		}

		m[kv[0]] = kv[1]
	}

	return m, nil
}
<|endoftext|>


<commit_before>package consensus

import (
	"bufio"
	"os"
	"time"

	. "github.com/tendermint/go-common"
	"github.com/tendermint/go-wire"
	"github.com/tendermint/tendermint/types"
)

//--------------------------------------------------------
// types and functions for savings consensus messages

type ConsensusLogMessage struct {
	Time time.Time                    `json:"time"`
	Msg  ConsensusLogMessageInterface `json:"msg"`
}

type ConsensusLogMessageInterface interface{}

var _ = wire.RegisterInterface(
	struct{ ConsensusLogMessageInterface }{},
	wire.ConcreteType{types.EventDataRoundState{}, 0x01},
	wire.ConcreteType{msgInfo{}, 0x02},
	wire.ConcreteType{timeoutInfo{}, 0x03},
)

//--------------------------------------------------------
// Simple write-ahead logger

// Write ahead logger writes msgs to disk before they are processed.
// Can be used for crash-recovery and deterministic replay
// TODO: currently the wal is overwritten during replay catchup
//   give it a mode so it's either reading or appending - must read to end to start appending again
type WAL struct {
	fp     *os.File
	exists bool // if the file already existed (restarted process)

	done chan struct{}

	light bool // ignore block parts
}

func NewWAL(file string, light bool) (*WAL, error) {
	var walExists bool
	if _, err := os.Stat(file); err == nil {
		walExists = true
	}
	fp, err := os.OpenFile(file, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
	if err != nil {
		return nil, err
	}
	return &WAL{
		fp:     fp,
		exists: walExists,
		done:   make(chan struct{}),
		light:  light,
	}, nil
}

// called in newStep and for each pass in receiveRoutine
func (wal *WAL) Save(msg ConsensusLogMessageInterface) {
	if wal != nil {
		if wal.light {
			if m, ok := msg.(msgInfo); ok {
				if _, ok := m.Msg.(*BlockPartMessage); ok {
					return
				}
			}
		}
		var n int
		var err error
		wire.WriteJSON(ConsensusLogMessage{time.Now(), msg}, wal.fp, &n, &err)
		wire.WriteTo([]byte("\n"), wal.fp, &n, &err) // one message per line
		if err != nil {
			PanicQ(Fmt("Error writing msg to consensus wal. Error: %v \n\nMessage: %v", err, msg))
		}
	}
}

// Must not be called concurrently with a write.
func (wal *WAL) Close() {
	if wal != nil {
		wal.fp.Close()
	}
	wal.done <- struct{}{}
}

func (wal *WAL) Wait() {
	<-wal.done
}

func (wal *WAL) SeekFromEnd(found func([]byte) bool) (nLines int, err error) {
	var current int64
	// start at the end
	current, err = wal.fp.Seek(0, 2)
	if err != nil {
		return
	}

	// backup until we find the the right line
	// current is how far we are from the beginning
	for {
		current -= 1
		if current < 0 {
			wal.fp.Seek(0, 0) // back to beginning
			return
		}
		// backup one and read a new byte
		if _, err = wal.fp.Seek(current, 0); err != nil {
			return
		}
		b := make([]byte, 1)
		if _, err = wal.fp.Read(b); err != nil {
			return
		}
		if b[0] == '\n' || len(b) == 0 {
			nLines += 1
			// read a full line
			reader := bufio.NewReader(wal.fp)
			lineBytes, _ := reader.ReadBytes('\n')
			if len(lineBytes) == 0 {
				continue
			}

			if found(lineBytes) {
				wal.fp.Seek(0, 1) // (?)
				wal.fp.Seek(current, 0)
				return
			}
		}
	}
}
<commit_msg>consensus: cswal doesnt write any consensus msgs in light mode<commit_after>package consensus

import (
	"bufio"
	"os"
	"time"

	. "github.com/tendermint/go-common"
	"github.com/tendermint/go-wire"
	"github.com/tendermint/tendermint/types"
)

//--------------------------------------------------------
// types and functions for savings consensus messages

type ConsensusLogMessage struct {
	Time time.Time                    `json:"time"`
	Msg  ConsensusLogMessageInterface `json:"msg"`
}

type ConsensusLogMessageInterface interface{}

var _ = wire.RegisterInterface(
	struct{ ConsensusLogMessageInterface }{},
	wire.ConcreteType{types.EventDataRoundState{}, 0x01},
	wire.ConcreteType{msgInfo{}, 0x02},
	wire.ConcreteType{timeoutInfo{}, 0x03},
)

//--------------------------------------------------------
// Simple write-ahead logger

// Write ahead logger writes msgs to disk before they are processed.
// Can be used for crash-recovery and deterministic replay
// TODO: currently the wal is overwritten during replay catchup
//   give it a mode so it's either reading or appending - must read to end to start appending again
type WAL struct {
	fp     *os.File
	exists bool // if the file already existed (restarted process)

	done chan struct{}

	light bool // ignore block parts
}

func NewWAL(file string, light bool) (*WAL, error) {
	var walExists bool
	if _, err := os.Stat(file); err == nil {
		walExists = true
	}
	fp, err := os.OpenFile(file, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600)
	if err != nil {
		return nil, err
	}
	return &WAL{
		fp:     fp,
		exists: walExists,
		done:   make(chan struct{}),
		light:  light,
	}, nil
}

// called in newStep and for each pass in receiveRoutine
func (wal *WAL) Save(clm ConsensusLogMessageInterface) {
	if wal != nil {
		if wal.light {
			// in light mode we only write new steps and timeouts (no votes, proposals, block parts)
			if _, ok := clm.(msgInfo); ok {
				return
			}
		}
		var n int
		var err error
		wire.WriteJSON(ConsensusLogMessage{time.Now(), clm}, wal.fp, &n, &err)
		wire.WriteTo([]byte("\n"), wal.fp, &n, &err) // one message per line
		if err != nil {
			PanicQ(Fmt("Error writing msg to consensus wal. Error: %v \n\nMessage: %v", err, clm))
		}
	}
}

// Must not be called concurrently with a write.
func (wal *WAL) Close() {
	if wal != nil {
		wal.fp.Close()
	}
	wal.done <- struct{}{}
}

func (wal *WAL) Wait() {
	<-wal.done
}

func (wal *WAL) SeekFromEnd(found func([]byte) bool) (nLines int, err error) {
	var current int64
	// start at the end
	current, err = wal.fp.Seek(0, 2)
	if err != nil {
		return
	}

	// backup until we find the the right line
	// current is how far we are from the beginning
	for {
		current -= 1
		if current < 0 {
			wal.fp.Seek(0, 0) // back to beginning
			return
		}
		// backup one and read a new byte
		if _, err = wal.fp.Seek(current, 0); err != nil {
			return
		}
		b := make([]byte, 1)
		if _, err = wal.fp.Read(b); err != nil {
			return
		}
		if b[0] == '\n' || len(b) == 0 {
			nLines += 1
			// read a full line
			reader := bufio.NewReader(wal.fp)
			lineBytes, _ := reader.ReadBytes('\n')
			if len(lineBytes) == 0 {
				continue
			}

			if found(lineBytes) {
				wal.fp.Seek(0, 1) // (?)
				wal.fp.Seek(current, 0)
				return
			}
		}
	}
}
<|endoftext|>


<commit_before>package gocast

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/gogo/protobuf/proto"
	"github.com/stampzilla/gocast/api"
	"github.com/stampzilla/gocast/events"
	"github.com/stampzilla/gocast/responses"
)

func (d *Device) reader() {
	for {
		packet, err := d.wrapper.Read()

		if err != nil {
			log.Println("Error reading from chromecast error:", err, "Packet:", packet)
			d.Disconnect()
			//d.reconnect <- struct{}{}
			return
		}

		message := &api.CastMessage{}
		err = proto.Unmarshal(*packet, message)
		if err != nil {
			log.Fatalf("Failed to unmarshal CastMessage: %s", err)
			continue
		}

		//spew.Dump("Message!", message)

		headers := &responses.Headers{}

		err = json.Unmarshal([]byte(*message.PayloadUtf8), headers)

		if err != nil {
			log.Fatalf("Failed to unmarshal message: %s", err)
			continue
		}

		catched := false
		d.RLock()
		for _, subscription := range d.subscriptions {
			if subscription.Receive(message, headers) {
				catched = true
			}
		}
		d.RUnlock()

		if !catched {
			log.Println("LOST MESSAGE:")
			spew.Dump(message)
		}
	}
}

func (d *Device) Connected() bool {
	d.RLock()
	defer d.RUnlock()
	return d.connected
}
func (d *Device) Connect() error {
	go d.reconnector()
	return d.connect()
}
func (d *Device) Reconnect() {
	select {
	case d.reconnect <- struct{}{}:
	default:
	}
}
func (d *Device) reconnector() {
	for {
		select {
		case <-d.reconnect:
			log.Println("Reconnect signal received")
			time.Sleep(time.Second * 2)
			d.connect()
		}
	}
}
func (d *Device) connect() error {
	log.Printf("connecting to %s:%d ...", d.ip, d.port)

	if d.conn != nil {
		return fmt.Errorf("Already connected to: %s (%s:%d)", d.Name(), d.Ip().String(), d.Port())
	}

	var err error
	d.conn, err = tls.Dial("tcp", fmt.Sprintf("%s:%d", d.ip, d.port), &tls.Config{
		InsecureSkipVerify: true,
	})

	if err != nil {
		//d.reconnect <- struct{}{}
		return fmt.Errorf("Failed to connect to Chromecast. Error:%s", err)
	}

	d.Lock()
	d.connected = true
	d.Unlock()

	event := events.Connected{}
	d.Dispatch(event)

	d.wrapper = NewPacketStream(d.conn)
	go d.reader()

	d.Subscribe("urn:x-cast:com.google.cast.tp.connection", "receiver-0", d.connectionHandler)
	d.Subscribe("urn:x-cast:com.google.cast.tp.heartbeat", "receiver-0", d.heartbeatHandler)
	d.Subscribe("urn:x-cast:com.google.cast.receiver", "receiver-0", d.ReceiverHandler)

	return nil
}

func (d *Device) Disconnect() {
	if d.conn != nil {
		d.RLock()
		for _, subscription := range d.subscriptions {
			subscription.Handler.Disconnect()
		}
		d.RUnlock()

		d.Lock()
		d.subscriptions = make(map[string]*Subscription, 0)
		d.Dispatch(events.Disconnected{})

		d.conn.Close()
		d.conn = nil
		d.Unlock()
	}

	d.Lock()
	d.connected = false
	d.Unlock()
}

func (d *Device) Send(urn, sourceId, destinationId string, payload responses.Payload) error {
	payloadJson, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("Failed to json.Marshal: ", err)
		return err
	}
	payloadString := string(payloadJson)

	message := &api.CastMessage{
		ProtocolVersion: api.CastMessage_CASTV2_1_0.Enum(),
		SourceId:        &sourceId,
		DestinationId:   &destinationId,
		Namespace:       &urn,
		PayloadType:     api.CastMessage_STRING.Enum(),
		PayloadUtf8:     &payloadString,
	}

	proto.SetDefaults(message)

	data, err := proto.Marshal(message)
	if err != nil {
		fmt.Println("Failed to proto.Marshal: ", err)
		return err
	}

	if *message.Namespace != "urn:x-cast:com.google.cast.tp.heartbeat" {
		log.Println("Writing:", spew.Sdump(message))
	}

	if d.conn == nil {
		return fmt.Errorf("We are disconnected, cannot send!")
	}

	_, err = d.wrapper.Write(data)

	return err
}
<commit_msg>Changed interlocks in Disconnect method<commit_after>package gocast

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"log"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/gogo/protobuf/proto"
	"github.com/stampzilla/gocast/api"
	"github.com/stampzilla/gocast/events"
	"github.com/stampzilla/gocast/responses"
)

func (d *Device) reader() {
	for {
		packet, err := d.wrapper.Read()

		if err != nil {
			log.Println("Error reading from chromecast error:", err, "Packet:", packet)
			d.Disconnect()
			//d.reconnect <- struct{}{}
			return
		}

		message := &api.CastMessage{}
		err = proto.Unmarshal(*packet, message)
		if err != nil {
			log.Fatalf("Failed to unmarshal CastMessage: %s", err)
			continue
		}

		//spew.Dump("Message!", message)

		headers := &responses.Headers{}

		err = json.Unmarshal([]byte(*message.PayloadUtf8), headers)

		if err != nil {
			log.Fatalf("Failed to unmarshal message: %s", err)
			continue
		}

		catched := false
		d.RLock()
		for _, subscription := range d.subscriptions {
			if subscription.Receive(message, headers) {
				catched = true
			}
		}
		d.RUnlock()

		if !catched {
			log.Println("LOST MESSAGE:")
			spew.Dump(message)
		}
	}
}

func (d *Device) Connected() bool {
	d.RLock()
	defer d.RUnlock()
	return d.connected
}
func (d *Device) Connect() error {
	go d.reconnector()
	return d.connect()
}
func (d *Device) Reconnect() {
	select {
	case d.reconnect <- struct{}{}:
	default:
	}
}
func (d *Device) reconnector() {
	for {
		select {
		case <-d.reconnect:
			log.Println("Reconnect signal received")
			time.Sleep(time.Second * 2)
			d.connect()
		}
	}
}
func (d *Device) connect() error {
	log.Printf("connecting to %s:%d ...", d.ip, d.port)

	if d.conn != nil {
		return fmt.Errorf("Already connected to: %s (%s:%d)", d.Name(), d.Ip().String(), d.Port())
	}

	var err error
	d.conn, err = tls.Dial("tcp", fmt.Sprintf("%s:%d", d.ip, d.port), &tls.Config{
		InsecureSkipVerify: true,
	})

	if err != nil {
		//d.reconnect <- struct{}{}
		return fmt.Errorf("Failed to connect to Chromecast. Error:%s", err)
	}

	d.Lock()
	d.connected = true
	d.Unlock()

	event := events.Connected{}
	d.Dispatch(event)

	d.wrapper = NewPacketStream(d.conn)
	go d.reader()

	d.Subscribe("urn:x-cast:com.google.cast.tp.connection", "receiver-0", d.connectionHandler)
	d.Subscribe("urn:x-cast:com.google.cast.tp.heartbeat", "receiver-0", d.heartbeatHandler)
	d.Subscribe("urn:x-cast:com.google.cast.receiver", "receiver-0", d.ReceiverHandler)

	return nil
}

func (d *Device) Disconnect() {
	d.Lock()
	if d.conn != nil {
		for _, subscription := range d.subscriptions {
			subscription.Handler.Disconnect()
		}

		d.subscriptions = make(map[string]*Subscription, 0)
		d.Dispatch(events.Disconnected{})

		d.conn.Close()
		d.conn = nil
	}

	d.connected = false
	d.Unlock()
}

func (d *Device) Send(urn, sourceId, destinationId string, payload responses.Payload) error {
	payloadJson, err := json.Marshal(payload)
	if err != nil {
		fmt.Println("Failed to json.Marshal: ", err)
		return err
	}
	payloadString := string(payloadJson)

	message := &api.CastMessage{
		ProtocolVersion: api.CastMessage_CASTV2_1_0.Enum(),
		SourceId:        &sourceId,
		DestinationId:   &destinationId,
		Namespace:       &urn,
		PayloadType:     api.CastMessage_STRING.Enum(),
		PayloadUtf8:     &payloadString,
	}

	proto.SetDefaults(message)

	data, err := proto.Marshal(message)
	if err != nil {
		fmt.Println("Failed to proto.Marshal: ", err)
		return err
	}

	if *message.Namespace != "urn:x-cast:com.google.cast.tp.heartbeat" {
		log.Println("Writing:", spew.Sdump(message))
	}

	if d.conn == nil {
		return fmt.Errorf("We are disconnected, cannot send!")
	}

	_, err = d.wrapper.Write(data)

	return err
}
<|endoftext|>