<commit_before>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ipv4net

import (
	"fmt"
	"net"
	"strconv"

	"github.com/apparentlymart/go-cidr/cidr"

	"github.com/ligato/vpp-agent/plugins/linuxv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/linuxv2/model/l3"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/l3"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/stn"
)

/* VPP - Host interconnect */
const (
	/* AF-PACKET + VETH */

	// logical & host names of the VETH interface connecting host stack with VPP.
	//  - the host stack side of the pipe
	hostInterconnectVETH1LogicalName = "veth-vpp1"
	hostInterconnectVETH1HostName    = "vpp1"

	// logical & host names of the VETH interface connecting host stack with VPP.
	//  - the VPP side of the pipe
	hostInterconnectVETH2LogicalName = "veth-vpp2"
	hostInterconnectVETH2HostName    = "vpp2"

	// logical name of the AF-packet interface attached to VETH2.
	hostInterconnectAFPacketLogicalName = "afpacket-vpp2"

	/* TAP */

	// HostInterconnectTAPinVPPLogicalName is the logical name of the TAP interface
	// connecting host stack with VPP
	//  - VPP side
	HostInterconnectTAPinVPPLogicalName = "tap-vpp2"

	// HostInterconnectTAPinLinuxLogicalName is the logical name of the TAP interface
	// connecting host stack with VPP
	//  - Linux side
	HostInterconnectTAPinLinuxLogicalName = "tap-vpp1"

	// HostInterconnectTAPinLinuxHostName is the physical name of the TAP interface
	// connecting host stack with VPP
	//  - the Linux side
	HostInterconnectTAPinLinuxHostName = "vpp1"
)

// prefix for the hardware address of host interconnects
var hostInterconnectHwAddrPrefix = []byte{0x34, 0x3c}

/************************** VPP <-> Host connectivity **************************/

// hostInterconnectVPPIfName returns the logical name of the VPP-host interconnect
// interface on the VPP side.
func (n *IPv4Net) hostInterconnectVPPIfName() string {
	if n.config.UseTAPInterfaces {
		return HostInterconnectTAPinVPPLogicalName
	}
	return hostInterconnectAFPacketLogicalName
}

// hostInterconnectLinuxIfName returns the logical name of the VPP-host interconnect
// interface on the Linux side.
func (n *IPv4Net) hostInterconnectLinuxIfName() string {
	if n.config.UseTAPInterfaces {
		return HostInterconnectTAPinLinuxLogicalName
	}
	return hostInterconnectVETH1LogicalName
}

// interconnectTapVPP returns configuration for the VPP-side of the TAP interface
// connecting VPP with the host stack.
func (n *IPv4Net) interconnectTapVPP() (key string, config *interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	tap := &interfaces.Interface{
		Name:    HostInterconnectTAPinVPPLogicalName,
		Type:    interfaces.Interface_TAP,
		Mtu:     n.config.MTUSize,
		Enabled: true,
		Vrf:     n.GetMainVrfID(),
		Link: &interfaces.Interface_Tap{
			Tap: &interfaces.TapLink{},
		},
		PhysAddress: hwAddrForNodeInterface(n.NodeSync.GetNodeID(), hostInterconnectHwAddrPrefix),
	}
	if n.InSTNMode() {
		tap.Unnumbered = &interfaces.Interface_Unnumbered{
			InterfaceWithIp: n.mainPhysicalIf,
		}
	} else {
		tap.IpAddresses = []string{n.ipam.HostInterconnectIPInVPP().String() + "/" + strconv.Itoa(size)}

	}
	if n.config.TAPInterfaceVersion == 2 {
		tap.GetTap().Version = 2
		tap.GetTap().RxRingSize = uint32(n.config.TAPv2RxRingSize)
		tap.GetTap().TxRingSize = uint32(n.config.TAPv2TxRingSize)
	}
	key = interfaces.InterfaceKey(tap.Name)
	return key, tap
}

// interconnectTapHost returns configuration for the Host-side of the TAP interface
// connecting VPP with the host stack.
func (n *IPv4Net) interconnectTapHost() (key string, config *linux_interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	tap := &linux_interfaces.Interface{
		Name: HostInterconnectTAPinLinuxLogicalName,
		Type: linux_interfaces.Interface_TAP_TO_VPP,
		Link: &linux_interfaces.Interface_Tap{
			Tap: &linux_interfaces.TapLink{
				VppTapIfName: HostInterconnectTAPinVPPLogicalName,
			},
		},
		Mtu:        n.config.MTUSize,
		HostIfName: HostInterconnectTAPinLinuxHostName,
		Enabled:    true,
	}
	if n.InSTNMode() {
		if len(n.nodeIP) > 0 {
			tap.IpAddresses = []string{combineAddrWithNet(n.nodeIP, n.nodeIPNet).String()}
		}
	} else {
		tap.IpAddresses = []string{n.ipam.HostInterconnectIPInLinux().String() + "/" + strconv.Itoa(size)}
	}
	key = linux_interfaces.InterfaceKey(tap.Name)
	return key, tap
}

// interconnectAfpacket returns configuration for the AF-Packet interface attached
// to interconnectVethVpp (see below)
func (n *IPv4Net) interconnectAfpacket() (key string, config *interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	afpacket := &interfaces.Interface{
		Name: hostInterconnectAFPacketLogicalName,
		Type: interfaces.Interface_AF_PACKET,
		Link: &interfaces.Interface_Afpacket{
			Afpacket: &interfaces.AfpacketLink{
				HostIfName: hostInterconnectVETH2HostName,
			},
		},
		Mtu:         n.config.MTUSize,
		Enabled:     true,
		Vrf:         n.GetMainVrfID(),
		IpAddresses: []string{n.ipam.HostInterconnectIPInVPP().String() + "/" + strconv.Itoa(size)},
		PhysAddress: hwAddrForNodeInterface(n.NodeSync.GetNodeID(), hostInterconnectHwAddrPrefix),
	}
	key = interfaces.InterfaceKey(afpacket.Name)
	return key, afpacket
}

// interconnectVethVpp returns configuration for VPP-side of the VETH pipe connecting
// vswitch with the host stack.
func (n *IPv4Net) interconnectVethVpp() (key string, config *linux_interfaces.Interface) {
	veth := &linux_interfaces.Interface{
		Name: hostInterconnectVETH2LogicalName,
		Type: linux_interfaces.Interface_VETH,
		Link: &linux_interfaces.Interface_Veth{
			Veth: &linux_interfaces.VethLink{PeerIfName: hostInterconnectVETH1LogicalName},
		},
		Mtu:        n.config.MTUSize,
		Enabled:    true,
		HostIfName: hostInterconnectVETH2HostName,
	}
	key = linux_interfaces.InterfaceKey(veth.Name)
	return key, veth
}

// interconnectVethHost returns configuration for host-side of the VETH pipe connecting
// vswitch with the host stack.
func (n *IPv4Net) interconnectVethHost() (key string, config *linux_interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	veth := &linux_interfaces.Interface{
		Name: hostInterconnectVETH1LogicalName,
		Type: linux_interfaces.Interface_VETH,
		Link: &linux_interfaces.Interface_Veth{
			Veth: &linux_interfaces.VethLink{PeerIfName: hostInterconnectVETH2LogicalName},
		},
		Mtu:         n.config.MTUSize,
		Enabled:     true,
		HostIfName:  hostInterconnectVETH1HostName,
		IpAddresses: []string{n.ipam.HostInterconnectIPInLinux().String() + "/" + strconv.Itoa(size)},
	}
	if n.config.TCPChecksumOffloadDisabled {
		veth.GetVeth().RxChecksumOffloading = linux_interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
		veth.GetVeth().TxChecksumOffloading = linux_interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
	}
	key = linux_interfaces.InterfaceKey(veth.Name)
	return key, veth
}

// routesToHost return one route to configure on VPP for every host interface.
func (n *IPv4Net) routesToHost(nextHopIP net.IP) map[string]*l3.StaticRoute {
	routes := make(map[string]*l3.StaticRoute)

	// generate a /32 static route from VPP for each of the host's IPs
	for _, ip := range n.hostIPs {
		route := &l3.StaticRoute{
			DstNetwork:        fmt.Sprintf("%s/32", ip.String()),
			NextHopAddr:       nextHopIP.String(),
			OutgoingInterface: n.hostInterconnectVPPIfName(),
			VrfId:             n.GetMainVrfID(),
		}
		key := l3.RouteKey(route.VrfId, route.DstNetwork, route.NextHopAddr)
		routes[key] = route
	}

	return routes
}

// routePODsFromHost returns configuration for route for the host stack to direct
// traffic destined to pods via VPP.
func (n *IPv4Net) routePODsFromHost(nextHopIP net.IP) (key string, config *linux_l3.StaticRoute) {
	route := &linux_l3.StaticRoute{
		OutgoingInterface: hostInterconnectVETH1LogicalName,
		Scope:             linux_l3.StaticRoute_GLOBAL,
		DstNetwork:        n.ipam.PodSubnetAllNodes().String(),
		GwAddr:            nextHopIP.String(),
	}
	if n.config.UseTAPInterfaces {
		route.OutgoingInterface = HostInterconnectTAPinLinuxLogicalName
	}
	key = linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
	return key, route
}

// routeServicesFromHost returns configuration for route for the host stack to direct
// traffic destined to services via VPP.
func (n *IPv4Net) routeServicesFromHost(nextHopIP net.IP) (key string, config *linux_l3.StaticRoute) {
	route := &linux_l3.StaticRoute{
		OutgoingInterface: hostInterconnectVETH1LogicalName,
		Scope:             linux_l3.StaticRoute_GLOBAL,
		DstNetwork:        n.ipam.ServiceNetwork().String(),
		GwAddr:            nextHopIP.String(),
	}
	if n.config.UseTAPInterfaces {
		route.OutgoingInterface = HostInterconnectTAPinLinuxLogicalName
	}
	key = linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
	return key, route
}

/************************************ STN *************************************/

// stnRule returns configuration for STN rule, used to forward all traffic not matched
// in VPP to host via interconnect interface.
// The method assumes that node has IP address allocated!
func (n *IPv4Net) stnRule() (key string, config *stn.Rule) {
	rule := &stn.Rule{
		IpAddress: n.nodeIP.String(),
		Interface: n.hostInterconnectVPPIfName(),
	}
	key = stn.Key(rule.Interface, rule.IpAddress)
	return key, rule
}

// proxyArpForSTNGateway configures proxy ARP used in the STN case to let VPP to answer
// to ARP requests coming from the host stack.
func (n *IPv4Net) proxyArpForSTNGateway() (key string, config *l3.ProxyARP) {
	firstIP, lastIP := cidr.AddressRange(n.nodeIPNet)

	// If larger than a /31, remove network and broadcast addresses
	// from address range.
	if cidr.AddressCount(n.nodeIPNet) > 2 {
		firstIP = cidr.Inc(firstIP)
		lastIP = cidr.Dec(lastIP)
	}

	proxyarp := &l3.ProxyARP{
		Interfaces: []*l3.ProxyARP_Interface{
			{Name: n.hostInterconnectVPPIfName()},
		},
		Ranges: []*l3.ProxyARP_Range{
			{
				FirstIpAddr: firstIP.String(),
				LastIpAddr:  lastIP.String(),
			},
		},
	}
	key = l3.ProxyARPKey
	return key, proxyarp
}

// stnRoutesForVPP returns VPP routes mirroring Host routes that were associated
// with the stolen interface.
func (n *IPv4Net) stnRoutesForVPP() map[string]*l3.StaticRoute {
	routes := make(map[string]*l3.StaticRoute)

	for _, stnRoute := range n.stnRoutes {
		route := &l3.StaticRoute{
			DstNetwork:        stnRoute.DestinationSubnet,
			NextHopAddr:       stnRoute.NextHopIp,
			OutgoingInterface: n.mainPhysicalIf,
			VrfId:             n.GetMainVrfID(),
		}
		key := l3.RouteKey(route.VrfId, route.DstNetwork, route.NextHopAddr)
		routes[key] = route
	}

	return routes
}

// stnRoutesForHost returns configuration of routes that were associated
// with the stolen interface, now updated to route via host-interconnect.
func (n *IPv4Net) stnRoutesForHost() map[string]*linux_l3.StaticRoute {
	routes := make(map[string]*linux_l3.StaticRoute)

	for _, stnRoute := range n.stnRoutes {
		if stnRoute.NextHopIp == "" {
			continue // skip routes with no next hop IP (link-local)
		}
		route := &linux_l3.StaticRoute{
			DstNetwork:        stnRoute.DestinationSubnet,
			GwAddr:            stnRoute.NextHopIp,
			Scope:             linux_l3.StaticRoute_GLOBAL,
			OutgoingInterface: n.hostInterconnectLinuxIfName(),
		}
		if route.DstNetwork == "" {
			route.DstNetwork = ipv4NetAny
		}
		key := linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
		routes[key] = route
	}

	return routes
}
<commit_msg>Fix VPP routes in STN<commit_after>// Copyright (c) 2017 Cisco and/or its affiliates.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ipv4net

import (
	"fmt"
	"net"
	"strconv"

	"github.com/apparentlymart/go-cidr/cidr"

	"github.com/ligato/vpp-agent/plugins/linuxv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/linuxv2/model/l3"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/interfaces"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/l3"
	"github.com/ligato/vpp-agent/plugins/vppv2/model/stn"
)

/* VPP - Host interconnect */
const (
	/* AF-PACKET + VETH */

	// logical & host names of the VETH interface connecting host stack with VPP.
	//  - the host stack side of the pipe
	hostInterconnectVETH1LogicalName = "veth-vpp1"
	hostInterconnectVETH1HostName    = "vpp1"

	// logical & host names of the VETH interface connecting host stack with VPP.
	//  - the VPP side of the pipe
	hostInterconnectVETH2LogicalName = "veth-vpp2"
	hostInterconnectVETH2HostName    = "vpp2"

	// logical name of the AF-packet interface attached to VETH2.
	hostInterconnectAFPacketLogicalName = "afpacket-vpp2"

	/* TAP */

	// HostInterconnectTAPinVPPLogicalName is the logical name of the TAP interface
	// connecting host stack with VPP
	//  - VPP side
	HostInterconnectTAPinVPPLogicalName = "tap-vpp2"

	// HostInterconnectTAPinLinuxLogicalName is the logical name of the TAP interface
	// connecting host stack with VPP
	//  - Linux side
	HostInterconnectTAPinLinuxLogicalName = "tap-vpp1"

	// HostInterconnectTAPinLinuxHostName is the physical name of the TAP interface
	// connecting host stack with VPP
	//  - the Linux side
	HostInterconnectTAPinLinuxHostName = "vpp1"
)

// prefix for the hardware address of host interconnects
var hostInterconnectHwAddrPrefix = []byte{0x34, 0x3c}

/************************** VPP <-> Host connectivity **************************/

// hostInterconnectVPPIfName returns the logical name of the VPP-host interconnect
// interface on the VPP side.
func (n *IPv4Net) hostInterconnectVPPIfName() string {
	if n.config.UseTAPInterfaces {
		return HostInterconnectTAPinVPPLogicalName
	}
	return hostInterconnectAFPacketLogicalName
}

// hostInterconnectLinuxIfName returns the logical name of the VPP-host interconnect
// interface on the Linux side.
func (n *IPv4Net) hostInterconnectLinuxIfName() string {
	if n.config.UseTAPInterfaces {
		return HostInterconnectTAPinLinuxLogicalName
	}
	return hostInterconnectVETH1LogicalName
}

// interconnectTapVPP returns configuration for the VPP-side of the TAP interface
// connecting VPP with the host stack.
func (n *IPv4Net) interconnectTapVPP() (key string, config *interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	tap := &interfaces.Interface{
		Name:    HostInterconnectTAPinVPPLogicalName,
		Type:    interfaces.Interface_TAP,
		Mtu:     n.config.MTUSize,
		Enabled: true,
		Vrf:     n.GetMainVrfID(),
		Link: &interfaces.Interface_Tap{
			Tap: &interfaces.TapLink{},
		},
		PhysAddress: hwAddrForNodeInterface(n.NodeSync.GetNodeID(), hostInterconnectHwAddrPrefix),
	}
	if n.InSTNMode() {
		tap.Unnumbered = &interfaces.Interface_Unnumbered{
			InterfaceWithIp: n.mainPhysicalIf,
		}
	} else {
		tap.IpAddresses = []string{n.ipam.HostInterconnectIPInVPP().String() + "/" + strconv.Itoa(size)}

	}
	if n.config.TAPInterfaceVersion == 2 {
		tap.GetTap().Version = 2
		tap.GetTap().RxRingSize = uint32(n.config.TAPv2RxRingSize)
		tap.GetTap().TxRingSize = uint32(n.config.TAPv2TxRingSize)
	}
	key = interfaces.InterfaceKey(tap.Name)
	return key, tap
}

// interconnectTapHost returns configuration for the Host-side of the TAP interface
// connecting VPP with the host stack.
func (n *IPv4Net) interconnectTapHost() (key string, config *linux_interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	tap := &linux_interfaces.Interface{
		Name: HostInterconnectTAPinLinuxLogicalName,
		Type: linux_interfaces.Interface_TAP_TO_VPP,
		Link: &linux_interfaces.Interface_Tap{
			Tap: &linux_interfaces.TapLink{
				VppTapIfName: HostInterconnectTAPinVPPLogicalName,
			},
		},
		Mtu:        n.config.MTUSize,
		HostIfName: HostInterconnectTAPinLinuxHostName,
		Enabled:    true,
	}
	if n.InSTNMode() {
		if len(n.nodeIP) > 0 {
			tap.IpAddresses = []string{combineAddrWithNet(n.nodeIP, n.nodeIPNet).String()}
		}
	} else {
		tap.IpAddresses = []string{n.ipam.HostInterconnectIPInLinux().String() + "/" + strconv.Itoa(size)}
	}
	key = linux_interfaces.InterfaceKey(tap.Name)
	return key, tap
}

// interconnectAfpacket returns configuration for the AF-Packet interface attached
// to interconnectVethVpp (see below)
func (n *IPv4Net) interconnectAfpacket() (key string, config *interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	afpacket := &interfaces.Interface{
		Name: hostInterconnectAFPacketLogicalName,
		Type: interfaces.Interface_AF_PACKET,
		Link: &interfaces.Interface_Afpacket{
			Afpacket: &interfaces.AfpacketLink{
				HostIfName: hostInterconnectVETH2HostName,
			},
		},
		Mtu:         n.config.MTUSize,
		Enabled:     true,
		Vrf:         n.GetMainVrfID(),
		IpAddresses: []string{n.ipam.HostInterconnectIPInVPP().String() + "/" + strconv.Itoa(size)},
		PhysAddress: hwAddrForNodeInterface(n.NodeSync.GetNodeID(), hostInterconnectHwAddrPrefix),
	}
	key = interfaces.InterfaceKey(afpacket.Name)
	return key, afpacket
}

// interconnectVethVpp returns configuration for VPP-side of the VETH pipe connecting
// vswitch with the host stack.
func (n *IPv4Net) interconnectVethVpp() (key string, config *linux_interfaces.Interface) {
	veth := &linux_interfaces.Interface{
		Name: hostInterconnectVETH2LogicalName,
		Type: linux_interfaces.Interface_VETH,
		Link: &linux_interfaces.Interface_Veth{
			Veth: &linux_interfaces.VethLink{PeerIfName: hostInterconnectVETH1LogicalName},
		},
		Mtu:        n.config.MTUSize,
		Enabled:    true,
		HostIfName: hostInterconnectVETH2HostName,
	}
	key = linux_interfaces.InterfaceKey(veth.Name)
	return key, veth
}

// interconnectVethHost returns configuration for host-side of the VETH pipe connecting
// vswitch with the host stack.
func (n *IPv4Net) interconnectVethHost() (key string, config *linux_interfaces.Interface) {
	size, _ := n.ipam.HostInterconnectSubnetThisNode().Mask.Size()
	veth := &linux_interfaces.Interface{
		Name: hostInterconnectVETH1LogicalName,
		Type: linux_interfaces.Interface_VETH,
		Link: &linux_interfaces.Interface_Veth{
			Veth: &linux_interfaces.VethLink{PeerIfName: hostInterconnectVETH2LogicalName},
		},
		Mtu:         n.config.MTUSize,
		Enabled:     true,
		HostIfName:  hostInterconnectVETH1HostName,
		IpAddresses: []string{n.ipam.HostInterconnectIPInLinux().String() + "/" + strconv.Itoa(size)},
	}
	if n.config.TCPChecksumOffloadDisabled {
		veth.GetVeth().RxChecksumOffloading = linux_interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
		veth.GetVeth().TxChecksumOffloading = linux_interfaces.VethLink_CHKSM_OFFLOAD_DISABLED
	}
	key = linux_interfaces.InterfaceKey(veth.Name)
	return key, veth
}

// routesToHost return one route to configure on VPP for every host interface.
func (n *IPv4Net) routesToHost(nextHopIP net.IP) map[string]*l3.StaticRoute {
	routes := make(map[string]*l3.StaticRoute)

	// generate a /32 static route from VPP for each of the host's IPs
	for _, ip := range n.hostIPs {
		route := &l3.StaticRoute{
			DstNetwork:        fmt.Sprintf("%s/32", ip.String()),
			NextHopAddr:       nextHopIP.String(),
			OutgoingInterface: n.hostInterconnectVPPIfName(),
			VrfId:             n.GetMainVrfID(),
		}
		key := l3.RouteKey(route.VrfId, route.DstNetwork, route.NextHopAddr)
		routes[key] = route
	}

	return routes
}

// routePODsFromHost returns configuration for route for the host stack to direct
// traffic destined to pods via VPP.
func (n *IPv4Net) routePODsFromHost(nextHopIP net.IP) (key string, config *linux_l3.StaticRoute) {
	route := &linux_l3.StaticRoute{
		OutgoingInterface: hostInterconnectVETH1LogicalName,
		Scope:             linux_l3.StaticRoute_GLOBAL,
		DstNetwork:        n.ipam.PodSubnetAllNodes().String(),
		GwAddr:            nextHopIP.String(),
	}
	if n.config.UseTAPInterfaces {
		route.OutgoingInterface = HostInterconnectTAPinLinuxLogicalName
	}
	key = linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
	return key, route
}

// routeServicesFromHost returns configuration for route for the host stack to direct
// traffic destined to services via VPP.
func (n *IPv4Net) routeServicesFromHost(nextHopIP net.IP) (key string, config *linux_l3.StaticRoute) {
	route := &linux_l3.StaticRoute{
		OutgoingInterface: hostInterconnectVETH1LogicalName,
		Scope:             linux_l3.StaticRoute_GLOBAL,
		DstNetwork:        n.ipam.ServiceNetwork().String(),
		GwAddr:            nextHopIP.String(),
	}
	if n.config.UseTAPInterfaces {
		route.OutgoingInterface = HostInterconnectTAPinLinuxLogicalName
	}
	key = linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
	return key, route
}

/************************************ STN *************************************/

// stnRule returns configuration for STN rule, used to forward all traffic not matched
// in VPP to host via interconnect interface.
// The method assumes that node has IP address allocated!
func (n *IPv4Net) stnRule() (key string, config *stn.Rule) {
	rule := &stn.Rule{
		IpAddress: n.nodeIP.String(),
		Interface: n.hostInterconnectVPPIfName(),
	}
	key = stn.Key(rule.Interface, rule.IpAddress)
	return key, rule
}

// proxyArpForSTNGateway configures proxy ARP used in the STN case to let VPP to answer
// to ARP requests coming from the host stack.
func (n *IPv4Net) proxyArpForSTNGateway() (key string, config *l3.ProxyARP) {
	firstIP, lastIP := cidr.AddressRange(n.nodeIPNet)

	// If larger than a /31, remove network and broadcast addresses
	// from address range.
	if cidr.AddressCount(n.nodeIPNet) > 2 {
		firstIP = cidr.Inc(firstIP)
		lastIP = cidr.Dec(lastIP)
	}

	proxyarp := &l3.ProxyARP{
		Interfaces: []*l3.ProxyARP_Interface{
			{Name: n.hostInterconnectVPPIfName()},
		},
		Ranges: []*l3.ProxyARP_Range{
			{
				FirstIpAddr: firstIP.String(),
				LastIpAddr:  lastIP.String(),
			},
		},
	}
	key = l3.ProxyARPKey
	return key, proxyarp
}

// stnRoutesForVPP returns VPP routes mirroring Host routes that were associated
// with the stolen interface.
func (n *IPv4Net) stnRoutesForVPP() map[string]*l3.StaticRoute {
	routes := make(map[string]*l3.StaticRoute)

	for _, stnRoute := range n.stnRoutes {
		if stnRoute.NextHopIp == "" {
			continue // skip routes with no next hop IP (link-local)
		}
		route := &l3.StaticRoute{
			DstNetwork:        stnRoute.DestinationSubnet,
			NextHopAddr:       stnRoute.NextHopIp,
			OutgoingInterface: n.mainPhysicalIf,
			VrfId:             n.GetMainVrfID(),
		}
		if route.DstNetwork == "" {
			route.DstNetwork = ipv4NetAny
		}
		key := l3.RouteKey(route.VrfId, route.DstNetwork, route.NextHopAddr)
		routes[key] = route
	}

	return routes
}

// stnRoutesForHost returns configuration of routes that were associated
// with the stolen interface, now updated to route via host-interconnect.
func (n *IPv4Net) stnRoutesForHost() map[string]*linux_l3.StaticRoute {
	routes := make(map[string]*linux_l3.StaticRoute)

	for _, stnRoute := range n.stnRoutes {
		if stnRoute.NextHopIp == "" {
			continue // skip routes with no next hop IP (link-local)
		}
		route := &linux_l3.StaticRoute{
			DstNetwork:        stnRoute.DestinationSubnet,
			GwAddr:            stnRoute.NextHopIp,
			Scope:             linux_l3.StaticRoute_GLOBAL,
			OutgoingInterface: n.hostInterconnectLinuxIfName(),
		}
		if route.DstNetwork == "" {
			route.DstNetwork = ipv4NetAny
		}
		key := linux_l3.StaticRouteKey(route.DstNetwork, route.OutgoingInterface)
		routes[key] = route
	}

	return routes
}
<|endoftext|>


<commit_before>package remote

import (
	"github.com/weaveworks/flux"
)

func UnavailableError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Cannot contact fluxd

To service this request, we need to ask the agent running in your
cluster (fluxd) for some information. But we can't connect to it at
present.

This may be because it's not running at all, or because it has
temporarily disconnected. You can check whether fluxd is connected with

    fluxctl status

If you are sure fluxd is running, you can simply wait a few seconds
and try the operation again.

If you are not sure if fluxd is running, please consult the
installation instructions in our documentation:

    https://github.com/weaveworks/flux/blob/master/site/installing.md

If you are still stuck, please log an issue:

    https://github.com/weaveworks/flux/issues

`,
		Err: err,
	}}
}

func UpgradeNeededError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Your fluxd needs to be upgraded

To service this request, we need to ask the agent running in your
cluster (fluxd) to perform an operation on our behalf, but the
version you have running is too old to understand the request.

Please install the latest version of fluxd and try again.

`,
		Err: err,
	}}
}

func ClusterError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Error from Flux daemon

The Flux daemon (fluxd) reported this error:

    ` + err.Error() + `

which indicates that it is running, but cannot complete the request.

Thus may be because the request wasn't valid; e.g., you asked for
something in a namespace that doesn't exist.

Otherwise, it is likely to be an ongoing problem until fluxd is
updated and/or redeployed. For help, please consult the installation
instructions:

    https://github.com/weaveworks/flux/blob/master/site/installing.md

If you are still stuck, please log an issue:

    https://github.com/weaveworks/flux/issues

`,
		Err: err,
	}}
}
<commit_msg>Remove `flux status` command from help. Slight rewording.<commit_after>package remote

import (
	"github.com/weaveworks/flux"
)

func UnavailableError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Cannot contact flux

To service this request, we need to ask the agent running in your
cluster (flux) for some information. But we can't connect to it at
present.

This may be because it's not running at all, is temporarily
disconnected or has been firewalled.

If you are sure flux is running, you can simply wait a few seconds
and try the operation again.

Otherwise, please consult the installation instructions in our
documentation:

    https://github.com/weaveworks/flux/blob/master/site/installing.md

If you are still stuck, please log an issue:

    https://github.com/weaveworks/flux/issues

`,
		Err: err,
	}}
}

func UpgradeNeededError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Your fluxd needs to be upgraded

To service this request, we need to ask the agent running in your
cluster (fluxd) to perform an operation on our behalf, but the
version you have running is too old to understand the request.

Please install the latest version of fluxd and try again.

`,
		Err: err,
	}}
}

func ClusterError(err error) error {
	return flux.UserConfigProblem{&flux.BaseError{
		Help: `Error from Flux daemon

The Flux daemon (fluxd) reported this error:

    ` + err.Error() + `

which indicates that it is running, but cannot complete the request.

Thus may be because the request wasn't valid; e.g., you asked for
something in a namespace that doesn't exist.

Otherwise, it is likely to be an ongoing problem until fluxd is
updated and/or redeployed. For help, please consult the installation
instructions:

    https://github.com/weaveworks/flux/blob/master/site/installing.md

If you are still stuck, please log an issue:

    https://github.com/weaveworks/flux/issues

`,
		Err: err,
	}}
}
<|endoftext|>


<commit_before>package box

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
)

// TODO(ttacon): reconcile this with Folder for one common struct?
type File struct {
	ID                string          `json:"id,omitempty"`
	FolderUploadEmail *AccessEmail    `json:"folder_upload_email,omitempty"`
	Parent            *Item           `json:"parent,omitempty"`
	ItemStatus        string          `json:"item_status"`
	ItemCollection    *ItemCollection `json:"item_collection"`
	Type              string          `json:"type"` // TODO(ttacon): enum
	Description       string          `json:"description"`
	Size              int             `json:"size"`
	CreateBy          *Item           `json:"created_by"`
	ModifiedBy        *Item           `json:"modified_by"`
	TrashedAt         *string         `json:"trashed_at"`          // TODO(ttacon): change to time.Time
	ContentModifiedAt *string         `json:"content_modified_at"` // TODO(ttacon): change to time.Time
	PurgedAt          *string         `json:"purged_at"`           // TODO(ttacon): change to time.Time, this field isn't documented but I keep getting it back...
	SharedLinkg       *string         `json:"shared_link"`
	SequenceId        string          `json:"sequence_id"`
	ETag              *string         `json:"etag"`
	Name              string          `json:"name"`
	CreatedAt         *string         `json:"created_at"` // TODO(ttacon): change to time.Time
	OwnedBy           *Item           `json:"owned_by"`
	ModifiedAt        *string         `json:"modified_at"`        // TODO(ttacon): change to time.Time
	ContentCreatedAt  *string         `json:"content_created_at"` // TODO(ttacon): change to time.Time
	PathCollection    *PathCollection `json:"path_collection"`    // TODO(ttacon): make sure this is the correct kind of struct(ure)
	SharedLink        *Link           `json:"shared_link"`

	SHA1 string `json:"sha1"`
}

type FileCollection struct {
	TotalCount int     `json:"total_count"`
	Entries    []*File `json:"entries"`
}

// Documentation: https://developer.box.com/docs/#files-get
func (c *Client) GetFile(fileId string) (*http.Response, *File, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation https://developer.box.com/docs/#files-upload-a-file
// TODO(ttacon): deal with handling SHA1 headers
func (c *Client) UploadFile(filePath, parentId string) (*http.Response, *FileCollection, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()

	fileContents, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, nil, err
	}

	fi, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	var (
		body   = &bytes.Buffer{}
		writer = multipart.NewWriter(body)
	)

	// write the file
	part, err := writer.CreateFormFile("file", fi.Name())
	if err != nil {
		return nil, nil, err
	}
	part.Write(fileContents)

	// write the other form fields we need
	err = writer.WriteField("filename", fi.Name())
	if err != nil {
		return nil, nil, err
	}

	err = writer.WriteField("parent_id", parentId)
	if err != nil {
		return nil, nil, err
	}

	// TODO(ttacon): add in content_created_at, content_modified_at

	err = writer.Close()
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("https://upload.box.com/api/2.0/files/content"),
		body,
	)
	req.Header.Add("Content-Type", writer.FormDataContentType())
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data FileCollection
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-delete-a-file
func (c *Client) DeleteFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"DELETE",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-copy-a-file
func (c *Client) CopyFile(fileId, parent, name string) (*http.Response, *File, error) {
	var bodyData = map[string]interface{}{
		"parent": map[string]string{
			"id": parent,
		},
		"name": name,
	}

	dataBytes, err := json.Marshal(&bodyData)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("%s/files/%s/copy", BASE_URL, fileId),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// NOTE: we return the http.Response as Box may return a 202 if there is not
// yet a download link, or a 302 with the link - this allows the user to
// decide what to do.
// Documentation: https://developers.box.com/docs/#files-download-a-file
func (c *Client) DownloadFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/content", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-view-versions-of-a-file
// TODO(ttacon): don't use file collection, make actual structs specific to file versions
func (c *Client) ViewVersionsOfFile(fileId string) (*http.Response, *FileCollection, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/versions", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data FileCollection
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// NOTE: we only return the response as there are many possible responses that we
// feel the user should have control over
// Documentation: https://developers.box.com/docs/#files-get-a-thumbnail-for-a-file
func (c *Client) GetThumbnail(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/thumbnail.extension", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-create-a-shared-link-for-a-file
func (c *Client) CreateSharedLinkForFile(fileId, access, unsharedAt string, canDownload, canPreview bool) (*http.Response, *File, error) {
	var dataMap = make(map[string]interface{})
	if len(access) > 0 {
		dataMap["access"] = access
	}
	// TODO(ttacon): support unshared_at as time.Time
	// TODO(ttacon): validate access is open or company before add permissions
	if canDownload {
		dataMap["permissions"] = map[string]bool{
			"can_download": canDownload,
		}
	}
	if canPreview {
		if m, ok := dataMap["permissions"]; ok {
			mVal, _ := m.(map[string]bool)
			mVal["can_preview"] = canPreview
		} else {
			dataMap["permissions"] = map[string]bool{
				"can_preview": canPreview,
			}
		}
	}

	dataBytes, err := json.Marshal(&dataMap)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"PUT",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-get-a-trashed-file
func (c *Client) GetTrashedFile(fileId string) (*http.Response, *File, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/trash", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-restore-a-trashed-item
func (c *Client) RestoreTrashedItem(name, parentId string) (*http.Response, *File, error) {
	var dataMap = make(map[string]interface{})
	if len(name) > 0 {
		dataMap["name"] = name
	}
	if len(parentId) > 0 {
		dataMap["parent"] = map[string]string{
			"id": parentId,
		}
	}
	dataBytes, err := json.Marshal(&dataMap)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf(),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-permanently-delete-a-trashed-file
func (c *Client) PermanentlyDeleteTrashedFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"DELETE",
		fmt.Sprintf("%s/files/%s/trash", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}
<commit_msg>Add ViewCommentsOnFile<commit_after>package box

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
)

// TODO(ttacon): reconcile this with Folder for one common struct?
type File struct {
	ID                string          `json:"id,omitempty"`
	FolderUploadEmail *AccessEmail    `json:"folder_upload_email,omitempty"`
	Parent            *Item           `json:"parent,omitempty"`
	ItemStatus        string          `json:"item_status"`
	ItemCollection    *ItemCollection `json:"item_collection"`
	Type              string          `json:"type"` // TODO(ttacon): enum
	Description       string          `json:"description"`
	Size              int             `json:"size"`
	CreateBy          *Item           `json:"created_by"`
	ModifiedBy        *Item           `json:"modified_by"`
	TrashedAt         *string         `json:"trashed_at"`          // TODO(ttacon): change to time.Time
	ContentModifiedAt *string         `json:"content_modified_at"` // TODO(ttacon): change to time.Time
	PurgedAt          *string         `json:"purged_at"`           // TODO(ttacon): change to time.Time, this field isn't documented but I keep getting it back...
	SharedLinkg       *string         `json:"shared_link"`
	SequenceId        string          `json:"sequence_id"`
	ETag              *string         `json:"etag"`
	Name              string          `json:"name"`
	CreatedAt         *string         `json:"created_at"` // TODO(ttacon): change to time.Time
	OwnedBy           *Item           `json:"owned_by"`
	ModifiedAt        *string         `json:"modified_at"`        // TODO(ttacon): change to time.Time
	ContentCreatedAt  *string         `json:"content_created_at"` // TODO(ttacon): change to time.Time
	PathCollection    *PathCollection `json:"path_collection"`    // TODO(ttacon): make sure this is the correct kind of struct(ure)
	SharedLink        *Link           `json:"shared_link"`

	SHA1 string `json:"sha1"`
}

type FileCollection struct {
	TotalCount int     `json:"total_count"`
	Entries    []*File `json:"entries"`
}

// Documentation: https://developer.box.com/docs/#files-get
func (c *Client) GetFile(fileId string) (*http.Response, *File, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation https://developer.box.com/docs/#files-upload-a-file
// TODO(ttacon): deal with handling SHA1 headers
func (c *Client) UploadFile(filePath, parentId string) (*http.Response, *FileCollection, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, nil, err
	}
	defer file.Close()

	fileContents, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, nil, err
	}

	fi, err := file.Stat()
	if err != nil {
		return nil, nil, err
	}

	var (
		body   = &bytes.Buffer{}
		writer = multipart.NewWriter(body)
	)

	// write the file
	part, err := writer.CreateFormFile("file", fi.Name())
	if err != nil {
		return nil, nil, err
	}
	part.Write(fileContents)

	// write the other form fields we need
	err = writer.WriteField("filename", fi.Name())
	if err != nil {
		return nil, nil, err
	}

	err = writer.WriteField("parent_id", parentId)
	if err != nil {
		return nil, nil, err
	}

	// TODO(ttacon): add in content_created_at, content_modified_at

	err = writer.Close()
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("https://upload.box.com/api/2.0/files/content"),
		body,
	)
	req.Header.Add("Content-Type", writer.FormDataContentType())
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data FileCollection
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-delete-a-file
func (c *Client) DeleteFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"DELETE",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-copy-a-file
func (c *Client) CopyFile(fileId, parent, name string) (*http.Response, *File, error) {
	var bodyData = map[string]interface{}{
		"parent": map[string]string{
			"id": parent,
		},
		"name": name,
	}

	dataBytes, err := json.Marshal(&bodyData)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf("%s/files/%s/copy", BASE_URL, fileId),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// NOTE: we return the http.Response as Box may return a 202 if there is not
// yet a download link, or a 302 with the link - this allows the user to
// decide what to do.
// Documentation: https://developers.box.com/docs/#files-download-a-file
func (c *Client) DownloadFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/content", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-view-versions-of-a-file
// TODO(ttacon): don't use file collection, make actual structs specific to file versions
func (c *Client) ViewVersionsOfFile(fileId string) (*http.Response, *FileCollection, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/versions", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data FileCollection
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// NOTE: we only return the response as there are many possible responses that we
// feel the user should have control over
// Documentation: https://developers.box.com/docs/#files-get-a-thumbnail-for-a-file
func (c *Client) GetThumbnail(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/thumbnail.extension", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-create-a-shared-link-for-a-file
func (c *Client) CreateSharedLinkForFile(fileId, access, unsharedAt string, canDownload, canPreview bool) (*http.Response, *File, error) {
	var dataMap = make(map[string]interface{})
	if len(access) > 0 {
		dataMap["access"] = access
	}
	// TODO(ttacon): support unshared_at as time.Time
	// TODO(ttacon): validate access is open or company before add permissions
	if canDownload {
		dataMap["permissions"] = map[string]bool{
			"can_download": canDownload,
		}
	}
	if canPreview {
		if m, ok := dataMap["permissions"]; ok {
			mVal, _ := m.(map[string]bool)
			mVal["can_preview"] = canPreview
		} else {
			dataMap["permissions"] = map[string]bool{
				"can_preview": canPreview,
			}
		}
	}

	dataBytes, err := json.Marshal(&dataMap)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"PUT",
		fmt.Sprintf("%s/files/%s", BASE_URL, fileId),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-get-a-trashed-file
func (c *Client) GetTrashedFile(fileId string) (*http.Response, *File, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/trash", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-restore-a-trashed-item
func (c *Client) RestoreTrashedItem(name, parentId string) (*http.Response, *File, error) {
	var dataMap = make(map[string]interface{})
	if len(name) > 0 {
		dataMap["name"] = name
	}
	if len(parentId) > 0 {
		dataMap["parent"] = map[string]string{
			"id": parentId,
		}
	}
	dataBytes, err := json.Marshal(&dataMap)
	if err != nil {
		return nil, nil, err
	}

	req, err := http.NewRequest(
		"POST",
		fmt.Sprintf(),
		bytes.NewReader(dataBytes),
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data File
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

// Documentation: https://developers.box.com/docs/#files-permanently-delete-a-trashed-file
func (c *Client) PermanentlyDeleteTrashedFile(fileId string) (*http.Response, error) {
	req, err := http.NewRequest(
		"DELETE",
		fmt.Sprintf("%s/files/%s/trash", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, err
	}

	return c.Trans.Client().Do(req)
}

// Documentation: https://developers.box.com/docs/#files-view-the-comments-on-a-file
func (c *Client) ViewCommentsOnFile(fileId string) (*http.Response, *CommentCollection, error) {
	req, err := http.NewRequest(
		"GET",
		fmt.Sprintf("%s/files/%s/comments", BASE_URL, fileId),
		nil,
	)
	if err != nil {
		return nil, nil, err
	}

	resp, err := c.Trans.Client().Do(req)
	if err != nil {
		return resp, nil, err
	}

	var data CommentCollection
	err = json.NewDecoder(resp.Body).Decode(&data)
	return resp, &data, err
}

type CommentCollection struct {
	TotalCount int        `json:"total_count"`
	Entries    []*Comment `json:"entries"`
}

type Comment struct {
	Type           string `json:"type"`
	Id             string `json:"id"`
	IsReplyComment bool   `json:"is_reply_comment"`
	Message        string `json:"message"`
	CreatedBy      *Item  `json:"created_by"` // TODO(ttacon): change this to user when make struct
	Item           *Item  `json:"item"`
	CreatedAt      string `json:"created_at"`  // TODO(ttacon): change to time.Time
	ModifiedAt     string `json:"modified_at"` // TODO(ttacon): change to time.Time
}
<|endoftext|>


<commit_before>package dropbox

import (
	"encoding/json"
	"io"
	"time"
)

// Files client for files and folders.
type Files struct {
	*Client
}

// NewFiles client.
func NewFiles(config *Config) *Files {
	return &Files{
		Client: &Client{
			Config: config,
		},
	}
}

// WriteMode determines what to do if the file already exists.
type WriteMode string

// Supported write modes.
const (
	WriteModeAdd       WriteMode = "add"
	WriteModeOverwrite           = "overwrite"
)

// Metadata for a file or folder.
type Metadata struct {
	Tag            string    `json:".tag"`
	Name           string    `json:"name"`
	PathLower      string    `json:"path_lower"`
	ClientModified time.Time `json:"client_modified"`
	ServerModified time.Time `json:"server_modified"`
	Rev            string    `json:"rev"`
	Size           uint64    `json:"size"`
	ID             string    `json:"id"`
}

// GetMetadataInput request input.
type GetMetadataInput struct {
	Path             string `json:"path"`
	IncludeMediaInfo bool   `json:"include_media_info"`
}

// GetMetadataOutput request output.
type GetMetadataOutput struct {
	Metadata
}

// GetMetadata returns the metadata for a file or folder.
func (c *Files) GetMetadata(in *GetMetadataInput) (out *GetMetadataOutput, err error) {
	body, err := c.call("/files/get_metadata", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// CreateFolderInput request input.
type CreateFolderInput struct {
	Path string `json:"path"`
}

// CreateFolderOutput request output.
type CreateFolderOutput struct {
	Name      string `json:"name"`
	PathLower string `json:"path_lower"`
	ID        string `json:"id"`
}

// CreateFolder creates a folder.
func (c *Files) CreateFolder(in *CreateFolderInput) (out *CreateFolderOutput, err error) {
	body, err := c.call("/files/create_folder", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// DeleteInput request input.
type DeleteInput struct {
	Path string `json:"path"`
}

// DeleteOutput request output.
type DeleteOutput struct {
	Metadata
}

// Delete a file or folder and its contents.
func (c *Files) Delete(in *DeleteInput) (out *DeleteOutput, err error) {
	body, err := c.call("/files/delete", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// PermanentlyDeleteInput request input.
type PermanentlyDeleteInput struct {
	Path string `json:"path"`
}

// PermanentlyDelete a file or folder and its contents.
func (c *Files) PermanentlyDelete(in *PermanentlyDeleteInput) (err error) {
	body, err := c.call("/files/delete", in)
	if err != nil {
		return
	}
	defer body.Close()

	return
}

// CopyInput request input.
type CopyInput struct {
	FromPath string `json:"from_path"`
	ToPath   string `json:"to_path"`
}

// CopyOutput request output.
type CopyOutput struct {
	Metadata
}

// Copy a file or folder to a different location.
func (c *Files) Copy(in *CopyInput) (out *CopyOutput, err error) {
	body, err := c.call("/files/copy", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// MoveInput request input.
type MoveInput struct {
	FromPath string `json:"from_path"`
	ToPath   string `json:"to_path"`
}

// MoveOutput request output.
type MoveOutput struct {
	Metadata
}

// Move a file or folder to a different location.
func (c *Files) Move(in *MoveInput) (out *MoveOutput, err error) {
	body, err := c.call("/files/move", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// RestoreInput request input.
type RestoreInput struct {
	Path string `json:"path"`
	Rev  string `json:"rev"`
}

// RestoreOutput request output.
type RestoreOutput struct {
	Metadata
}

// Restore a file to a specific revision.
func (c *Files) Restore(in *RestoreInput) (out *RestoreOutput, err error) {
	body, err := c.call("/files/restore", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// ListFolderInput request input.
type ListFolderInput struct {
	Path             string `json:"path"`
	Recursive        bool   `json:"recursive"`
	IncludeMediaInfo bool   `json:"include_media_info"`
	IncludeDeleted   bool   `json:"include_deleted"`
}

// ListFolderOutput request output.
type ListFolderOutput struct {
	Cursor  string `json:"cursor"`
	HasMore bool   `json:"has_more"`
	Entries []*Metadata
}

// ListFolder returns the metadata for a file or folder.
func (c *Files) ListFolder(in *ListFolderInput) (out *ListFolderOutput, err error) {
	in.Path = normalizePath(in.Path)

	body, err := c.call("/files/list_folder", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// ListFolderContinueInput request input.
type ListFolderContinueInput struct {
	Cursor string `json:"cursor"`
}

// ListFolderContinue pagenates using the cursor from ListFolder.
func (c *Files) ListFolderContinue(in *ListFolderContinueInput) (out *ListFolderOutput, err error) {
	body, err := c.call("/files/list_folder/continue", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// SearchMode determines how a search is performed.
type SearchMode string

// Supported search modes.
const (
	SearchModeFilename           SearchMode = "filename"
	SearchModeFilenameAndContent            = "filename_and_content"
	SearchModeDeletedFilename               = "deleted_filename"
)

// SearchMatchType represents the type of match made.
type SearchMatchType string

// Supported search match types.
const (
	SearchMatchFilename SearchMatchType = "filename"
	SearchMatchContent                  = "content"
	SearchMatchBoth                     = "both"
)

// SearchMatch represents a matched file or folder.
type SearchMatch struct {
	MatchType struct {
		Tag SearchMatchType `json:".tag"`
	} `json:"match_type"`
	Metadata *Metadata `json:"metadata"`
}

// SearchInput request input.
type SearchInput struct {
	Path       string     `json:"path"`
	Query      string     `json:"query"`
	Start      uint64     `json:"start,omitempty"`
	MaxResults uint64     `json:"max_results,omitempty"`
	Mode       SearchMode `json:"mode"`
}

// SearchOutput request output.
type SearchOutput struct {
	Matches []*SearchMatch `json:"matches"`
	More    bool           `json:"more"`
	Start   uint64         `json:"start"`
}

// Search for files and folders.
func (c *Files) Search(in *SearchInput) (out *SearchOutput, err error) {
	in.Path = normalizePath(in.Path)

	if in.Mode == "" {
		in.Mode = SearchModeFilename
	}

	body, err := c.call("/files/search", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// UploadInput request input.
type UploadInput struct {
	Path           string    `json:"path"`
	Mode           WriteMode `json:"mode"`
	AutoRename     bool      `json:"autorename"`
	Mute           bool      `json:"mute"`
	ClientModified time.Time `json:"client_modified,omitempty"`
	Reader         io.Reader `json:"-"`
}

// UploadOutput request output.
type UploadOutput struct {
	Metadata
}

// Upload a file smaller than 150MB.
func (c *Files) Upload(in *UploadInput) (out *UploadOutput, err error) {
	body, err := c.download("/files/upload", in, in.Reader)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// DownloadInput request input.
type DownloadInput struct {
	Path string `json:"path"`
}

// DownloadOutput request output.
type DownloadOutput struct {
	Body io.ReadCloser
}

// Download a file.
func (c *Files) Download(in *DownloadInput) (out *DownloadOutput, err error) {
	body, err := c.download("/files/download", in, nil)
	if err != nil {
		return
	}

	out = &DownloadOutput{body}
	return
}

const (
	// GetThumbnailFormatJPEG specifies a JPG thumbnail
	GetThumbnailFormatJPEG = "jpeg"
	// GetThumbnailFormatPNG specifies a PNG thumbnail
	GetThumbnailFormatPNG = "png"
)

const (
	// GetThumbnailSizeW32H32 specifies a size of 32 by 32 px
	GetThumbnailSizeW32H32 = "w32h32"
	// GetThumbnailSizeW64H64 specifies a size of 64 by 64 px
	GetThumbnailSizeW64H64 = "w64h64"
	// GetThumbnailSizeW128H128 specifies a size of 128 by 128 px
	GetThumbnailSizeW128H128 = "w128h128"
	// GetThumbnailSizeW640H480 specifies a size of 640 by 480 px
	GetThumbnailSizeW640H480 = "w640h480"
	// GetThumbnailSizeW1024H768 specifies a size of 1024 by 768 px
	GetThumbnailSizeW1024H768 = "w1024h768"
)

// GetThumbnailInput request input.
type GetThumbnailInput struct {
	Path   string `json:"path"`
	Format string `json:"format"`
	Size   string `json:"size"`
}

// GetThumbnailOutput request output.
type GetThumbnailOutput struct {
	Body io.ReadCloser
}

// GetThumbnail a thumbnail for a file. Currently thumbnails are only generated for the
// files with the following extensions: png, jpeg, png, tiff, tif, gif and bmp.
func (c *Files) GetThumbnail(in *GetThumbnailInput) (out *GetThumbnailOutput, err error) {
	body, err := c.download("/files/get_thumbnail", in, nil)
	if err != nil {
		return
	}

	out = &GetThumbnailOutput{body}
	return
}

// GetPreviewInput request input.
type GetPreviewInput struct {
	Path string `json:"path"`
}

// GetPreviewOutput request output.
type GetPreviewOutput struct {
	Body io.ReadCloser
}

// GetPreview a preview for a file. Currently previews are only generated for the
// files with the following extensions: .doc, .docx, .docm, .ppt, .pps, .ppsx,
// .ppsm, .pptx, .pptm, .xls, .xlsx, .xlsm, .rtf
func (c *Files) GetPreview(in *GetPreviewInput) (out *GetPreviewOutput, err error) {
	body, err := c.download("/files/get_preview", in, nil)
	if err != nil {
		return
	}

	out = &GetPreviewOutput{body}
	return
}

// Normalize path so people can use "/" as they expect.
func normalizePath(s string) string {
	if s == "/" {
		return ""
	} else {
		return s
	}
}
<commit_msg>add types for thumbnail format and size<commit_after>package dropbox

import (
	"encoding/json"
	"io"
	"time"
)

// Files client for files and folders.
type Files struct {
	*Client
}

// NewFiles client.
func NewFiles(config *Config) *Files {
	return &Files{
		Client: &Client{
			Config: config,
		},
	}
}

// WriteMode determines what to do if the file already exists.
type WriteMode string

// Supported write modes.
const (
	WriteModeAdd       WriteMode = "add"
	WriteModeOverwrite           = "overwrite"
)

// Metadata for a file or folder.
type Metadata struct {
	Tag            string    `json:".tag"`
	Name           string    `json:"name"`
	PathLower      string    `json:"path_lower"`
	ClientModified time.Time `json:"client_modified"`
	ServerModified time.Time `json:"server_modified"`
	Rev            string    `json:"rev"`
	Size           uint64    `json:"size"`
	ID             string    `json:"id"`
}

// GetMetadataInput request input.
type GetMetadataInput struct {
	Path             string `json:"path"`
	IncludeMediaInfo bool   `json:"include_media_info"`
}

// GetMetadataOutput request output.
type GetMetadataOutput struct {
	Metadata
}

// GetMetadata returns the metadata for a file or folder.
func (c *Files) GetMetadata(in *GetMetadataInput) (out *GetMetadataOutput, err error) {
	body, err := c.call("/files/get_metadata", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// CreateFolderInput request input.
type CreateFolderInput struct {
	Path string `json:"path"`
}

// CreateFolderOutput request output.
type CreateFolderOutput struct {
	Name      string `json:"name"`
	PathLower string `json:"path_lower"`
	ID        string `json:"id"`
}

// CreateFolder creates a folder.
func (c *Files) CreateFolder(in *CreateFolderInput) (out *CreateFolderOutput, err error) {
	body, err := c.call("/files/create_folder", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// DeleteInput request input.
type DeleteInput struct {
	Path string `json:"path"`
}

// DeleteOutput request output.
type DeleteOutput struct {
	Metadata
}

// Delete a file or folder and its contents.
func (c *Files) Delete(in *DeleteInput) (out *DeleteOutput, err error) {
	body, err := c.call("/files/delete", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// PermanentlyDeleteInput request input.
type PermanentlyDeleteInput struct {
	Path string `json:"path"`
}

// PermanentlyDelete a file or folder and its contents.
func (c *Files) PermanentlyDelete(in *PermanentlyDeleteInput) (err error) {
	body, err := c.call("/files/delete", in)
	if err != nil {
		return
	}
	defer body.Close()

	return
}

// CopyInput request input.
type CopyInput struct {
	FromPath string `json:"from_path"`
	ToPath   string `json:"to_path"`
}

// CopyOutput request output.
type CopyOutput struct {
	Metadata
}

// Copy a file or folder to a different location.
func (c *Files) Copy(in *CopyInput) (out *CopyOutput, err error) {
	body, err := c.call("/files/copy", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// MoveInput request input.
type MoveInput struct {
	FromPath string `json:"from_path"`
	ToPath   string `json:"to_path"`
}

// MoveOutput request output.
type MoveOutput struct {
	Metadata
}

// Move a file or folder to a different location.
func (c *Files) Move(in *MoveInput) (out *MoveOutput, err error) {
	body, err := c.call("/files/move", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// RestoreInput request input.
type RestoreInput struct {
	Path string `json:"path"`
	Rev  string `json:"rev"`
}

// RestoreOutput request output.
type RestoreOutput struct {
	Metadata
}

// Restore a file to a specific revision.
func (c *Files) Restore(in *RestoreInput) (out *RestoreOutput, err error) {
	body, err := c.call("/files/restore", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// ListFolderInput request input.
type ListFolderInput struct {
	Path             string `json:"path"`
	Recursive        bool   `json:"recursive"`
	IncludeMediaInfo bool   `json:"include_media_info"`
	IncludeDeleted   bool   `json:"include_deleted"`
}

// ListFolderOutput request output.
type ListFolderOutput struct {
	Cursor  string `json:"cursor"`
	HasMore bool   `json:"has_more"`
	Entries []*Metadata
}

// ListFolder returns the metadata for a file or folder.
func (c *Files) ListFolder(in *ListFolderInput) (out *ListFolderOutput, err error) {
	in.Path = normalizePath(in.Path)

	body, err := c.call("/files/list_folder", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// ListFolderContinueInput request input.
type ListFolderContinueInput struct {
	Cursor string `json:"cursor"`
}

// ListFolderContinue pagenates using the cursor from ListFolder.
func (c *Files) ListFolderContinue(in *ListFolderContinueInput) (out *ListFolderOutput, err error) {
	body, err := c.call("/files/list_folder/continue", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// SearchMode determines how a search is performed.
type SearchMode string

// Supported search modes.
const (
	SearchModeFilename           SearchMode = "filename"
	SearchModeFilenameAndContent            = "filename_and_content"
	SearchModeDeletedFilename               = "deleted_filename"
)

// SearchMatchType represents the type of match made.
type SearchMatchType string

// Supported search match types.
const (
	SearchMatchFilename SearchMatchType = "filename"
	SearchMatchContent                  = "content"
	SearchMatchBoth                     = "both"
)

// SearchMatch represents a matched file or folder.
type SearchMatch struct {
	MatchType struct {
		Tag SearchMatchType `json:".tag"`
	} `json:"match_type"`
	Metadata *Metadata `json:"metadata"`
}

// SearchInput request input.
type SearchInput struct {
	Path       string     `json:"path"`
	Query      string     `json:"query"`
	Start      uint64     `json:"start,omitempty"`
	MaxResults uint64     `json:"max_results,omitempty"`
	Mode       SearchMode `json:"mode"`
}

// SearchOutput request output.
type SearchOutput struct {
	Matches []*SearchMatch `json:"matches"`
	More    bool           `json:"more"`
	Start   uint64         `json:"start"`
}

// Search for files and folders.
func (c *Files) Search(in *SearchInput) (out *SearchOutput, err error) {
	in.Path = normalizePath(in.Path)

	if in.Mode == "" {
		in.Mode = SearchModeFilename
	}

	body, err := c.call("/files/search", in)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// UploadInput request input.
type UploadInput struct {
	Path           string    `json:"path"`
	Mode           WriteMode `json:"mode"`
	AutoRename     bool      `json:"autorename"`
	Mute           bool      `json:"mute"`
	ClientModified time.Time `json:"client_modified,omitempty"`
	Reader         io.Reader `json:"-"`
}

// UploadOutput request output.
type UploadOutput struct {
	Metadata
}

// Upload a file smaller than 150MB.
func (c *Files) Upload(in *UploadInput) (out *UploadOutput, err error) {
	body, err := c.download("/files/upload", in, in.Reader)
	if err != nil {
		return
	}
	defer body.Close()

	err = json.NewDecoder(body).Decode(&out)
	return
}

// DownloadInput request input.
type DownloadInput struct {
	Path string `json:"path"`
}

// DownloadOutput request output.
type DownloadOutput struct {
	Body io.ReadCloser
}

// Download a file.
func (c *Files) Download(in *DownloadInput) (out *DownloadOutput, err error) {
	body, err := c.download("/files/download", in, nil)
	if err != nil {
		return
	}

	out = &DownloadOutput{body}
	return
}

// ThumbnailFormat determines the format of the thumbnail.
type ThumbnailFormat string

const (
	// GetThumbnailFormatJPEG specifies a JPG thumbnail
	GetThumbnailFormatJPEG ThumbnailFormat = "jpeg"
	// GetThumbnailFormatPNG specifies a PNG thumbnail
	GetThumbnailFormatPNG = "png"
)

// ThumbnailFormat determines the size of the thumbnail.
type ThumbnailSize string

const (
	// GetThumbnailSizeW32H32 specifies a size of 32 by 32 px
	GetThumbnailSizeW32H32 ThumbnailSize = "w32h32"
	// GetThumbnailSizeW64H64 specifies a size of 64 by 64 px
	GetThumbnailSizeW64H64 = "w64h64"
	// GetThumbnailSizeW128H128 specifies a size of 128 by 128 px
	GetThumbnailSizeW128H128 = "w128h128"
	// GetThumbnailSizeW640H480 specifies a size of 640 by 480 px
	GetThumbnailSizeW640H480 = "w640h480"
	// GetThumbnailSizeW1024H768 specifies a size of 1024 by 768 px
	GetThumbnailSizeW1024H768 = "w1024h768"
)

// GetThumbnailInput request input.
type GetThumbnailInput struct {
	Path   string          `json:"path"`
	Format ThumbnailFormat `json:"format"`
	Size   ThumbnailSize   `json:"size"`
}

// GetThumbnailOutput request output.
type GetThumbnailOutput struct {
	Body io.ReadCloser
}

// GetThumbnail a thumbnail for a file. Currently thumbnails are only generated for the
// files with the following extensions: png, jpeg, png, tiff, tif, gif and bmp.
func (c *Files) GetThumbnail(in *GetThumbnailInput) (out *GetThumbnailOutput, err error) {
	body, err := c.download("/files/get_thumbnail", in, nil)
	if err != nil {
		return
	}

	out = &GetThumbnailOutput{body}
	return
}

// GetPreviewInput request input.
type GetPreviewInput struct {
	Path string `json:"path"`
}

// GetPreviewOutput request output.
type GetPreviewOutput struct {
	Body io.ReadCloser
}

// GetPreview a preview for a file. Currently previews are only generated for the
// files with the following extensions: .doc, .docx, .docm, .ppt, .pps, .ppsx,
// .ppsm, .pptx, .pptm, .xls, .xlsx, .xlsm, .rtf
func (c *Files) GetPreview(in *GetPreviewInput) (out *GetPreviewOutput, err error) {
	body, err := c.download("/files/get_preview", in, nil)
	if err != nil {
		return
	}

	out = &GetPreviewOutput{body}
	return
}

// Normalize path so people can use "/" as they expect.
func normalizePath(s string) string {
	if s == "/" {
		return ""
	} else {
		return s
	}
}
<|endoftext|>


<commit_before>package render

import (
	"encoding/json"
	"errors"
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/oakmound/oak/dlog"
	"github.com/oakmound/oak/fileutil"
)

var (
	regexpSingleNumber, _ = regexp.Compile(`^\d+$`)
	regexpTwoNumbers, _   = regexp.Compile(`^\d+x\d+$`)
)

var (
	// Form ...main/core.go/assets/images,
	// the image directory.
	wd, _ = os.Getwd()
	dir   = filepath.Join(
		wd,
		"assets",
		"images")
	loadedImages = make(map[string]*image.RGBA)
	loadedSheets = make(map[string]*Sheet)
	// move to some batch load settings
	defaultPad = 0
	loadLock   = sync.Mutex{}
)

func loadPNG(directory, fileName string) *image.RGBA {

	loadLock.Lock()
	if _, ok := loadedImages[fileName]; !ok {
		imgFile, err := fileutil.Open(filepath.Join(directory, fileName))
		if err != nil {
			log.Fatal(err)
		}
		defer func() {
			err = imgFile.Close()
			if err != nil {
				dlog.Error(err)
			}
		}()

		img, err := png.Decode(imgFile)
		if err != nil {
			log.Fatal(err)
		}

		bounds := img.Bounds()
		rgba := image.NewRGBA(bounds)
		for x := 0; x < bounds.Max.X; x++ {
			for y := 0; y < bounds.Max.Y; y++ {
				rgba.Set(x, y, color.RGBAModel.Convert(img.At(x, y)))
			}
		}

		loadedImages[fileName] = rgba

		dlog.Verb("Loaded filename: ", fileName)
	}
	r := loadedImages[fileName]
	loadLock.Unlock()
	return r
}

// LoadSprite loads the input fileName into a Sprite
func LoadSprite(fileName string) *Sprite {
	return NewSprite(0, 0, loadPNG(dir, fileName))
}

// GetSheet tries to find the given file in the set of loaded sheets.
// If it fails, it will panic unhelpfully. Todo: fix this
// If it succeeds, it will return the sheet (a 2d array of sprites)
func GetSheet(fileName string) [][]*Sprite {
	sprites := make([][]*Sprite, 0)
	dlog.Verb(loadedSheets, fileName, loadedSheets[fileName])

	sheet, _ := LoadSheet(dir, fileName, 0, 0, 0)
	for x, row := range *sheet {
		sprites = append(sprites, make([]*Sprite, 0))
		for y := range row {
			sprites[x] = append(sprites[x], sheet.SubSprite(x, y))
		}
	}

	return sprites
}

// LoadSheet loads a file in some directory with sheets of (w,h) sized sprites,
// where there is pad pixels of vertical/horizontal pad between each sprite
func LoadSheet(directory, fileName string, w, h, pad int) (*Sheet, error) {
	if _, ok := loadedImages[fileName]; !ok {
		dlog.Verb("Missing file in loaded images: ", fileName)
		loadedImages[fileName] = loadPNG(directory, fileName)
	}
	if sheetP, ok := loadedSheets[fileName]; ok {
		return sheetP, nil
	}
	dlog.Verb("Loading sheet: ", fileName)
	rgba := loadedImages[fileName]
	bounds := rgba.Bounds()

	sheetW := bounds.Max.X / w
	remainderW := bounds.Max.X % w
	sheetH := bounds.Max.Y / h
	remainderH := bounds.Max.Y % h

	var widthBuffers, heightBuffers int
	if pad != 0 {
		widthBuffers = remainderW / pad
		heightBuffers = remainderH / pad
	} else {
		widthBuffers = sheetW - 1
		heightBuffers = sheetH - 1
	}

	if sheetW < 1 || sheetH < 1 ||
		widthBuffers != sheetW-1 ||
		heightBuffers != sheetH-1 {
		dlog.Error("Bad dimensions given to load sheet")
		return nil, errors.New("Bad dimensions given to load sheet")
	}

	sheet := make(Sheet, sheetW)
	i := 0
	for x := 0; x < bounds.Max.X; x += (w + pad) {
		sheet[i] = make([]*image.RGBA, sheetH)
		j := 0
		for y := 0; y < bounds.Max.Y; y += (h + pad) {
			sheet[i][j] = subImage(rgba, x, y, w, h)
			j++
		}
		i++
	}

	dlog.Verb("Loaded sheet into map")
	loadedSheets[fileName] = &sheet

	return loadedSheets[fileName], nil
}

// LoadSheetAnimation loads a sheet and then calls LoadAnimation on that sheet
func LoadSheetAnimation(fileName string, w, h, pad int, fps float64, frames []int) (*Animation, error) {
	sheet, err := LoadSheet(dir, fileName, w, h, pad)
	if err != nil {
		return nil, err
	}
	return LoadAnimation(sheet, w, h, pad, fps, frames)
}

// LoadAnimation takes in a sheet with sheet dimensions, a frame rate and a list of frames where
// frames are in x,y pairs ([0,0,1,0,2,0] for (0,0) (1,0) (2,0)) and returns an animation from that
func LoadAnimation(sheet *Sheet, w, h, pad int, fps float64, frames []int) (*Animation, error) {
	animation, err := NewAnimation(sheet, fps, frames)
	if err != nil {
		return nil, err
	}
	return animation, nil
}

func subImage(rgba *image.RGBA, x, y, w, h int) *image.RGBA {
	out := image.NewRGBA(image.Rect(0, 0, w, h))
	for i := 0; i < w; i++ {
		for j := 0; j < h; j++ {
			out.Set(i, j, rgba.At(x+i, y+j))
		}
	}
	return out
}

// BatchLoad loads subdirectories from the given base folder and imports all files,
// using alias rules to automatically determine the size of sprites and sheets in
// subfolders.
// A folder named 16x8 will have its images split into sheets where each sprite is
// 16x8, for example. 16 is a shorter way of writing 16x16.
// An alias.json file can be included that can indicate what dimensions named folders
// represent, so a "tiles": "32" field in the json would indicate that sprite sheets
// in the /tiles folder should be read as 32x32
func BatchLoad(baseFolder string) error {

	// dir2 := filepath.Join(dir, "textures")
	folders, _ := fileutil.ReadDir(baseFolder)

	aliasFile, err := fileutil.ReadFile(filepath.Join(baseFolder, "alias.json"))
	aliases := make(map[string]string)
	if err == nil {
		err = json.Unmarshal(aliasFile, &aliases)
		if err != nil {
			dlog.Error("Alias file unparseable: ", err)
		} else {
			dlog.Verb(aliases)
		}
	}

	for i, folder := range folders {

		dlog.Verb("folder ", i, folder.Name())
		if folder.IsDir() {

			var frameW int
			var frameH int

			if folder.Name() == "raw" {
				frameW = 0
				frameH = 0
			} else if result := regexpTwoNumbers.Find([]byte(folder.Name())); result != nil {
				vals := strings.Split(string(result), "x")
				dlog.Verb("Extracted dimensions: ", vals)
				frameW, _ = strconv.Atoi(vals[0])
				frameH, _ = strconv.Atoi(vals[1])
			} else if result := regexpSingleNumber.Find([]byte(folder.Name())); result != nil {
				val, _ := strconv.Atoi(string(result))
				frameW = val
				frameH = val
			} else {
				if aliased, ok := aliases[folder.Name()]; ok {
					if result := regexpTwoNumbers.Find([]byte(aliased)); result != nil {
						vals := strings.Split(string(result), "x")
						dlog.Verb("Extracted dimensions: ", vals)
						frameW, _ = strconv.Atoi(vals[0])
						frameH, _ = strconv.Atoi(vals[1])
					} else if result := regexpSingleNumber.Find([]byte(aliased)); result != nil {
						val, _ := strconv.Atoi(string(result))
						frameW = val
						frameH = val
					} else {
						return errors.New("Alias value not parseable as a frame width and height pair")
					}
				} else {
					return errors.New("Alias name not found in alias file")
				}
			}

			files, _ := fileutil.ReadDir(filepath.Join(baseFolder, folder.Name()))
			for _, file := range files {
				if !file.IsDir() {
					n := file.Name()
					switch n[len(n)-4:] {
					case ".png":
						dlog.Verb("loading file ", n)
						buff := loadPNG(baseFolder, filepath.Join(folder.Name(), n))
						w := buff.Bounds().Max.X
						h := buff.Bounds().Max.Y

						dlog.Verb("buffer: ", w, h, " frame: ", frameW, frameH)

						if frameW == 0 || frameH == 0 {
							continue
						} else if w < frameW || h < frameH {
							dlog.Error("File ", n, " in folder", folder.Name(), " is too small for these folder dimensions")
							return errors.New("File in folder is too small for these folder dimensions")

							// Load this as a sheet if it is greater
							// than the folder size's frame size
						} else if w != frameW || h != frameH {
							dlog.Verb("Loading as sprite sheet")
							_, err = LoadSheet(baseFolder, filepath.Join(folder.Name(), n), frameW, frameH, defaultPad)
							if err != nil {
								dlog.Error(err)
							}
						}
					default:
						dlog.Error("Unsupported file ending for batchLoad: ", n)
					}
				}
			}
		} else {
			dlog.Verb("Not Folder")
		}

	}
	return nil
}
<commit_msg>Adds jpeg and gif support<commit_after>package render

import (
	"encoding/json"
	"errors"
	"image"
	"image/color"
	"image/gif"
	"image/jpeg"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"sync"

	"github.com/oakmound/oak/dlog"
	"github.com/oakmound/oak/fileutil"
)

var (
	regexpSingleNumber, _ = regexp.Compile(`^\d+$`)
	regexpTwoNumbers, _   = regexp.Compile(`^\d+x\d+$`)
)

var (
	// Form ...main/core.go/assets/images,
	// the image directory.
	wd, _ = os.Getwd()
	dir   = filepath.Join(
		wd,
		"assets",
		"images")
	loadedImages = make(map[string]*image.RGBA)
	loadedSheets = make(map[string]*Sheet)
	// move to some batch load settings
	defaultPad = 0
	loadLock   = sync.Mutex{}
)

func loadImage(directory, fileName string) *image.RGBA {

	loadLock.Lock()
	if _, ok := loadedImages[fileName]; !ok {
		imgFile, err := fileutil.Open(filepath.Join(directory, fileName))
		if err != nil {
			log.Fatal(err)
		}
		defer func() {
			err = imgFile.Close()
			if err != nil {
				dlog.Error(err)
			}
		}()

		ext := fileName[len(fileName)-4:]
		var img image.Image
		switch ext {
		case ".png":
			img, err = png.Decode(imgFile)
		case ".gif":
			img, err = gif.Decode(imgFile)
		case "jpeg":
			img, err = jpeg.Decode(imgFile)
		}
		if err != nil {
			log.Fatal(err)
		}

		bounds := img.Bounds()
		rgba := image.NewRGBA(bounds)
		for x := 0; x < bounds.Max.X; x++ {
			for y := 0; y < bounds.Max.Y; y++ {
				rgba.Set(x, y, color.RGBAModel.Convert(img.At(x, y)))
			}
		}

		loadedImages[fileName] = rgba

		dlog.Verb("Loaded filename: ", fileName)
	}
	r := loadedImages[fileName]
	loadLock.Unlock()
	return r
}

// LoadSprite loads the input fileName into a Sprite
func LoadSprite(fileName string) *Sprite {
	return NewSprite(0, 0, loadImage(dir, fileName))
}

// GetSheet tries to find the given file in the set of loaded sheets.
// If it fails, it will panic unhelpfully. Todo: fix this
// If it succeeds, it will return the sheet (a 2d array of sprites)
func GetSheet(fileName string) [][]*Sprite {
	sprites := make([][]*Sprite, 0)
	dlog.Verb(loadedSheets, fileName, loadedSheets[fileName])

	sheet, _ := LoadSheet(dir, fileName, 0, 0, 0)
	for x, row := range *sheet {
		sprites = append(sprites, make([]*Sprite, 0))
		for y := range row {
			sprites[x] = append(sprites[x], sheet.SubSprite(x, y))
		}
	}

	return sprites
}

// LoadSheet loads a file in some directory with sheets of (w,h) sized sprites,
// where there is pad pixels of vertical/horizontal pad between each sprite
func LoadSheet(directory, fileName string, w, h, pad int) (*Sheet, error) {
	if _, ok := loadedImages[fileName]; !ok {
		dlog.Verb("Missing file in loaded images: ", fileName)
		loadedImages[fileName] = loadImage(directory, fileName)
	}
	if sheetP, ok := loadedSheets[fileName]; ok {
		return sheetP, nil
	}
	dlog.Verb("Loading sheet: ", fileName)
	rgba := loadedImages[fileName]
	bounds := rgba.Bounds()

	sheetW := bounds.Max.X / w
	remainderW := bounds.Max.X % w
	sheetH := bounds.Max.Y / h
	remainderH := bounds.Max.Y % h

	var widthBuffers, heightBuffers int
	if pad != 0 {
		widthBuffers = remainderW / pad
		heightBuffers = remainderH / pad
	} else {
		widthBuffers = sheetW - 1
		heightBuffers = sheetH - 1
	}

	if sheetW < 1 || sheetH < 1 ||
		widthBuffers != sheetW-1 ||
		heightBuffers != sheetH-1 {
		dlog.Error("Bad dimensions given to load sheet")
		return nil, errors.New("Bad dimensions given to load sheet")
	}

	sheet := make(Sheet, sheetW)
	i := 0
	for x := 0; x < bounds.Max.X; x += (w + pad) {
		sheet[i] = make([]*image.RGBA, sheetH)
		j := 0
		for y := 0; y < bounds.Max.Y; y += (h + pad) {
			sheet[i][j] = subImage(rgba, x, y, w, h)
			j++
		}
		i++
	}

	dlog.Verb("Loaded sheet into map")
	loadedSheets[fileName] = &sheet

	return loadedSheets[fileName], nil
}

// LoadSheetAnimation loads a sheet and then calls LoadAnimation on that sheet
func LoadSheetAnimation(fileName string, w, h, pad int, fps float64, frames []int) (*Animation, error) {
	sheet, err := LoadSheet(dir, fileName, w, h, pad)
	if err != nil {
		return nil, err
	}
	return LoadAnimation(sheet, w, h, pad, fps, frames)
}

// LoadAnimation takes in a sheet with sheet dimensions, a frame rate and a list of frames where
// frames are in x,y pairs ([0,0,1,0,2,0] for (0,0) (1,0) (2,0)) and returns an animation from that
func LoadAnimation(sheet *Sheet, w, h, pad int, fps float64, frames []int) (*Animation, error) {
	animation, err := NewAnimation(sheet, fps, frames)
	if err != nil {
		return nil, err
	}
	return animation, nil
}

func subImage(rgba *image.RGBA, x, y, w, h int) *image.RGBA {
	out := image.NewRGBA(image.Rect(0, 0, w, h))
	for i := 0; i < w; i++ {
		for j := 0; j < h; j++ {
			out.Set(i, j, rgba.At(x+i, y+j))
		}
	}
	return out
}

// BatchLoad loads subdirectories from the given base folder and imports all files,
// using alias rules to automatically determine the size of sprites and sheets in
// subfolders.
// A folder named 16x8 will have its images split into sheets where each sprite is
// 16x8, for example. 16 is a shorter way of writing 16x16.
// An alias.json file can be included that can indicate what dimensions named folders
// represent, so a "tiles": "32" field in the json would indicate that sprite sheets
// in the /tiles folder should be read as 32x32
func BatchLoad(baseFolder string) error {

	// dir2 := filepath.Join(dir, "textures")
	folders, _ := fileutil.ReadDir(baseFolder)

	aliasFile, err := fileutil.ReadFile(filepath.Join(baseFolder, "alias.json"))
	aliases := make(map[string]string)
	if err == nil {
		err = json.Unmarshal(aliasFile, &aliases)
		if err != nil {
			dlog.Error("Alias file unparseable: ", err)
		}
	}

	for i, folder := range folders {

		dlog.Verb("folder ", i, folder.Name())
		if folder.IsDir() {

			var frameW int
			var frameH int

			if folder.Name() == "raw" {
				frameW = 0
				frameH = 0
			} else if result := regexpTwoNumbers.Find([]byte(folder.Name())); result != nil {
				vals := strings.Split(string(result), "x")
				dlog.Verb("Extracted dimensions: ", vals)
				frameW, _ = strconv.Atoi(vals[0])
				frameH, _ = strconv.Atoi(vals[1])
			} else if result := regexpSingleNumber.Find([]byte(folder.Name())); result != nil {
				val, _ := strconv.Atoi(string(result))
				frameW = val
				frameH = val
			} else {
				if aliased, ok := aliases[folder.Name()]; ok {
					if result := regexpTwoNumbers.Find([]byte(aliased)); result != nil {
						vals := strings.Split(string(result), "x")
						dlog.Verb("Extracted dimensions: ", vals)
						frameW, _ = strconv.Atoi(vals[0])
						frameH, _ = strconv.Atoi(vals[1])
					} else if result := regexpSingleNumber.Find([]byte(aliased)); result != nil {
						val, _ := strconv.Atoi(string(result))
						frameW = val
						frameH = val
					} else {
						return errors.New("Alias value not parseable as a frame width and height pair")
					}
				} else {
					return errors.New("Alias name not found in alias file")
				}
			}

			files, _ := fileutil.ReadDir(filepath.Join(baseFolder, folder.Name()))
			for _, file := range files {
				if !file.IsDir() {
					n := file.Name()
					switch n[len(n)-4:] {
					case ".png":
						dlog.Verb("loading file ", n)
						buff := loadImage(baseFolder, filepath.Join(folder.Name(), n))
						w := buff.Bounds().Max.X
						h := buff.Bounds().Max.Y

						dlog.Verb("buffer: ", w, h, " frame: ", frameW, frameH)

						if frameW == 0 || frameH == 0 {
							continue
						} else if w < frameW || h < frameH {
							dlog.Error("File ", n, " in folder", folder.Name(), " is too small for these folder dimensions")
							return errors.New("File in folder is too small for these folder dimensions")

							// Load this as a sheet if it is greater
							// than the folder size's frame size
						} else if w != frameW || h != frameH {
							dlog.Verb("Loading as sprite sheet")
							_, err = LoadSheet(baseFolder, filepath.Join(folder.Name(), n), frameW, frameH, defaultPad)
							if err != nil {
								dlog.Error(err)
							}
						}
					default:
						dlog.Error("Unsupported file ending for batchLoad: ", n)
					}
				}
			}
		} else {
			dlog.Verb("Not Folder")
		}

	}
	return nil
}
<|endoftext|>


<commit_before>package remoting

import (
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	"github.com/rogeralsing/gam/actor"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

var _ actor.ActorRef = &RemoteActorRef{}

type server struct{}

func (s *server) Receive(stream Remoting_ReceiveServer) error {
	for {
		envelope, err := stream.Recv()
		if err != nil {
			return err
		}
		pid := envelope.Target
		message := UnpackMessage(envelope)
		pid.Tell(message)
	}
}

func remoteHandler(pid *actor.PID) (actor.ActorRef, bool) {
	ref := NewRemoteActorRef(pid)
	return ref, true
}

var endpointManagerPID *actor.PID

func StartServer(host string) {
	actor.ProcessRegistry.RegisterHostResolver(remoteHandler)
	actor.ProcessRegistry.Host = host

	endpointManagerPID = actor.SpawnTemplate(&EndpointManager{})

	lis, err := net.Listen("tcp", host)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	RegisterRemotingServer(s, &server{})
	log.Printf("Starting GAM server on %v.", host)
	go s.Serve(lis)
}

type RemoteActorRef struct {
	pid *actor.PID
}

func NewRemoteActorRef(pid *actor.PID) actor.ActorRef {
	return &RemoteActorRef{
		pid: pid,
	}
}

func (ref *RemoteActorRef) Tell(message interface{}) {
	switch msg := message.(type) {
	case proto.Message:
		envelope, _ := PackMessage(msg, ref.pid)
		endpointManagerPID.Tell(envelope)
	default:
		log.Printf("failed, trying to send non Proto %v message to %v", msg, ref.pid)
	}
}

func (ref *RemoteActorRef) SendSystemMessage(message actor.SystemMessage) {

}

func (ref *RemoteActorRef) Stop() {

}

func sendMessage(message proto.Message, target *actor.PID) {

}

type EndpointManager struct {
	connections map[string]*actor.PID
}

func (state *EndpointManager) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case actor.Started:
		state.connections = make(map[string]*actor.PID)
		log.Println("Started EndpointManager")
	case *MessageEnvelope:
		pid, ok := state.connections[msg.Target.Host]
		if !ok {
			pid = actor.SpawnTemplate(&EndpointWriter{host: msg.Target.Host})
			state.connections[msg.Target.Host] = pid
		}
		pid.Tell(msg)
	}
}

type EndpointWriter struct {
	host   string
	conn   *grpc.ClientConn
	stream Remoting_ReceiveClient
}

func (state *EndpointWriter) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case actor.Started:
		log.Println("Started EndpointWriter for host", state.host)
		log.Println("Connecting to host", state.host)
		conn, err := grpc.Dial(state.host, grpc.WithInsecure())
		if err != nil {
			log.Fatalf("did not connect: %v", err)
		}
		log.Println("Connected to host", state.host)
		state.conn = conn
		c := NewRemotingClient(conn)
		log.Println("Getting stream from host", state.host)
		stream, err := c.Receive(context.Background())
		log.Println("Got stream from host", state.host)
		state.stream = stream
	case actor.Stopped:
		state.conn.Close()
	case actor.Restarting:
		state.conn.Close()
	case *MessageEnvelope:
		err := state.stream.Send(msg)
		if err != nil {
			ctx.Stash()
			log.Println("Failed to send to host", state.host)
			panic("restart")
		}
	}
}
<commit_msg>cleanup<commit_after>package remoting

import (
	"log"
	"net"

	"github.com/golang/protobuf/proto"
	"github.com/rogeralsing/gam/actor"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
)

var _ actor.ActorRef = &RemoteActorRef{}

type server struct{}

func (s *server) Receive(stream Remoting_ReceiveServer) error {
	for {
		envelope, err := stream.Recv()
		if err != nil {
			return err
		}
		pid := envelope.Target
		message := UnpackMessage(envelope)
		pid.Tell(message)
	}
}

func remoteHandler(pid *actor.PID) (actor.ActorRef, bool) {
	ref := NewRemoteActorRef(pid)
	return ref, true
}

var endpointManagerPID *actor.PID

func StartServer(host string) {
	actor.ProcessRegistry.RegisterHostResolver(remoteHandler)
	actor.ProcessRegistry.Host = host

	endpointManagerPID = actor.SpawnTemplate(&EndpointManager{})

	lis, err := net.Listen("tcp", host)
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}
	s := grpc.NewServer()
	RegisterRemotingServer(s, &server{})
	log.Printf("Starting GAM server on %v.", host)
	go s.Serve(lis)
}

type RemoteActorRef struct {
	pid *actor.PID
}

func NewRemoteActorRef(pid *actor.PID) actor.ActorRef {
	return &RemoteActorRef{
		pid: pid,
	}
}

func (ref *RemoteActorRef) Tell(message interface{}) {
	switch msg := message.(type) {
	case proto.Message:
		envelope, _ := PackMessage(msg, ref.pid)
		endpointManagerPID.Tell(envelope)
	default:
		log.Printf("failed, trying to send non Proto %v message to %v", msg, ref.pid)
	}
}

func (ref *RemoteActorRef) SendSystemMessage(message actor.SystemMessage) {}

func (ref *RemoteActorRef) Stop() {}

type EndpointManager struct {
	connections map[string]*actor.PID
}

func (state *EndpointManager) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case actor.Started:
		state.connections = make(map[string]*actor.PID)
		log.Println("Started EndpointManager")
	case *MessageEnvelope:
		pid, ok := state.connections[msg.Target.Host]
		if !ok {
			pid = actor.SpawnTemplate(&EndpointWriter{host: msg.Target.Host})
			state.connections[msg.Target.Host] = pid
		}
		pid.Tell(msg)
	}
}

type EndpointWriter struct {
	host   string
	conn   *grpc.ClientConn
	stream Remoting_ReceiveClient
}

func (state *EndpointWriter) Receive(ctx actor.Context) {
	switch msg := ctx.Message().(type) {
	case actor.Started:
		log.Println("Started EndpointWriter for host", state.host)
		log.Println("Connecting to host", state.host)
		conn, err := grpc.Dial(state.host, grpc.WithInsecure())
		if err != nil {
			log.Fatalf("did not connect: %v", err)
		}
		log.Println("Connected to host", state.host)
		state.conn = conn
		c := NewRemotingClient(conn)
		log.Println("Getting stream from host", state.host)
		stream, err := c.Receive(context.Background())
		log.Println("Got stream from host", state.host)
		state.stream = stream
	case actor.Stopped:
		state.conn.Close()
	case actor.Restarting:
		state.conn.Close()
	case *MessageEnvelope:
		err := state.stream.Send(msg)
		if err != nil {
			ctx.Stash()
			log.Println("Failed to send to host", state.host)
			panic("restart")
		}
	}
}
<|endoftext|>