<commit_before>package docker

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"

	log "github.com/sirupsen/logrus"

	dockertypes "github.com/docker/docker/api/types"
	dockercontainer "github.com/docker/docker/api/types/container"
	dockernetwork "github.com/docker/docker/api/types/network"
	dockerslice "github.com/docker/docker/api/types/strslice"

	"encoding/json"

	enginetypes "github.com/projecteru2/core/engine/types"
	coretypes "github.com/projecteru2/core/types"
)

const (
	minMemory     = units.MiB * 4
	restartAlways = "always"
	root          = "root"
)

type rawArgs struct {
	PidMode    dockercontainer.PidMode `json:"pid_mod"`
	StorageOpt map[string]string       `json:"storage_opt"`
	CapAdd     []string                `json:"cap_add"`
	CapDrop    []string                `json:"cap_drop"`
}

// VirtualizationCreate create a container
func (e *Engine) VirtualizationCreate(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions) (*enginetypes.VirtualizationCreated, error) {
	r := &enginetypes.VirtualizationCreated{}
	// memory should more than 4MiB
	if opts.Memory < minMemory {
		return r, coretypes.ErrBadMemory
	}
	// set default log driver if lambda
	if opts.Lambda {
		opts.LogType = "json-file"
	}
	// set restart always
	restartRetryCount := 3
	if opts.RestartPolicy == restartAlways {
		restartRetryCount = 0
	}
	// network mode 和 networks 互斥
	// 没有 networks 的时候用 networkmode 的值
	// 有 networks 的时候一律用用 networks 的值作为 mode
	networkMode := dockercontainer.NetworkMode(opts.Network)
	for name := range opts.Networks {
		networkMode = dockercontainer.NetworkMode(name)
		opts.Networks[name] = ""
		break
	}
	// 如果没有 network 用默认值替换
	if networkMode == "" {
		networkMode = dockercontainer.NetworkMode(e.config.Docker.NetworkMode)
	}
	// log config
	if opts.Debug {
		opts.LogType = e.config.Docker.Log.Type
		opts.LogConfig = e.config.Docker.Log.Config
		if opts.LogConfig == nil {
			opts.LogConfig = map[string]string{
				"tag": fmt.Sprintf("%s {{.ID}}", opts.Name),
			}
		}
	}
	// add node IP
	hostIP := GetIP(e.client.DaemonHost())
	opts.Env = append(opts.Env, fmt.Sprintf("ERU_NODE_IP=%s", hostIP))
	// 如果有给dns就优先用给定的dns.
	// 没有给出dns的时候, 如果设定是用宿主机IP作为dns, 就会把宿主机IP设置过去.
	// 其他情况就是默认值.
	// 哦对, networkMode如果是host也不给dns.
	if len(opts.DNS) == 0 && e.config.Docker.UseLocalDNS && hostIP != "" {
		opts.DNS = []string{hostIP}
	}
	// mount paths
	binds, volumes := makeMountPaths(opts)
	log.Debugf("[VirtualizationCreate] App %s will bind %v", opts.Name, binds)

	config := &dockercontainer.Config{
		Env:             opts.Env,
		Cmd:             dockerslice.StrSlice(opts.Cmd),
		User:            opts.User,
		Image:           opts.Image,
		Volumes:         volumes,
		WorkingDir:      opts.WorkingDir,
		NetworkDisabled: networkMode == "",
		Labels:          opts.Labels,
		OpenStdin:       opts.Stdin,
		Tty:             opts.Stdin,
	}

	resource := makeResourceSetting(opts.Quota, opts.Memory, opts.CPU, opts.NUMANode, opts.SoftLimit)
	// set ulimits
	resource.Ulimits = []*units.Ulimit{
		&units.Ulimit{Name: "nofile", Soft: 65535, Hard: 65535},
	}
	if networkMode.IsHost() {
		opts.DNS = []string{}
		opts.Sysctl = map[string]string{}
	}
	rArgs := &rawArgs{StorageOpt: map[string]string{}}
	if len(opts.RawArgs) > 0 {
		if err := json.Unmarshal(opts.RawArgs, rArgs); err != nil {
			return r, err
		}
	}
	if opts.Storage > 0 {
		rArgs.StorageOpt["size"] = fmt.Sprintf("%v", opts.Storage)
	}
	// 如果有指定用户，用指定用户
	// 没有指定用户，用镜像自己的
	// CapAdd and Privileged
	capAdds := dockerslice.StrSlice(rArgs.CapAdd)
	if opts.Privileged {
		opts.User = root
		capAdds = append(capAdds, "SYS_ADMIN")
	}
	hostConfig := &dockercontainer.HostConfig{
		Binds: binds,
		DNS:   opts.DNS,
		LogConfig: dockercontainer.LogConfig{
			Type:   opts.LogType,
			Config: opts.LogConfig,
		},
		NetworkMode: networkMode,
		RestartPolicy: dockercontainer.RestartPolicy{
			Name:              opts.RestartPolicy,
			MaximumRetryCount: restartRetryCount,
		},
		CapAdd:     capAdds,
		ExtraHosts: opts.Hosts,
		Privileged: opts.Privileged,
		Resources:  resource,
		Sysctls:    opts.Sysctl,
		PidMode:    rArgs.PidMode,
		StorageOpt: rArgs.StorageOpt,
	}

	if hostConfig.NetworkMode.IsBridge() {
		portMapping := nat.PortMap{}
		exposePorts := nat.PortSet{}
		for _, p := range opts.Publish {
			port, err := nat.NewPort("tcp", p)
			if err != nil {
				return r, err
			}
			exposePorts[port] = struct{}{}
			portMapping[port] = []nat.PortBinding{}
			portMapping[port] = append(portMapping[port], nat.PortBinding{HostPort: p})
		}
		hostConfig.PortBindings = portMapping
		config.ExposedPorts = exposePorts
	}

	networkConfig := &dockernetwork.NetworkingConfig{
		EndpointsConfig: map[string]*dockernetwork.EndpointSettings{},
	}
	for networkID, ipv4 := range opts.Networks {
		endpointSetting, err := e.makeIPV4EndpointSetting(ipv4)
		if err != nil {
			return r, err
		}
		ipForShow := ipv4
		if ipForShow == "" {
			ipForShow = "[AutoAlloc]"
		}
		networkConfig.EndpointsConfig[networkID] = endpointSetting
		log.Infof("[ConnectToNetwork] Connect to %v with IP %v", networkID, ipForShow)
	}

	containerCreated, err := e.client.ContainerCreate(ctx, config, hostConfig, networkConfig, opts.Name)
	r.Name = opts.Name
	r.ID = containerCreated.ID
	return r, err
}

// VirtualizationCopyTo copy things to virtualization
func (e *Engine) VirtualizationCopyTo(ctx context.Context, ID, path string, content io.Reader, AllowOverwriteDirWithFile, CopyUIDGID bool) error {
	return e.client.CopyToContainer(ctx, ID, path, content, dockertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: AllowOverwriteDirWithFile, CopyUIDGID: CopyUIDGID})
}

// VirtualizationStart start virtualization
func (e *Engine) VirtualizationStart(ctx context.Context, ID string) error {
	return e.client.ContainerStart(ctx, ID, dockertypes.ContainerStartOptions{})
}

// VirtualizationStop stop virtualization
func (e *Engine) VirtualizationStop(ctx context.Context, ID string) error {
	return e.client.ContainerStop(ctx, ID, nil)
}

// VirtualizationRemove remove virtualization
func (e *Engine) VirtualizationRemove(ctx context.Context, ID string, removeVolumes, force bool) error {
	return e.client.ContainerRemove(ctx, ID, dockertypes.ContainerRemoveOptions{RemoveVolumes: removeVolumes, Force: force})
}

// VirtualizationInspect get virtualization info
func (e *Engine) VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error) {
	if e.client == nil {
		return nil, coretypes.ErrNilEngine
	}

	containerJSON, err := e.client.ContainerInspect(ctx, ID)
	r := &enginetypes.VirtualizationInfo{}
	if err != nil {
		return r, err
	}
	r.ID = containerJSON.ID
	r.User = containerJSON.Config.User
	r.Image = containerJSON.Config.Image
	r.Env = containerJSON.Config.Env
	r.Labels = containerJSON.Config.Labels
	r.Running = containerJSON.State.Running
	r.Networks = map[string]string{}
	for networkName, networkSetting := range containerJSON.NetworkSettings.Networks {
		ip := networkSetting.IPAddress
		if dockercontainer.NetworkMode(networkName).IsHost() {
			ip = GetIP(e.client.DaemonHost())
		}
		r.Networks[networkName] = ip
	}
	return r, nil
}

// VirtualizationLogs show virtualization logs
func (e *Engine) VirtualizationLogs(ctx context.Context, ID string, follow, stdout, stderr bool) (io.ReadCloser, error) {
	logsOpts := dockertypes.ContainerLogsOptions{Follow: follow, ShowStdout: stdout, ShowStderr: stderr}
	resp, err := e.client.ContainerLogs(ctx, ID, logsOpts)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(mergeStream(resp)), nil
}

// VirtualizationAttach attach to a virtualization
func (e *Engine) VirtualizationAttach(ctx context.Context, ID string, stream, stdin bool) (io.ReadCloser, io.WriteCloser, error) {
	opts := dockertypes.ContainerAttachOptions{
		Stream: stream,
		Stdin:  stdin,
		Logs:   true,
		Stdout: true,
		Stderr: true,
	}
	resp, err := e.client.ContainerAttach(ctx, ID, opts)
	if err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(resp.Reader), resp.Conn, nil
}

// VirtualizationResize resizes remote terminal
func (e *Engine) VirtualizationResize(ctx context.Context, containerID string, height, width uint) (err error) {
	opts := dockertypes.ResizeOptions{
		Height: height,
		Width:  width,
	}

	return e.client.ContainerResize(ctx, containerID, opts)
}

// VirtualizationWait wait virtualization exit
func (e *Engine) VirtualizationWait(ctx context.Context, ID, state string) (*enginetypes.VirtualizationWaitResult, error) {
	waitBody, errorCh := e.client.ContainerWait(ctx, ID, dockercontainer.WaitConditionNotRunning)
	r := &enginetypes.VirtualizationWaitResult{}
	select {
	case b := <-waitBody:
		if b.Error != nil {
			r.Message = b.Error.Message
		}
		r.Code = b.StatusCode
		return r, nil
	case err := <-errorCh:
		r.Message = err.Error()
		r.Code = -1
		return r, err
	}
}

// VirtualizationUpdateResource update virtualization resource
func (e *Engine) VirtualizationUpdateResource(ctx context.Context, ID string, opts *enginetypes.VirtualizationResource) error {
	if opts.Memory < minMemory {
		return coretypes.ErrBadMemory
	}
	newResource := makeResourceSetting(opts.Quota, opts.Memory, opts.CPU, opts.NUMANode, opts.SoftLimit)
	updateConfig := dockercontainer.UpdateConfig{Resources: newResource}
	_, err := e.client.ContainerUpdate(ctx, ID, updateConfig)
	return err
}

// VirtualizationCopyFrom copy thing from a virtualization
func (e *Engine) VirtualizationCopyFrom(ctx context.Context, ID, path string) (io.ReadCloser, string, error) {
	resp, stat, err := e.client.CopyFromContainer(ctx, ID, path)
	if err != nil {
		return nil, "", err
	}
	return resp, stat.Name, err
}
<commit_msg>fix networkConfig for container replace (#142)<commit_after>package docker

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/docker/go-connections/nat"
	"github.com/docker/go-units"

	log "github.com/sirupsen/logrus"

	dockertypes "github.com/docker/docker/api/types"
	dockercontainer "github.com/docker/docker/api/types/container"
	dockernetwork "github.com/docker/docker/api/types/network"
	dockerslice "github.com/docker/docker/api/types/strslice"

	"encoding/json"

	enginetypes "github.com/projecteru2/core/engine/types"
	coretypes "github.com/projecteru2/core/types"
)

const (
	minMemory     = units.MiB * 4
	restartAlways = "always"
	root          = "root"
)

type rawArgs struct {
	PidMode    dockercontainer.PidMode `json:"pid_mod"`
	StorageOpt map[string]string       `json:"storage_opt"`
	CapAdd     []string                `json:"cap_add"`
	CapDrop    []string                `json:"cap_drop"`
}

// VirtualizationCreate create a container
func (e *Engine) VirtualizationCreate(ctx context.Context, opts *enginetypes.VirtualizationCreateOptions) (*enginetypes.VirtualizationCreated, error) {
	r := &enginetypes.VirtualizationCreated{}
	// memory should more than 4MiB
	if opts.Memory < minMemory {
		return r, coretypes.ErrBadMemory
	}
	// set default log driver if lambda
	if opts.Lambda {
		opts.LogType = "json-file"
	}
	// set restart always
	restartRetryCount := 3
	if opts.RestartPolicy == restartAlways {
		restartRetryCount = 0
	}
	// network mode 和 networks 互斥
	// 没有 networks 的时候用 networkmode 的值
	// 有 networks 的时候一律用用 networks 的值作为 mode
	networkMode := dockercontainer.NetworkMode(opts.Network)
	for name := range opts.Networks {
		networkMode = dockercontainer.NetworkMode(name)
		break
	}
	// 如果没有 network 用默认值替换
	if networkMode == "" {
		networkMode = dockercontainer.NetworkMode(e.config.Docker.NetworkMode)
	}
	// log config
	if opts.Debug {
		opts.LogType = e.config.Docker.Log.Type
		opts.LogConfig = e.config.Docker.Log.Config
		if opts.LogConfig == nil {
			opts.LogConfig = map[string]string{
				"tag": fmt.Sprintf("%s {{.ID}}", opts.Name),
			}
		}
	}
	// add node IP
	hostIP := GetIP(e.client.DaemonHost())
	opts.Env = append(opts.Env, fmt.Sprintf("ERU_NODE_IP=%s", hostIP))
	// 如果有给dns就优先用给定的dns.
	// 没有给出dns的时候, 如果设定是用宿主机IP作为dns, 就会把宿主机IP设置过去.
	// 其他情况就是默认值.
	// 哦对, networkMode如果是host也不给dns.
	if len(opts.DNS) == 0 && e.config.Docker.UseLocalDNS && hostIP != "" {
		opts.DNS = []string{hostIP}
	}
	// mount paths
	binds, volumes := makeMountPaths(opts)
	log.Debugf("[VirtualizationCreate] App %s will bind %v", opts.Name, binds)

	config := &dockercontainer.Config{
		Env:             opts.Env,
		Cmd:             dockerslice.StrSlice(opts.Cmd),
		User:            opts.User,
		Image:           opts.Image,
		Volumes:         volumes,
		WorkingDir:      opts.WorkingDir,
		NetworkDisabled: networkMode == "",
		Labels:          opts.Labels,
		OpenStdin:       opts.Stdin,
		Tty:             opts.Stdin,
	}

	resource := makeResourceSetting(opts.Quota, opts.Memory, opts.CPU, opts.NUMANode, opts.SoftLimit)
	// set ulimits
	resource.Ulimits = []*units.Ulimit{
		&units.Ulimit{Name: "nofile", Soft: 65535, Hard: 65535},
	}
	if networkMode.IsHost() {
		opts.DNS = []string{}
		opts.Sysctl = map[string]string{}
	}
	rArgs := &rawArgs{StorageOpt: map[string]string{}}
	if len(opts.RawArgs) > 0 {
		if err := json.Unmarshal(opts.RawArgs, rArgs); err != nil {
			return r, err
		}
	}
	if opts.Storage > 0 {
		rArgs.StorageOpt["size"] = fmt.Sprintf("%v", opts.Storage)
	}
	// 如果有指定用户，用指定用户
	// 没有指定用户，用镜像自己的
	// CapAdd and Privileged
	capAdds := dockerslice.StrSlice(rArgs.CapAdd)
	if opts.Privileged {
		opts.User = root
		capAdds = append(capAdds, "SYS_ADMIN")
	}
	hostConfig := &dockercontainer.HostConfig{
		Binds: binds,
		DNS:   opts.DNS,
		LogConfig: dockercontainer.LogConfig{
			Type:   opts.LogType,
			Config: opts.LogConfig,
		},
		NetworkMode: networkMode,
		RestartPolicy: dockercontainer.RestartPolicy{
			Name:              opts.RestartPolicy,
			MaximumRetryCount: restartRetryCount,
		},
		CapAdd:     capAdds,
		ExtraHosts: opts.Hosts,
		Privileged: opts.Privileged,
		Resources:  resource,
		Sysctls:    opts.Sysctl,
		PidMode:    rArgs.PidMode,
		StorageOpt: rArgs.StorageOpt,
	}

	if hostConfig.NetworkMode.IsBridge() {
		portMapping := nat.PortMap{}
		exposePorts := nat.PortSet{}
		for _, p := range opts.Publish {
			port, err := nat.NewPort("tcp", p)
			if err != nil {
				return r, err
			}
			exposePorts[port] = struct{}{}
			portMapping[port] = []nat.PortBinding{}
			portMapping[port] = append(portMapping[port], nat.PortBinding{HostPort: p})
		}
		hostConfig.PortBindings = portMapping
		config.ExposedPorts = exposePorts
	}

	networkConfig := &dockernetwork.NetworkingConfig{
		EndpointsConfig: map[string]*dockernetwork.EndpointSettings{},
	}
	for networkID, ipv4 := range opts.Networks {
		endpointSetting, err := e.makeIPV4EndpointSetting(ipv4)
		if err != nil {
			return r, err
		}
		ipForShow := ipv4
		if ipForShow == "" {
			ipForShow = "[AutoAlloc]"
		}
		networkConfig.EndpointsConfig[networkID] = endpointSetting
		log.Infof("[ConnectToNetwork] Connect to %v with IP %v", networkID, ipForShow)
	}

	containerCreated, err := e.client.ContainerCreate(ctx, config, hostConfig, networkConfig, opts.Name)
	r.Name = opts.Name
	r.ID = containerCreated.ID
	return r, err
}

// VirtualizationCopyTo copy things to virtualization
func (e *Engine) VirtualizationCopyTo(ctx context.Context, ID, path string, content io.Reader, AllowOverwriteDirWithFile, CopyUIDGID bool) error {
	return e.client.CopyToContainer(ctx, ID, path, content, dockertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: AllowOverwriteDirWithFile, CopyUIDGID: CopyUIDGID})
}

// VirtualizationStart start virtualization
func (e *Engine) VirtualizationStart(ctx context.Context, ID string) error {
	return e.client.ContainerStart(ctx, ID, dockertypes.ContainerStartOptions{})
}

// VirtualizationStop stop virtualization
func (e *Engine) VirtualizationStop(ctx context.Context, ID string) error {
	return e.client.ContainerStop(ctx, ID, nil)
}

// VirtualizationRemove remove virtualization
func (e *Engine) VirtualizationRemove(ctx context.Context, ID string, removeVolumes, force bool) error {
	return e.client.ContainerRemove(ctx, ID, dockertypes.ContainerRemoveOptions{RemoveVolumes: removeVolumes, Force: force})
}

// VirtualizationInspect get virtualization info
func (e *Engine) VirtualizationInspect(ctx context.Context, ID string) (*enginetypes.VirtualizationInfo, error) {
	if e.client == nil {
		return nil, coretypes.ErrNilEngine
	}

	containerJSON, err := e.client.ContainerInspect(ctx, ID)
	r := &enginetypes.VirtualizationInfo{}
	if err != nil {
		return r, err
	}
	r.ID = containerJSON.ID
	r.User = containerJSON.Config.User
	r.Image = containerJSON.Config.Image
	r.Env = containerJSON.Config.Env
	r.Labels = containerJSON.Config.Labels
	r.Running = containerJSON.State.Running
	r.Networks = map[string]string{}
	for networkName, networkSetting := range containerJSON.NetworkSettings.Networks {
		ip := networkSetting.IPAddress
		if dockercontainer.NetworkMode(networkName).IsHost() {
			ip = GetIP(e.client.DaemonHost())
		}
		r.Networks[networkName] = ip
	}
	return r, nil
}

// VirtualizationLogs show virtualization logs
func (e *Engine) VirtualizationLogs(ctx context.Context, ID string, follow, stdout, stderr bool) (io.ReadCloser, error) {
	logsOpts := dockertypes.ContainerLogsOptions{Follow: follow, ShowStdout: stdout, ShowStderr: stderr}
	resp, err := e.client.ContainerLogs(ctx, ID, logsOpts)
	if err != nil {
		return nil, err
	}
	return ioutil.NopCloser(mergeStream(resp)), nil
}

// VirtualizationAttach attach to a virtualization
func (e *Engine) VirtualizationAttach(ctx context.Context, ID string, stream, stdin bool) (io.ReadCloser, io.WriteCloser, error) {
	opts := dockertypes.ContainerAttachOptions{
		Stream: stream,
		Stdin:  stdin,
		Logs:   true,
		Stdout: true,
		Stderr: true,
	}
	resp, err := e.client.ContainerAttach(ctx, ID, opts)
	if err != nil {
		return nil, nil, err
	}
	return ioutil.NopCloser(resp.Reader), resp.Conn, nil
}

// VirtualizationResize resizes remote terminal
func (e *Engine) VirtualizationResize(ctx context.Context, containerID string, height, width uint) (err error) {
	opts := dockertypes.ResizeOptions{
		Height: height,
		Width:  width,
	}

	return e.client.ContainerResize(ctx, containerID, opts)
}

// VirtualizationWait wait virtualization exit
func (e *Engine) VirtualizationWait(ctx context.Context, ID, state string) (*enginetypes.VirtualizationWaitResult, error) {
	waitBody, errorCh := e.client.ContainerWait(ctx, ID, dockercontainer.WaitConditionNotRunning)
	r := &enginetypes.VirtualizationWaitResult{}
	select {
	case b := <-waitBody:
		if b.Error != nil {
			r.Message = b.Error.Message
		}
		r.Code = b.StatusCode
		return r, nil
	case err := <-errorCh:
		r.Message = err.Error()
		r.Code = -1
		return r, err
	}
}

// VirtualizationUpdateResource update virtualization resource
func (e *Engine) VirtualizationUpdateResource(ctx context.Context, ID string, opts *enginetypes.VirtualizationResource) error {
	if opts.Memory < minMemory {
		return coretypes.ErrBadMemory
	}
	newResource := makeResourceSetting(opts.Quota, opts.Memory, opts.CPU, opts.NUMANode, opts.SoftLimit)
	updateConfig := dockercontainer.UpdateConfig{Resources: newResource}
	_, err := e.client.ContainerUpdate(ctx, ID, updateConfig)
	return err
}

// VirtualizationCopyFrom copy thing from a virtualization
func (e *Engine) VirtualizationCopyFrom(ctx context.Context, ID, path string) (io.ReadCloser, string, error) {
	resp, stat, err := e.client.CopyFromContainer(ctx, ID, path)
	if err != nil {
		return nil, "", err
	}
	return resp, stat.Name, err
}
<|endoftext|>


<commit_before>// Package gdm provides an interface to the plex.tv GDM protocol for
// Plex player/server discovery.
package gdm

import "net"
import "fmt"
import "strings"
import "time"
import "strconv"

const gdmPlayerPort = 32412
const gdmServerPort = 32414
const serverWaitTime time.Duration = 2

// GetPlayers returns a list of Players.
func GetPlayers() ([]*GDMMessage, error) {
    return getter(gdmPlayerPort)
}

// GetPlayer returns a single Player matching the name supplied.
func GetPlayer(name string) (*GDMMessage, error) {
    gdms, err := getter(gdmPlayerPort)
    if err != nil {
        return nil, err
    }
    for _, gdm := range gdms {
        if (gdm.Props["Name"] == name) || (gdm.Address.IP.String() == name) {
            return gdm, nil
        }
    }
    return nil, fmt.Errorf("no player found named `%s`", name)
}

// GetServers returns a list of Servers.
func GetServers() ([]*GDMMessage, error) {
    return getter(gdmServerPort)
}

// GetServer returns a single Server matching the name supplied.
func GetServer(name string) (*GDMMessage, error) {
    gdms, err := getter(gdmServerPort)
    if err != nil {
        return nil, err
    }
    for _, gdm := range gdms {
        if (gdm.Props["Name"] == name) || (gdm.Address.IP.String() == name) {
            return gdm, nil
        }
    }
    return nil, fmt.Errorf("no player found named `%s`", name)
}

// WatchPlayers returns a *GDMWatcher instance containing a channel
// on which it pushes all Players that answer the regular broadcasts
func WatchPlayers(freq int) (*GDMWatcher, error) {
    gdms, err := watcher(gdmPlayerPort, freq)
    if err != nil {
        return nil, err
    }
    return gdms, nil
}

// WatchServers returns a *GDMWatcher instance containing a channel
// on which it pushes all Servers that answer the regular broadcasts
func WatchServers(freq int) (*GDMWatcher, error) {
    gdms, err := watcher(gdmServerPort, freq)
    if err != nil {
        return nil, err
    }
    return gdms, nil
}

// GDMMessage holds the information of one Player or Server
type GDMMessage struct {
    Address *net.UDPAddr
    Added bool
    Props map[string]string
}

// GDMWatcher is the structure containing the Watch channel.
type GDMWatcher struct {
    Watch chan *GDMMessage
    closer chan bool
}

type gdmBrowser struct {
    mc chan *GDMMessage
    ticker *time.Ticker
    conn *net.UDPConn
}

// Close sends all go routines associated a signal to exit.
func (w *GDMWatcher) Close() {
    w.closer <- true
}

func getter(port int) ([]*GDMMessage, error) {
    items := make([]*GDMMessage, 0)
    browser, err := setupBrowser(10)
    if err != nil { return nil, nil }
    browser.listen()
    browser.browse(port)

    timer := time.NewTimer(time.Second * serverWaitTime)
    done := false
    for !done {
        select {
        case <-timer.C:
            done = true
            browser.conn.Close()
        case msg := <- browser.mc:
            items = append(items, msg)
        }
    }
    return items, nil
}

func watcher(port int, freq int) (*GDMWatcher, error) {
    browser, err := setupBrowser(10)
    if err != nil {
        return nil, err
    }
    browser.listen()
    browser.browse(port)
    go func() {
        for {
            <-browser.ticker.C
            browser.browse(port)
        }
    }()

    watcher := &GDMWatcher{Watch: browser.mc, closer: make(chan bool)}
    browser.closer(watcher.closer)

    return watcher, nil
}

func setupBrowser(i int) (*gdmBrowser, error) {
    refresh := (time.Duration(i) * time.Second)
    // setup listener to broadcast stuff
    udpaddr, err := net.ResolveUDPAddr("udp4", ":0")
    if err != nil {
        return nil, err
    }
    conn, lerr := net.ListenUDP("udp4", udpaddr)
    if lerr != nil {
        return nil, lerr
    }
    return &gdmBrowser{conn: conn,
                       mc: make(chan *GDMMessage),
                       ticker: time.NewTicker(refresh)}, nil
}

func (b *gdmBrowser) listen() {
    go func() {
        buf := make([]byte, 1024)
        for {
            mlen, raddr, err := b.conn.ReadFromUDP(buf)
            if err != nil {
                if nerr, ok := err.(net.Error); !ok || !nerr.Temporary() {
                    // this connection has become useless
                    close(b.mc)
                    return
            }
                continue
            }
            msg := string(buf[0:mlen])
            if strings.HasPrefix(msg, "HTTP/1.0 200 OK") {
                gdmmsg := newGDMMessage(msg, raddr)
                b.mc <- gdmmsg
            }
        }
    }()
}

func (b *gdmBrowser) closer(s chan bool) {
    go func() {
        select {
        case <- s:
            b.conn.Close()
            return
        }
    }()
}

func (b *gdmBrowser) browse(port int) {
    addrs := getBroadcastAddrs()
    ports := strconv.Itoa(port)
    for _, a := range addrs {
        udpdest, err := net.ResolveUDPAddr("udp4", a+":"+ports)
       if err != nil {
            continue
        }
        buf := []byte("M-SEARCH * HTTP/1.1\r\n\r\n")
        _, werr := b.conn.WriteToUDP(buf, udpdest);
        if werr != nil {
            continue
        }
    }
}

func newGDMMessage(data string, addr *net.UDPAddr) (*GDMMessage) {
    msglines := strings.Split(data, "\r\n")
    gdm := GDMMessage{Address: addr, Added: true, Props: make(map[string]string)}
    for _, m := range msglines {
        if strings.Contains(m, ":") {
            kv := strings.Split(m, ":")
            if len(kv) == 2 {
                gdm.Props[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
            }
        }
    }
    return &gdm
}

func getBroadcastAddrs() (baddr []string) {
    ifaces, _ := net.Interfaces()
    for _, iface := range ifaces {
        // only send on interfaces that are up and can do broadcast
        upandbcast := net.FlagBroadcast | net.FlagUp
        if iface.Flags & upandbcast != upandbcast {
            continue
        }
        addrs, _ := iface.Addrs()
        if len(addrs) == 0 {
            continue
        }
        for _, addr := range addrs {
            var bcast []string
            ip, ipn, err := net.ParseCIDR(addr.String())
            if err != nil {
                continue
            }
            // we only handle ipv4 addresses for now
            ipv4 := ip.To4()
            if ipv4 == nil {
                continue
            }
            for i, m := range ipn.Mask {
                if m == 0 {
                    bcast = append(bcast, "255")
                } else {
                    bcast = append(bcast, fmt.Sprintf("%d", ipv4[i]))
                }
            }
            baddr = append(baddr, strings.Join(bcast, "."))
        }
    }
    return baddr
}
<commit_msg>interval fix<commit_after>// Package gdm provides an interface to the plex.tv GDM protocol for
// Plex player/server discovery.
package gdm

import "net"
import "fmt"
import "strings"
import "time"
import "strconv"

const gdmPlayerPort = 32412
const gdmServerPort = 32414
const serverWaitTime time.Duration = 2

// GetPlayers returns a list of Players.
func GetPlayers() ([]*GDMMessage, error) {
	return getter(gdmPlayerPort)
}

// GetPlayer returns a single Player matching the name supplied.
func GetPlayer(name string) (*GDMMessage, error) {
	gdms, err := getter(gdmPlayerPort)
	if err != nil {
		return nil, err
	}
	for _, gdm := range gdms {
		if (gdm.Props["Name"] == name) || (gdm.Address.IP.String() == name) {
			return gdm, nil
		}
	}
	return nil, fmt.Errorf("no player found named `%s`", name)
}

// GetServers returns a list of Servers.
func GetServers() ([]*GDMMessage, error) {
	return getter(gdmServerPort)
}

// GetServer returns a single Server matching the name supplied.
func GetServer(name string) (*GDMMessage, error) {
	gdms, err := getter(gdmServerPort)
	if err != nil {
		return nil, err
	}
	for _, gdm := range gdms {
		if (gdm.Props["Name"] == name) || (gdm.Address.IP.String() == name) {
			return gdm, nil
		}
	}
	return nil, fmt.Errorf("no player found named `%s`", name)
}

// WatchPlayers returns a *GDMWatcher instance containing a channel
// on which it pushes all Players that answer the regular broadcasts
func WatchPlayers(freq int) (*GDMWatcher, error) {
	gdms, err := watcher(gdmPlayerPort, freq)
	if err != nil {
		return nil, err
	}
	return gdms, nil
}

// WatchServers returns a *GDMWatcher instance containing a channel
// on which it pushes all Servers that answer the regular broadcasts
func WatchServers(freq int) (*GDMWatcher, error) {
	gdms, err := watcher(gdmServerPort, freq)
	if err != nil {
		return nil, err
	}
	return gdms, nil
}

// GDMMessage holds the information of one Player or Server
type GDMMessage struct {
	Address *net.UDPAddr
	Added   bool
	Props   map[string]string
}

// GDMWatcher is the structure containing the Watch channel.
type GDMWatcher struct {
	Watch  chan *GDMMessage
	closer chan bool
}

type gdmBrowser struct {
	mc     chan *GDMMessage
	ticker *time.Ticker
	conn   *net.UDPConn
}

// Close sends all go routines associated a signal to exit.
func (w *GDMWatcher) Close() {
	w.closer <- true
}

func getter(port int) ([]*GDMMessage, error) {
	items := make([]*GDMMessage, 0)
	browser, err := setupBrowser(10)
	if err != nil {
		return nil, nil
	}
	browser.listen()
	browser.browse(port)

	timer := time.NewTimer(time.Second * serverWaitTime)
	done := false
	for !done {
		select {
		case <-timer.C:
			done = true
			browser.conn.Close()
		case msg := <-browser.mc:
			items = append(items, msg)
		}
	}
	return items, nil
}

func watcher(port int, freq int) (*GDMWatcher, error) {
	browser, err := setupBrowser(freq)
	if err != nil {
		return nil, err
	}
	browser.listen()
	browser.browse(port)
	go func() {
		for {
			<-browser.ticker.C
			browser.browse(port)
		}
	}()

	watcher := &GDMWatcher{Watch: browser.mc, closer: make(chan bool)}
	browser.closer(watcher.closer)

	return watcher, nil
}

func setupBrowser(i int) (*gdmBrowser, error) {
	refresh := (time.Duration(i) * time.Second)
	// setup listener to broadcast stuff
	udpaddr, err := net.ResolveUDPAddr("udp4", ":0")
	if err != nil {
		return nil, err
	}
	conn, lerr := net.ListenUDP("udp4", udpaddr)
	if lerr != nil {
		return nil, lerr
	}
	return &gdmBrowser{conn: conn,
		mc:     make(chan *GDMMessage),
		ticker: time.NewTicker(refresh)}, nil
}

func (b *gdmBrowser) listen() {
	go func() {
		buf := make([]byte, 1024)
		for {
			mlen, raddr, err := b.conn.ReadFromUDP(buf)
			if err != nil {
				if nerr, ok := err.(net.Error); !ok || !nerr.Temporary() {
					// this connection has become useless
					close(b.mc)
					return
				}
				continue
			}
			msg := string(buf[0:mlen])
			if strings.HasPrefix(msg, "HTTP/1.0 200 OK") {
				gdmmsg := newGDMMessage(msg, raddr)
				b.mc <- gdmmsg
			}
		}
	}()
}

func (b *gdmBrowser) closer(s chan bool) {
	go func() {
		select {
		case <-s:
			b.conn.Close()
			return
		}
	}()
}

func (b *gdmBrowser) browse(port int) {
	addrs := getBroadcastAddrs()
	ports := strconv.Itoa(port)
	for _, a := range addrs {
		udpdest, err := net.ResolveUDPAddr("udp4", a+":"+ports)
		if err != nil {
			continue
		}
		buf := []byte("M-SEARCH * HTTP/1.1\r\n\r\n")
		_, werr := b.conn.WriteToUDP(buf, udpdest)
		if werr != nil {
			continue
		}
	}
}

func newGDMMessage(data string, addr *net.UDPAddr) *GDMMessage {
	msglines := strings.Split(data, "\r\n")
	gdm := GDMMessage{Address: addr, Added: true, Props: make(map[string]string)}
	for _, m := range msglines {
		if strings.Contains(m, ":") {
			kv := strings.Split(m, ":")
			if len(kv) == 2 {
				gdm.Props[strings.TrimSpace(kv[0])] = strings.TrimSpace(kv[1])
			}
		}
	}
	return &gdm
}

func getBroadcastAddrs() (baddr []string) {
	ifaces, _ := net.Interfaces()
	for _, iface := range ifaces {
		// only send on interfaces that are up and can do broadcast
		upandbcast := net.FlagBroadcast | net.FlagUp
		if iface.Flags&upandbcast != upandbcast {
			continue
		}
		addrs, _ := iface.Addrs()
		if len(addrs) == 0 {
			continue
		}
		for _, addr := range addrs {
			var bcast []string
			ip, ipn, err := net.ParseCIDR(addr.String())
			if err != nil {
				continue
			}
			// we only handle ipv4 addresses for now
			ipv4 := ip.To4()
			if ipv4 == nil {
				continue
			}
			for i, m := range ipn.Mask {
				if m == 0 {
					bcast = append(bcast, "255")
				} else {
					bcast = append(bcast, fmt.Sprintf("%d", ipv4[i]))
				}
			}
			baddr = append(baddr, strings.Join(bcast, "."))
		}
	}
	return baddr
}
<|endoftext|>


<commit_before>package lfsapi

import (
	"context"
	"net/http"
)

// ckey is a type that wraps a string for package-unique context.Context keys.
type ckey string

const (
	// contextKeyRetries is a context.Context key for storing the desired
	// number of retries for a given request.
	contextKeyRetries ckey = "retries"
)

// WithRetries stores the desired number of retries "n" on the given
// http.Request, and causes it to be retried "n" times in the case of a non-nil
// network related error.
func WithRetries(req *http.Request, n int) *http.Request {
	ctx := req.Context()
	ctx = context.WithValue(ctx, contextKeyRetries, n)

	return req.WithContext(ctx)
}
<commit_msg>lfsapi: introduce Retries<commit_after>package lfsapi

import (
	"context"
	"net/http"
)

// ckey is a type that wraps a string for package-unique context.Context keys.
type ckey string

const (
	// contextKeyRetries is a context.Context key for storing the desired
	// number of retries for a given request.
	contextKeyRetries ckey = "retries"
)

// WithRetries stores the desired number of retries "n" on the given
// http.Request, and causes it to be retried "n" times in the case of a non-nil
// network related error.
func WithRetries(req *http.Request, n int) *http.Request {
	ctx := req.Context()
	ctx = context.WithValue(ctx, contextKeyRetries, n)

	return req.WithContext(ctx)
}

// Retries returns the number of retries requested for a given http.Request.
func Retries(req *http.Request) (int, bool) {
	n, ok := req.Context().Value(contextKeyRetries).(int)

	return n, ok
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/printer"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
)

var (
	fRecursive bool
	fOneLine   bool
	fJSON      bool
)

func init() {
	flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
	flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
	flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
}

func usage() {
	fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()
	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(2)
	}
	pos := flag.Args()[0]
	name, start, _, err := parsePos(pos)
	if err != nil {
		log.Fatal(err)
	}
	eval, err := filepath.EvalSymlinks(name)
	if err != nil {
		log.Fatal(err)
	}
	name, err = filepath.Abs(eval)
	if err != nil {
		log.Fatal(err)
	}
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	bpkg, err := buildutil.ContainingPackage(&build.Default, cwd, name)
	if err != nil {
		log.Fatal(err)
	}
	conf := &loader.Config{}
	conf.TypeCheckFuncBodies = func(s string) bool {
		return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
	}
	conf.ImportWithTests(bpkg.ImportPath)
	lprog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}
	var tf *token.File
	var af *ast.File
	pkg := lprog.InitialPackages()[0]
	for _, ff := range pkg.Files {
		file := lprog.Fset.File(ff.Pos())
		if file.Name() == name {
			af = ff
			tf = file
			break
		}
	}
	tstart, tend, err := fileOffsetToPos(tf, start, start)
	if err != nil {
		log.Fatal(err)
	}
	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
	var complit *ast.CompositeLit
	for _, p := range path {
		if p, ok := p.(*ast.CompositeLit); ok {
			complit = p
			break
		}
	}
	if complit == nil {
		log.Fatal("no composite literal found near point")
	}
	if len(complit.Elts) == 0 {
		printComplit(complit, complit, lprog.Fset, lprog.Fset)
		return
	}
	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
		lit := complit
		if fOneLine {
			lit = copyExpr(complit, 1).(*ast.CompositeLit)
		}
		printComplit(complit, lit, lprog.Fset, lprog.Fset)
		return
	}
	st, ok := pkg.TypeOf(complit.Type).Underlying().(*types.Struct)
	if !ok {
		log.Fatal("not a struct initialiser")
		return
	}

	var calcPos func(int) token.Pos
	if fOneLine {
		calcPos = func(int) token.Pos { return token.Pos(1) }
	} else {
		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
	}

	newFset := token.NewFileSet()
	newFile := newFset.AddFile("", -1, st.NumFields()+2)
	newComplit := &ast.CompositeLit{
		Type:   complit.Type,
		Lbrace: 1,
		Rbrace: token.Pos(st.NumFields() + 2),
	}
	if fOneLine {
		newComplit.Rbrace = 1
	}
	newFile.AddLine(1)
	newFile.AddLine(st.NumFields() + 2)
	for i := 0; i < st.NumFields(); i++ {
		newFile.AddLine(2 + i)
		field := st.Field(i)
		elt := &ast.KeyValueExpr{
			Key:   &ast.Ident{NamePos: calcPos(i), Name: field.Name()},
			Value: copyExpr(complit.Elts[i], calcPos(i)),
		}
		newComplit.Elts = append(newComplit.Elts, elt)
	}
	printComplit(complit, newComplit, lprog.Fset, newFset)
}

func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
	buf := &bytes.Buffer{}
	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
	_ = cfg.Fprint(buf, newfset, newlit)
	if fJSON {
		output := struct {
			Start       int    `json:"start"`
			End         int    `json:"end"`
			Replacement string `json:"replacement"`
		}{
			oldfset.Position(oldlit.Pos()).Offset,
			oldfset.Position(oldlit.End()).Offset,
			buf.String(),
		}
		_ = json.NewEncoder(os.Stdout).Encode(output)
	} else {
		fmt.Println(buf.String())
	}
}

func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
	switch expr := expr.(type) {
	case *ast.BasicLit:
		cp := *expr
		cp.ValuePos = 0
		return &cp
	case *ast.BinaryExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.OpPos = 0
		cp.Y = copyExpr(cp.Y, line)
		return &cp
	case *ast.CallExpr:
		cp := *expr
		cp.Fun = copyExpr(cp.Fun, line)
		cp.Lparen = 0
		for i, v := range cp.Args {
			cp.Args[i] = copyExpr(v, line)
		}
		if cp.Ellipsis != 0 {
			cp.Ellipsis = line
		}
		cp.Rparen = 0
		return &cp
	case *ast.CompositeLit:
		cp := *expr
		cp.Type = copyExpr(cp.Type, line)
		cp.Lbrace = 0
		for i, v := range cp.Elts {
			cp.Elts[i] = copyExpr(v, line)
		}
		cp.Rbrace = 0
		return &cp
	case *ast.Ident:
		cp := *expr
		cp.NamePos = 0
		return &cp
	case *ast.IndexExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Index = copyExpr(cp.Index, line)
		cp.Rbrack = 0
		return &cp
	case *ast.KeyValueExpr:
		cp := *expr
		cp.Key = copyExpr(cp.Key, line)
		cp.Colon = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ParenExpr:
		cp := *expr
		cp.Lparen = 0
		cp.X = copyExpr(cp.X, line)
		cp.Rparen = 0
		return &cp
	case *ast.SelectorExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
		return &cp
	case *ast.SliceExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Low = copyExpr(cp.Low, line)
		cp.High = copyExpr(cp.High, line)
		cp.Max = copyExpr(cp.Max, line)
		cp.Rbrack = 0
		return &cp
	case *ast.StarExpr:
		cp := *expr
		cp.Star = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.TypeAssertExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lparen = 0
		cp.Type = copyExpr(cp.Type, line)
		cp.Rparen = 0
		return &cp
	case *ast.UnaryExpr:
		cp := *expr
		cp.OpPos = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case nil:
		return nil
	default:
		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
	}
	return nil
}
<commit_msg>Don't crash when encountering maps or arrays<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/printer"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
)

var (
	fRecursive bool
	fOneLine   bool
	fJSON      bool
)

func init() {
	flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
	flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
	flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
}

func usage() {
	fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()
	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(2)
	}
	pos := flag.Args()[0]
	name, start, _, err := parsePos(pos)
	if err != nil {
		log.Fatal(err)
	}
	eval, err := filepath.EvalSymlinks(name)
	if err != nil {
		log.Fatal(err)
	}
	name, err = filepath.Abs(eval)
	if err != nil {
		log.Fatal(err)
	}
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	bpkg, err := buildutil.ContainingPackage(&build.Default, cwd, name)
	if err != nil {
		log.Fatal(err)
	}
	conf := &loader.Config{}
	conf.TypeCheckFuncBodies = func(s string) bool {
		return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
	}
	conf.ImportWithTests(bpkg.ImportPath)
	lprog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}
	var tf *token.File
	var af *ast.File
	pkg := lprog.InitialPackages()[0]
	for _, ff := range pkg.Files {
		file := lprog.Fset.File(ff.Pos())
		if file.Name() == name {
			af = ff
			tf = file
			break
		}
	}
	tstart, tend, err := fileOffsetToPos(tf, start, start)
	if err != nil {
		log.Fatal(err)
	}
	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
	var complit *ast.CompositeLit
	for _, p := range path {
		if p, ok := p.(*ast.CompositeLit); ok {
			complit = p
			break
		}
	}
	if complit == nil {
		log.Fatal("no composite literal found near point")
	}
	if len(complit.Elts) == 0 {
		printComplit(complit, complit, lprog.Fset, lprog.Fset)
		return
	}
	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
		lit := complit
		if fOneLine {
			lit = copyExpr(complit, 1).(*ast.CompositeLit)
		}
		printComplit(complit, lit, lprog.Fset, lprog.Fset)
		return
	}
	st, ok := pkg.TypeOf(complit.Type).Underlying().(*types.Struct)
	if !ok {
		log.Fatal("not a struct initialiser")
		return
	}

	var calcPos func(int) token.Pos
	if fOneLine {
		calcPos = func(int) token.Pos { return token.Pos(1) }
	} else {
		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
	}

	newFset := token.NewFileSet()
	newFile := newFset.AddFile("", -1, st.NumFields()+2)
	newComplit := &ast.CompositeLit{
		Type:   complit.Type,
		Lbrace: 1,
		Rbrace: token.Pos(st.NumFields() + 2),
	}
	if fOneLine {
		newComplit.Rbrace = 1
	}
	newFile.AddLine(1)
	newFile.AddLine(st.NumFields() + 2)
	for i := 0; i < st.NumFields(); i++ {
		newFile.AddLine(2 + i)
		field := st.Field(i)
		elt := &ast.KeyValueExpr{
			Key:   &ast.Ident{NamePos: calcPos(i), Name: field.Name()},
			Value: copyExpr(complit.Elts[i], calcPos(i)),
		}
		newComplit.Elts = append(newComplit.Elts, elt)
	}
	printComplit(complit, newComplit, lprog.Fset, newFset)
}

func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
	buf := &bytes.Buffer{}
	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
	_ = cfg.Fprint(buf, newfset, newlit)
	if fJSON {
		output := struct {
			Start       int    `json:"start"`
			End         int    `json:"end"`
			Replacement string `json:"replacement"`
		}{
			oldfset.Position(oldlit.Pos()).Offset,
			oldfset.Position(oldlit.End()).Offset,
			buf.String(),
		}
		_ = json.NewEncoder(os.Stdout).Encode(output)
	} else {
		fmt.Println(buf.String())
	}
}

func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
	switch expr := expr.(type) {
	case *ast.BasicLit:
		cp := *expr
		cp.ValuePos = 0
		return &cp
	case *ast.BinaryExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.OpPos = 0
		cp.Y = copyExpr(cp.Y, line)
		return &cp
	case *ast.CallExpr:
		cp := *expr
		cp.Fun = copyExpr(cp.Fun, line)
		cp.Lparen = 0
		for i, v := range cp.Args {
			cp.Args[i] = copyExpr(v, line)
		}
		if cp.Ellipsis != 0 {
			cp.Ellipsis = line
		}
		cp.Rparen = 0
		return &cp
	case *ast.CompositeLit:
		cp := *expr
		cp.Type = copyExpr(cp.Type, line)
		cp.Lbrace = 0
		for i, v := range cp.Elts {
			cp.Elts[i] = copyExpr(v, line)
		}
		cp.Rbrace = 0
		return &cp
	case *ast.Ident:
		cp := *expr
		cp.NamePos = 0
		return &cp
	case *ast.IndexExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Index = copyExpr(cp.Index, line)
		cp.Rbrack = 0
		return &cp
	case *ast.KeyValueExpr:
		cp := *expr
		cp.Key = copyExpr(cp.Key, line)
		cp.Colon = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ParenExpr:
		cp := *expr
		cp.Lparen = 0
		cp.X = copyExpr(cp.X, line)
		cp.Rparen = 0
		return &cp
	case *ast.SelectorExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
		return &cp
	case *ast.SliceExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Low = copyExpr(cp.Low, line)
		cp.High = copyExpr(cp.High, line)
		cp.Max = copyExpr(cp.Max, line)
		cp.Rbrack = 0
		return &cp
	case *ast.StarExpr:
		cp := *expr
		cp.Star = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.TypeAssertExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lparen = 0
		cp.Type = copyExpr(cp.Type, line)
		cp.Rparen = 0
		return &cp
	case *ast.UnaryExpr:
		cp := *expr
		cp.OpPos = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.MapType:
		cp := *expr
		cp.Map = 0
		cp.Key = copyExpr(cp.Key, line)
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ArrayType:
		cp := *expr
		cp.Lbrack = 0
		cp.Len = copyExpr(cp.Len, line)
		cp.Elt = copyExpr(cp.Elt, line)
		return &cp
	case *ast.Ellipsis:
		cp := *expr
		cp.Elt = copyExpr(cp.Elt, line)
		cp.Ellipsis = line
		return &cp
	case nil:
		return nil
	default:
		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
	}
	return nil
}
<|endoftext|>


<commit_before>package views

import (
	"html/template"
	"sync"
	"path/filepath"
	"os"
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"io"
	"errors"
	"bytes"
	"github.com/Simbory/mego/fswatcher"
)

type ViewEngine struct {
	viewDir  string
	viewExt  string
	funcMap  template.FuncMap
	locker   sync.RWMutex
	viewMap  map[string]*tplCache
	compiled bool

	watcher *fswatcher.FileWatcher
}

func (engine *ViewEngine) AddFunc(name string, viewFunc interface{}) {
	if len(name) < 1 || viewFunc == nil {
		return
	}
	if engine.funcMap == nil {
		engine.funcMap = make(template.FuncMap)
	}
	if f,ok := engine.funcMap[name]; !ok || f==nil {
		engine.funcMap[name] = viewFunc
	}
}

func (engine *ViewEngine) addView(name string, v *tplCache) {
	if len(name) == 0 || v == nil {
		return
	}
	if engine.viewMap == nil {
		engine.viewMap = map[string]*tplCache{}
	}
	engine.viewMap[name] = v
}

func (engine *ViewEngine) getView(name string) (*template.Template, error) {
	if engine.viewMap == nil {
		return nil, nil
	}
	cache := engine.viewMap[name]
	if cache == nil {
		return nil, nil
	}
	return cache.tpl, cache.err
}

func (engine *ViewEngine) getDeep(file, parent string, t *template.Template) (*template.Template, [][]string, error){
	var fileAbsPath string
	if strings.HasPrefix(file, "../") {
		fileAbsPath = filepath.Join(engine.viewDir, filepath.Dir(parent), file)
	} else {
		fileAbsPath = filepath.Join(engine.viewDir, file)
	}
	stat, err := os.Stat(fileAbsPath)
	if err != nil || stat.IsDir() {
		return nil, [][]string{}, fmt.Errorf("The partial view '%s' in '%s' canot be found", file, parent)
	}
	data, err := ioutil.ReadFile(fileAbsPath)
	if err != nil {
		return nil, [][]string{}, err
	}
	t, err = t.New(file).Parse(string(data))
	if err != nil {
		return nil, [][]string{}, err
	}
	reg := regexp.MustCompile("[{]{2}[ \t]*template[ \t]+\"([^\"]+)\"")
	allSub := reg.FindAllStringSubmatch(string(data), -1)
	for _, m := range allSub {
		if len(m) == 2 {
			name := m[1]
			if !strings.HasSuffix(strings.ToLower(name), engine.viewExt) {
				continue
			}
			look := t.Lookup(name)
			if look != nil {
				continue
			}
			t, _, err = engine.getDeep(name, file, t)
			if err != nil {
				return nil, [][]string{}, err
			}
		}
	}
	return t, allSub, nil
}

func (engine *ViewEngine) getLoop(temp *template.Template, subMods [][]string, others ...string) (t *template.Template, err error) {
	t = temp
	for _, m := range subMods {
		if len(m) == 2 {
			tpl := t.Lookup(m[1])
			if tpl != nil {
				continue
			}
			//check filename
			for _, otherFile := range others {
				if otherFile == m[1] {
					var subMods1 [][]string
					t, subMods1, err = engine.getDeep(otherFile, "", t)
					if err != nil {
						return nil, err
					} else if subMods1 != nil && len(subMods1) > 0 {
						t, err = engine.getLoop(t, subMods1, others...)
					}
					break
				}
			}
		}
	}
	return
}

func (engine *ViewEngine) getTplCache(file string, others ...string) *tplCache {
	t := template.New(file)
	if engine.funcMap != nil {
		t.Funcs(engine.funcMap)
	}
	var subMods [][]string
	t, subMods, err := engine.getDeep(file, "", t)
	if err != nil {
		return &tplCache{nil, err}
	}
	t, err = engine.getLoop(t, subMods, others...)
	if err != nil {
		return &tplCache{nil, err}
	}
	return &tplCache{t, nil}
}

func (engine *ViewEngine) compile() error {
	engine.locker.Lock()
	defer engine.locker.Unlock()
	if engine.compiled {
		return nil
	}
	engine.AddFunc("include", engine.includeView)
	if _, err := os.Stat(engine.viewDir); err != nil {
		if os.IsNotExist(err) {
			return err
		}
		return fmt.Errorf("Failed to open view directory '%s'.", engine.viewDir)
	}
	vf := &file{
		root:    engine.viewDir,
		files:   make(map[string][]string),
		viewExt: engine.viewExt,
	}
	err := filepath.Walk(engine.viewDir, func(path string, f os.FileInfo, err error) error {
		return vf.visit(path, f, err)
	})
	if err != nil {
		return err
	}
	for _, v := range vf.files {
		for _, file := range v {
			v := engine.getTplCache(file, v...)
			engine.addView(file, v)
		}
	}
	engine.compiled = true
	return nil
}

func (engine *ViewEngine) includeView(viewName string, data interface{}) template.HTML {
	buf := &bytes.Buffer{}
	err := engine.Render(buf, viewName, data)
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.Bytes())
}

func (engine *ViewEngine) Clear() {
	engine.locker.Lock()
	defer engine.locker.Unlock()

	engine.compiled = false
	engine.viewMap = nil
}

func (engine *ViewEngine) Render(writer io.Writer, viewPath string, viewData interface{}) error {
	if len(viewPath) < 1 {
		return errors.New("The parameter 'viewPath' cannot be empty")
	}
	if !strings.HasSuffix(viewPath, engine.viewExt) {
		viewPath = viewPath + engine.viewExt
	}
	if !engine.compiled {
		err := engine.compile()
		if err != nil {
			return err
		}
	}
	tpl,err := engine.getView(viewPath)
	if err != nil {
		return err
	}
	if tpl == nil{
		return fmt.Errorf("The view file '%s' cannot be found", viewPath)
	}
	err = tpl.Execute(writer, viewData)
	if err != nil {
		return err
	}
	return nil
}

func NewEngine(rootDir, ext string) (*ViewEngine, error) {
	if len(ext) == 0 {
		ext = ".html"
	}
	if !strings.HasPrefix(ext, ".") {
		ext = "." + ext
	}
	w,err := fswatcher.NewWatcher()
	if err != nil {
		return nil, err
	}
	engine := &ViewEngine{
		viewDir: rootDir,
		viewExt: strings.ToLower(ext),
		watcher: w,
	}
	stat,err := os.Stat(engine.viewDir)
	if err != nil {
		return nil, err
	}
	if !stat.IsDir() {
		return nil, fmt.Errorf("Failed to open the view directory '%s'.", engine.viewDir)
	}
	engine.watcher.AddHandler(&compileHandler{engine})
	engine.watcher.Start()
	engine.watcher.AddWatch(engine.viewDir, true)
	return engine, nil
}<commit_msg>fix spell error<commit_after>package views

import (
	"html/template"
	"sync"
	"path/filepath"
	"os"
	"fmt"
	"io/ioutil"
	"regexp"
	"strings"
	"io"
	"errors"
	"bytes"
	"github.com/Simbory/mego/fswatcher"
)

type ViewEngine struct {
	viewDir  string
	viewExt  string
	funcMap  template.FuncMap
	locker   sync.RWMutex
	viewMap  map[string]*tplCache
	compiled bool

	watcher *fswatcher.FileWatcher
}

func (engine *ViewEngine) AddFunc(name string, viewFunc interface{}) {
	if len(name) < 1 || viewFunc == nil {
		return
	}
	if engine.funcMap == nil {
		engine.funcMap = make(template.FuncMap)
	}
	if f,ok := engine.funcMap[name]; !ok || f==nil {
		engine.funcMap[name] = viewFunc
	}
}

func (engine *ViewEngine) addView(name string, v *tplCache) {
	if len(name) == 0 || v == nil {
		return
	}
	if engine.viewMap == nil {
		engine.viewMap = map[string]*tplCache{}
	}
	engine.viewMap[name] = v
}

func (engine *ViewEngine) getView(name string) (*template.Template, error) {
	if engine.viewMap == nil {
		return nil, nil
	}
	cache := engine.viewMap[name]
	if cache == nil {
		return nil, nil
	}
	return cache.tpl, cache.err
}

func (engine *ViewEngine) getDeep(file, parent string, t *template.Template) (*template.Template, [][]string, error){
	var fileAbsPath string
	if strings.HasPrefix(file, "../") {
		fileAbsPath = filepath.Join(engine.viewDir, filepath.Dir(parent), file)
	} else {
		fileAbsPath = filepath.Join(engine.viewDir, file)
	}
	stat, err := os.Stat(fileAbsPath)
	if err != nil || stat.IsDir() {
		return nil, [][]string{}, fmt.Errorf("The partial view '%s' in '%s' cannot be found", file, parent)
	}
	data, err := ioutil.ReadFile(fileAbsPath)
	if err != nil {
		return nil, [][]string{}, err
	}
	t, err = t.New(file).Parse(string(data))
	if err != nil {
		return nil, [][]string{}, err
	}
	reg := regexp.MustCompile("[{]{2}[ \t]*template[ \t]+\"([^\"]+)\"")
	allSub := reg.FindAllStringSubmatch(string(data), -1)
	for _, m := range allSub {
		if len(m) == 2 {
			name := m[1]
			if !strings.HasSuffix(strings.ToLower(name), engine.viewExt) {
				continue
			}
			look := t.Lookup(name)
			if look != nil {
				continue
			}
			t, _, err = engine.getDeep(name, file, t)
			if err != nil {
				return nil, [][]string{}, err
			}
		}
	}
	return t, allSub, nil
}

func (engine *ViewEngine) getLoop(temp *template.Template, subMods [][]string, others ...string) (t *template.Template, err error) {
	t = temp
	for _, m := range subMods {
		if len(m) == 2 {
			tpl := t.Lookup(m[1])
			if tpl != nil {
				continue
			}
			//check filename
			for _, otherFile := range others {
				if otherFile == m[1] {
					var subMods1 [][]string
					t, subMods1, err = engine.getDeep(otherFile, "", t)
					if err != nil {
						return nil, err
					} else if subMods1 != nil && len(subMods1) > 0 {
						t, err = engine.getLoop(t, subMods1, others...)
					}
					break
				}
			}
		}
	}
	return
}

func (engine *ViewEngine) getTplCache(file string, others ...string) *tplCache {
	t := template.New(file)
	if engine.funcMap != nil {
		t.Funcs(engine.funcMap)
	}
	var subMods [][]string
	t, subMods, err := engine.getDeep(file, "", t)
	if err != nil {
		return &tplCache{nil, err}
	}
	t, err = engine.getLoop(t, subMods, others...)
	if err != nil {
		return &tplCache{nil, err}
	}
	return &tplCache{t, nil}
}

func (engine *ViewEngine) compile() error {
	engine.locker.Lock()
	defer engine.locker.Unlock()
	if engine.compiled {
		return nil
	}
	engine.AddFunc("include", engine.includeView)
	if _, err := os.Stat(engine.viewDir); err != nil {
		if os.IsNotExist(err) {
			return err
		}
		return fmt.Errorf("Failed to open view directory '%s'.", engine.viewDir)
	}
	vf := &file{
		root:    engine.viewDir,
		files:   make(map[string][]string),
		viewExt: engine.viewExt,
	}
	err := filepath.Walk(engine.viewDir, func(path string, f os.FileInfo, err error) error {
		return vf.visit(path, f, err)
	})
	if err != nil {
		return err
	}
	for _, v := range vf.files {
		for _, file := range v {
			v := engine.getTplCache(file, v...)
			engine.addView(file, v)
		}
	}
	engine.compiled = true
	return nil
}

func (engine *ViewEngine) includeView(viewName string, data interface{}) template.HTML {
	buf := &bytes.Buffer{}
	err := engine.Render(buf, viewName, data)
	if err != nil {
		panic(err)
	}
	return template.HTML(buf.Bytes())
}

func (engine *ViewEngine) Clear() {
	engine.locker.Lock()
	defer engine.locker.Unlock()

	engine.compiled = false
	engine.viewMap = nil
}

func (engine *ViewEngine) Render(writer io.Writer, viewPath string, viewData interface{}) error {
	if len(viewPath) < 1 {
		return errors.New("The parameter 'viewPath' cannot be empty")
	}
	if !strings.HasSuffix(viewPath, engine.viewExt) {
		viewPath = viewPath + engine.viewExt
	}
	if !engine.compiled {
		err := engine.compile()
		if err != nil {
			return err
		}
	}
	tpl,err := engine.getView(viewPath)
	if err != nil {
		return err
	}
	if tpl == nil{
		return fmt.Errorf("The view file '%s' cannot be found", viewPath)
	}
	err = tpl.Execute(writer, viewData)
	if err != nil {
		return err
	}
	return nil
}

func NewEngine(rootDir, ext string) (*ViewEngine, error) {
	if len(ext) == 0 {
		ext = ".html"
	}
	if !strings.HasPrefix(ext, ".") {
		ext = "." + ext
	}
	w,err := fswatcher.NewWatcher()
	if err != nil {
		return nil, err
	}
	engine := &ViewEngine{
		viewDir: rootDir,
		viewExt: strings.ToLower(ext),
		watcher: w,
	}
	stat,err := os.Stat(engine.viewDir)
	if err != nil {
		return nil, err
	}
	if !stat.IsDir() {
		return nil, fmt.Errorf("Failed to open the view directory '%s'.", engine.viewDir)
	}
	engine.watcher.AddHandler(&compileHandler{engine})
	engine.watcher.Start()
	engine.watcher.AddWatch(engine.viewDir, true)
	return engine, nil
}<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"makible.com/models"
	"makible.com/serial"
	"os"
	"reflect"
	"runtime"
	"strconv"
	"strings"
)

type Device struct {
	Name           string
	IdInfo         string
	LineTerminator string
	ECount         string
	Baud           int
	IODevice       io.ReadWriteCloser
	MoveSpeed      int
	Pos            position
	Homed          bool
	Greeting       string
	FileName       string
	FileData       string
	JobStatus      int
	ActiveQueue    []string
	HoldQueue      []string
}

type position struct {
	X  int
	Y  int
	Z  int
	E1 int
}

type movement struct {
	Axis     string
	Distance int `json: ",string"`
	Speed    int `json: ",string"`
}

type tool struct {
	Name  string
	Value int `json: ",string"`
}

type userfile struct {
	Name string
	Data string
}

const (
	DEFBAUD = 115200
	NSF     = "no such file or directory"
	DNC     = "device not configured"
	RM      = "device removed"
)

const (
	IDLE    = (1 << iota)
	RUNNING = (1 << iota)
	PAUSED  = (1 << iota)
)

//  [ TODO ]
//  need to dynamically list out
//  the devices by OS
func getAttachedDevices(existing *map[string]*Device) (string, error) {
	devName := "/dev/tty.usbmodem001" //  POSIX style naming
	if runtime.GOOS == "windows" {
		//  [HACK]
		//  temporary hack for Windows
		//  to let the user create a .txt
		//  file in the data dir for us to
		//  know what the COM port enumerates
		info, err := ioutil.ReadDir("config/")
		if err != nil {
			return "", fmt.Errorf("unable to get COM info from 'config/': %v\n", err)
		}

		found := false
		for _, f := range info {
			if strings.HasPrefix(f.Name(), "__COM") {
				devName = strings.Trim(f.Name(), "__")
				devName = strings.Trim(devName, ".txt")
				found = true
			}
		}

		if !found {
			return "", fmt.Errorf("__COM device not found")
		}
	}

	//  ===
	if (*existing)[devName] != nil {
		//  this means we should be tracking the device
		//  but if the err says that we can't find the device
		//  then we need to let it go
		_, err := serial.OpenPort(devName, DEFBAUD)
		if err != nil && strings.HasSuffix(err.Error(), NSF) {
			delete(*existing, devName)
			return "", fmt.Errorf(devName + " device removed")
		}

		return "", nil
	}

	d, err := serial.OpenPort(devName, DEFBAUD)
	if err != nil {
		return "", err
	}

	info, err := getFirmwareInfo(&d)
	if err != nil {
		return "", err
	}

	if len(info) > 1 {
		pos := position{
			X:  0,
			Y:  0,
			Z:  0,
			E1: 0,
		}

		dev := &Device{
			Name:           devName,
			LineTerminator: models.LINETERMINATOR,
			Baud:           DEFBAUD,
			IODevice:       d,
			MoveSpeed:      0,
			Pos:            pos,
			Homed:          false,
			Greeting:       info,
			JobStatus:      IDLE,
		}

		(*existing)[devName] = dev
		return devName, nil
	}

	return "", nil
}

func getFirmwareInfo(dev *io.ReadWriteCloser) (string, error) {
	resp, err := lobCommand(dev, models.FWMCODE+models.LINETERMINATOR)
	if err != nil {
		return "", err
	}

	return resp, nil
}

func (dev *Device) Do(action string, params string) (*Message, error) {
	if len(action) < 1 || action == "" {
		return nil, nil
	}
	switch action {
	case "move":
		var (
			mvr  movement
			dist int
		)

		if err := json.Unmarshal([]byte(params), &mvr); err != nil {
			return nil, err
		}

		cmd := "G1 " + mvr.Axis
		if mvr.Axis == "E1" {
			//  [ TODO ]
			//  The A6 only has 1 extruder so force to "G1 E"
			//  for the command. Will need to pull from a config
			//  .json file to dynamically manage this
			cmd = "G1 E"
		}

		//  using absolute positioning so the need to track where
		//  the device is in space and append the distance prior
		//  to issuing the cmd

		//  ===[ TODO ]
		//  add device safe-gaurds to prevent
		//  moving too far and get the available
		//  axis from the provided dev
		switch mvr.Axis {
		case "X":
			if dev.Pos.X == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.X += mvr.Distance
			dist = dev.Pos.X
		case "Y":
			if dev.Pos.Y == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.Y += mvr.Distance
			dist = dev.Pos.Y
		case "Z":
			if dev.Pos.Z == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.Z += mvr.Distance
			dist = dev.Pos.Z
		case "E1":
			dev.Pos.E1 += mvr.Distance
			dist = dev.Pos.E1
		default:
			return nil, errors.New("invalid axis provided")
		}

		cmd += strconv.Itoa(dist)

		//  check to see if the speed has
		//  changed or not and set accordingly
		if mvr.Speed != dev.MoveSpeed {
			dev.MoveSpeed = mvr.Speed
			cmd += " F" + strconv.Itoa(mvr.Speed)
		}

		//  tag on the device specific line terminator
		//  (e.g. MakiBox A6 == '\r\n')
		cmd += dev.LineTerminator

		//  lob
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "temper":
		var tmp tool
		if err := json.Unmarshal([]byte(params), &tmp); err != nil {
			return nil, err
		}

		//  [ TODO ]
		//  get the proper MCodes from the device config
		cmd := "M140 S" //  defaulting to heated bed for A6
		if tmp.Name == "hotend" {
			cmd = "M104 S"
		}
		cmd += strconv.Itoa(tmp.Value) + dev.LineTerminator

		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "home":
		var mvr movement
		if err := json.Unmarshal([]byte(params), &mvr); err != nil {
			return nil, err
		}

		//  grok the cmd
		cmd := "G28"
		if mvr.Axis != "ALL" {
			cmd += " " + mvr.Axis + "0"
		}

		cmd += dev.LineTerminator

		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}

		//  need to set the position of E to 0 on the device
		if mvr.Axis == "ALL" || mvr.Axis == "E" {
			cmd = "G92 E0" + dev.LineTerminator
			r, e := dev.LobCommand(cmd)
			if e != nil {
				return nil, e
			}
			resp += r
		}

		//  reset position to 0
		if mvr.Axis == "ALL" {
			dev.Pos = position{
				X:  0,
				Y:  0,
				Z:  0,
				E1: 0,
			}
			dev.Homed = true
		} else {
			axis := mvr.Axis
			if axis == "E" {
				axis = "E1"
			}
			reflect.ValueOf(&dev.Pos).Elem().FieldByName(axis).SetInt(0)
		}

		return responseMsg(dev.Name, action, resp), nil

	case "status":
		cmd := "M105" + dev.LineTerminator
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "load":
		//  === [ TODO ]
		var gc userfile
		if err := json.Unmarshal([]byte(params), &gc); err != nil {
			return nil, err
		}

		fn := "data/" + gc.Name
		info, _ := os.Stat(fn)
		if info != nil {
			//  this should mean file exists
			//  and for now, we will delete the existing file
			//  of the same name and write the new data
			if err := os.Remove(fn); err != nil {
				return nil, err
			}
		}

		f, err := os.Create(fn)
		if err != nil {
			return nil, err
		}

		if _, err := f.Write([]byte(gc.Data)); err != nil {
			fmt.Println(err)
		}

		dev.FileName, dev.FileData = gc.Name, gc.Data
		return responseMsg(dev.Name, action, "temp file written"), nil

	case "motley":
		if strings.Contains(params, "motorsoff") {
			cmd := "M84" + dev.LineTerminator
			resp, err := dev.LobCommand(cmd)

			if err != nil {
				return nil, err
			}
			return responseMsg(dev.Name, action, resp), nil
		}

	//  manual gcode entered by user
	//  via interactive console
	case "console":
		cmd := params + dev.LineTerminator
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	default:
		return nil, errors.New("invalid action: " + action)
	}

	return nil, nil
}

func (dev *Device) LobCommand(cmd string) (string, error) {
	return (lobCommand(&(dev.IODevice), cmd))
}

func lobCommand(dev *io.ReadWriteCloser, cmd string) (string, error) {
	//  check if valid code in device codes ::TODO::
	//  if so, then lob to the device
	n, err := (*dev).Write([]byte(cmd))
	if err != nil {
		return "", err
	}

	if n < 1 {
		return "", errors.New("unable to write to device")
	}

	//  read response from device
	response := "\n"
	for i := 0; i < 20; i++ {
		buf := make([]byte, 255)
		n, err = (*dev).Read(buf)
		if n < 1 {
			fmt.Printf("lobCommand - device did not respond: %d\n", n)
			return "", nil
		}

		response += string(buf[:n])
		if strings.Contains(response, "go") && strings.Contains(response, "ok ") {
			break
		}
	}

	return response, nil
}

func responseMsg(dn string, action string, body string) *Message {
	return &Message{
		Type:       "response",
		DeviceName: dn,
		Action:     action,
		Body:       body,
	}
}
<commit_msg>fixed import and run go fmt<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/makible/models"
	"github.com/makible/serial"
	"io"
	"io/ioutil"
	"os"
	"reflect"
	"runtime"
	"strconv"
	"strings"
)

type Device struct {
	Name           string
	IdInfo         string
	LineTerminator string
	ECount         string
	Baud           int
	IODevice       io.ReadWriteCloser
	MoveSpeed      int
	Pos            position
	Homed          bool
	Greeting       string
	FileName       string
	FileData       string
	JobStatus      int
	ActiveQueue    []string
	HoldQueue      []string
}

type position struct {
	X  int
	Y  int
	Z  int
	E1 int
}

type movement struct {
	Axis     string
	Distance int `json: ",string"`
	Speed    int `json: ",string"`
}

type tool struct {
	Name  string
	Value int `json: ",string"`
}

type userfile struct {
	Name string
	Data string
}

const (
	DEFBAUD = 115200
	NSF     = "no such file or directory"
	DNC     = "device not configured"
	RM      = "device removed"
)

const (
	IDLE    = (1 << iota)
	RUNNING = (1 << iota)
	PAUSED  = (1 << iota)
)

//  [ TODO ]
//  need to dynamically list out
//  the devices by OS
func getAttachedDevices(existing *map[string]*Device) (string, error) {
	devName := "/dev/tty.usbmodem001" //  POSIX style naming
	if runtime.GOOS == "windows" {
		//  [HACK]
		//  temporary hack for Windows
		//  to let the user create a .txt
		//  file in the data dir for us to
		//  know what the COM port enumerates
		info, err := ioutil.ReadDir("config/")
		if err != nil {
			return "", fmt.Errorf("unable to get COM info from 'config/': %v\n", err)
		}

		found := false
		for _, f := range info {
			if strings.HasPrefix(f.Name(), "__COM") {
				devName = strings.Trim(f.Name(), "__")
				devName = strings.Trim(devName, ".txt")
				found = true
			}
		}

		if !found {
			return "", fmt.Errorf("__COM device not found")
		}
	}

	//  ===
	if (*existing)[devName] != nil {
		//  this means we should be tracking the device
		//  but if the err says that we can't find the device
		//  then we need to let it go
		_, err := serial.OpenPort(devName, DEFBAUD)
		if err != nil && strings.HasSuffix(err.Error(), NSF) {
			delete(*existing, devName)
			return "", fmt.Errorf(devName + " device removed")
		}

		return "", nil
	}

	d, err := serial.OpenPort(devName, DEFBAUD)
	if err != nil {
		return "", err
	}

	info, err := getFirmwareInfo(&d)
	if err != nil {
		return "", err
	}

	if len(info) > 1 {
		pos := position{
			X:  0,
			Y:  0,
			Z:  0,
			E1: 0,
		}

		dev := &Device{
			Name:           devName,
			LineTerminator: models.LINETERMINATOR,
			Baud:           DEFBAUD,
			IODevice:       d,
			MoveSpeed:      0,
			Pos:            pos,
			Homed:          false,
			Greeting:       info,
			JobStatus:      IDLE,
		}

		(*existing)[devName] = dev
		return devName, nil
	}

	return "", nil
}

func getFirmwareInfo(dev *io.ReadWriteCloser) (string, error) {
	resp, err := lobCommand(dev, models.FWMCODE+models.LINETERMINATOR)
	if err != nil {
		return "", err
	}

	return resp, nil
}

func (dev *Device) Do(action string, params string) (*Message, error) {
	if len(action) < 1 || action == "" {
		return nil, nil
	}
	switch action {
	case "move":
		var (
			mvr  movement
			dist int
		)

		if err := json.Unmarshal([]byte(params), &mvr); err != nil {
			return nil, err
		}

		cmd := "G1 " + mvr.Axis
		if mvr.Axis == "E1" {
			//  [ TODO ]
			//  The A6 only has 1 extruder so force to "G1 E"
			//  for the command. Will need to pull from a config
			//  .json file to dynamically manage this
			cmd = "G1 E"
		}

		//  using absolute positioning so the need to track where
		//  the device is in space and append the distance prior
		//  to issuing the cmd

		//  ===[ TODO ]
		//  add device safe-gaurds to prevent
		//  moving too far and get the available
		//  axis from the provided dev
		switch mvr.Axis {
		case "X":
			if dev.Pos.X == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.X += mvr.Distance
			dist = dev.Pos.X
		case "Y":
			if dev.Pos.Y == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.Y += mvr.Distance
			dist = dev.Pos.Y
		case "Z":
			if dev.Pos.Z == 0 && mvr.Distance < 1 && dev.Homed {
				return nil, errors.New("device axis is currently at home")
			}
			dev.Pos.Z += mvr.Distance
			dist = dev.Pos.Z
		case "E1":
			dev.Pos.E1 += mvr.Distance
			dist = dev.Pos.E1
		default:
			return nil, errors.New("invalid axis provided")
		}

		cmd += strconv.Itoa(dist)

		//  check to see if the speed has
		//  changed or not and set accordingly
		if mvr.Speed != dev.MoveSpeed {
			dev.MoveSpeed = mvr.Speed
			cmd += " F" + strconv.Itoa(mvr.Speed)
		}

		//  tag on the device specific line terminator
		//  (e.g. MakiBox A6 == '\r\n')
		cmd += dev.LineTerminator

		//  lob
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "temper":
		var tmp tool
		if err := json.Unmarshal([]byte(params), &tmp); err != nil {
			return nil, err
		}

		//  [ TODO ]
		//  get the proper MCodes from the device config
		cmd := "M140 S" //  defaulting to heated bed for A6
		if tmp.Name == "hotend" {
			cmd = "M104 S"
		}
		cmd += strconv.Itoa(tmp.Value) + dev.LineTerminator

		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "home":
		var mvr movement
		if err := json.Unmarshal([]byte(params), &mvr); err != nil {
			return nil, err
		}

		//  grok the cmd
		cmd := "G28"
		if mvr.Axis != "ALL" {
			cmd += " " + mvr.Axis + "0"
		}

		cmd += dev.LineTerminator

		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}

		//  need to set the position of E to 0 on the device
		if mvr.Axis == "ALL" || mvr.Axis == "E" {
			cmd = "G92 E0" + dev.LineTerminator
			r, e := dev.LobCommand(cmd)
			if e != nil {
				return nil, e
			}
			resp += r
		}

		//  reset position to 0
		if mvr.Axis == "ALL" {
			dev.Pos = position{
				X:  0,
				Y:  0,
				Z:  0,
				E1: 0,
			}
			dev.Homed = true
		} else {
			axis := mvr.Axis
			if axis == "E" {
				axis = "E1"
			}
			reflect.ValueOf(&dev.Pos).Elem().FieldByName(axis).SetInt(0)
		}

		return responseMsg(dev.Name, action, resp), nil

	case "status":
		cmd := "M105" + dev.LineTerminator
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	case "load":
		//  === [ TODO ]
		var gc userfile
		if err := json.Unmarshal([]byte(params), &gc); err != nil {
			return nil, err
		}

		fn := "data/" + gc.Name
		info, _ := os.Stat(fn)
		if info != nil {
			//  this should mean file exists
			//  and for now, we will delete the existing file
			//  of the same name and write the new data
			if err := os.Remove(fn); err != nil {
				return nil, err
			}
		}

		f, err := os.Create(fn)
		if err != nil {
			return nil, err
		}

		if _, err := f.Write([]byte(gc.Data)); err != nil {
			fmt.Println(err)
		}

		dev.FileName, dev.FileData = gc.Name, gc.Data
		return responseMsg(dev.Name, action, "temp file written"), nil

	case "motley":
		if strings.Contains(params, "motorsoff") {
			cmd := "M84" + dev.LineTerminator
			resp, err := dev.LobCommand(cmd)

			if err != nil {
				return nil, err
			}
			return responseMsg(dev.Name, action, resp), nil
		}

	//  manual gcode entered by user
	//  via interactive console
	case "console":
		cmd := params + dev.LineTerminator
		resp, err := dev.LobCommand(cmd)
		if err != nil {
			return nil, err
		}
		return responseMsg(dev.Name, action, resp), nil

	default:
		return nil, errors.New("invalid action: " + action)
	}

	return nil, nil
}

func (dev *Device) LobCommand(cmd string) (string, error) {
	return (lobCommand(&(dev.IODevice), cmd))
}

func lobCommand(dev *io.ReadWriteCloser, cmd string) (string, error) {
	//  check if valid code in device codes ::TODO::
	//  if so, then lob to the device
	n, err := (*dev).Write([]byte(cmd))
	if err != nil {
		return "", err
	}

	if n < 1 {
		return "", errors.New("unable to write to device")
	}

	//  read response from device
	response := "\n"
	for i := 0; i < 20; i++ {
		buf := make([]byte, 255)
		n, err = (*dev).Read(buf)
		if n < 1 {
			fmt.Printf("lobCommand - device did not respond: %d\n", n)
			return "", nil
		}

		response += string(buf[:n])
		if strings.Contains(response, "go") && strings.Contains(response, "ok ") {
			break
		}
	}

	return response, nil
}

func responseMsg(dn string, action string, body string) *Message {
	return &Message{
		Type:       "response",
		DeviceName: dn,
		Action:     action,
		Body:       body,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"unicode/utf8"

	"github.com/charlievieth/gocode"
)

type GoCode struct {
	Autoinst      bool
	InstallSuffix string
	Env           map[string]string
	Home          string
	Dir           string
	Builtins      bool
	Fn            string
	Src           string
	Pos           int
	calltip       bool `json:"-"` // ignore
}

// Separate type so we can init Calltip to true
type GoCodeCalltip struct {
	GoCode
}

func (g *GoCodeCalltip) Init() {
	g.calltip = true
}

type GoCodeResponse struct {
	Candidates []gocode.Candidate
}

// WARN: Add auto install
func (g *GoCode) Call() (interface{}, string) {
	off, err := g.bytePos()
	if err != nil {
		return g.response(nil, err)
	}
	path := g.filepath()
	if g.calltip {
		return g.response(g.calltips(path, []byte(g.Src), off))
	}
	return g.response(g.complete(path, []byte(g.Src), off), nil)
}

func (g *GoCode) response(res []gocode.Candidate, err error) (GoCodeResponse, string) {
	if res == nil || len(res) == 0 {
		if g.Autoinst {
			autoInstall(AutoInstOptions{
				Src:           g.Src,
				Env:           g.Env,
				InstallSuffix: g.InstallSuffix,
			})
		}
		res = []gocode.Candidate{}
	}
	var errStr string
	if err != nil {
		errStr = err.Error()
	}
	return GoCodeResponse{Candidates: res}, errStr
}

// Matching GoSublime's behavior here...
func (g *GoCode) filepath() string {
	if filepath.IsAbs(g.Fn) {
		return filepath.Clean(g.Fn)
	}
	var base string
	if g.Dir != "" {
		base = g.Dir
	} else {
		base = g.Home
	}
	var name string
	if g.Fn != "" {
		name = g.Fn
	} else {
		name = "_.go"
	}
	return filepath.Join(base, name)
}

func (g *GoCode) complete(filename string, src []byte, offset int) []gocode.Candidate {
	conf := gocode.Config{
		GOROOT:        g.GOROOT(),
		GOPATH:        g.GOPATH(),
		InstallSuffix: g.InstallSuffix,
		Builtins:      g.Builtins,
	}
	return conf.Complete(src, filename, offset)
}

func (g *GoCode) GOPATH() string {
	if g.Env != nil {
		if s, ok := g.Env["GOPATH"]; ok && s != "" {
			return s
		}
	}
	return os.Getenv("GOPATH")
}

func (g *GoCode) GOROOT() string {
	if g.Env != nil {
		if s, ok := g.Env["GOROOT"]; ok && s != "" {
			return s
		}
	}
	return runtime.GOROOT()
}

// TODO: cache results and potentially cache AST ad file set.
func (g *GoCode) calltips(filename string, src []byte, offset int) ([]gocode.Candidate, error) {
	fset := token.NewFileSet()
	af, err := parser.ParseFile(fset, filename, src, 0)
	if af == nil {
		return nil, err
	}
	pos, err := g.convertOffset(af, fset, offset)
	if err != nil {
		return nil, err
	}
	id, err := g.findCaller(af, fset, pos)
	if err != nil {
		return nil, err
	}
	end := fset.Position(id.End())
	if !end.IsValid() {
		return nil, errors.New("calltip: invalid end pos")
	}
	if err := g.validLine(fset, pos, end); err != nil {
		return nil, err
	}
	cl := g.complete(filename, src, end.Offset)
	for i := 0; i < len(cl); i++ {
		if strings.EqualFold(id.Name, cl[i].Name) {
			return cl[i : i+1], nil
		}
	}
	return nil, errors.New("calltip: no candidates")
}

// Matching GoSublime's behavior here, see if this can be removed.
func (g *GoCode) validLine(fset *token.FileSet, off token.Pos, end token.Position) error {
	line := fset.Position(off).Line
	if end.Line == line || line == 0 {
		return nil
	}
	return fmt.Errorf("calltip: line mismatch end (%d) line (%d)", end.Line, line)
}

func (g *GoCode) convertOffset(af *ast.File, fset *token.FileSet, offset int) (token.Pos, error) {
	f := fset.File(af.Pos())
	if f == nil {
		return token.NoPos, errors.New("calltip: ast file not in fileset")
	}
	// Prevent Pos from panicking
	if offset > f.Size() {
		return token.NoPos, errors.New("calltip: illegal file offset")
	}
	return f.Pos(offset), nil
}

func (g *GoCode) findCaller(af *ast.File, fset *token.FileSet, pos token.Pos) (*ast.Ident, error) {
	v := calltipVisitor{
		pos:  pos,
		fset: fset,
	}
	ast.Walk(&v, af)
	if v.expr == nil || v.expr.Fun == nil {
		return nil, errors.New("calltip: nil CallExpr")
	}
	switch x := v.expr.Fun.(type) {
	case *ast.Ident:
		return x, nil
	case *ast.SelectorExpr:
		return x.Sel, nil
	}
	return nil, errors.New("calltip: invalid CallExpr.Fun")
}

// calltipVisitor satisfies the ast.Visitor interface.
type calltipVisitor struct {
	pos  token.Pos
	fset *token.FileSet
	expr *ast.CallExpr
}

func (v *calltipVisitor) Visit(node ast.Node) (w ast.Visitor) {
	if node == nil || v.expr != nil {
		return v
	}
	if x, ok := node.(*ast.CallExpr); ok {
		pos := x.Pos()
		end := x.End()
		if pos.IsValid() && end.IsValid() {
			if pos <= v.pos && v.pos <= end {
				v.expr = x
				return nil
			}
		}
	}
	return v
}

func (g *GoCode) bytePos() (int, error) {
	s := g.Src
	off := g.Pos
	if len(s) == 0 || len(s) <= off || off < 0 {
		return -1, errors.New("gocode: nil source")
	}
	i := 0
	var n int
	for n = 0; i < len(s) && n < off; n++ {
		if s[i] < utf8.RuneSelf {
			i++
		} else {
			_, size := utf8.DecodeRuneInString(s[i:])
			i += size
		}
	}
	if n == off && i < len(s) {
		return i, nil
	}
	return -1, fmt.Errorf("gocode: invalid offset: %d", g.Pos)
}

func init() {
	registry.Register("gocode_complete", func(b *Broker) Caller {
		return &GoCode{}
	})

	registry.Register("gocode_calltip", func(b *Broker) Caller {
		return &GoCode{calltip: true}
	})
}

// Initial implementation of calltips cache
/*

import (
	"fmt"
	"sync"

	"github.com/golang/groupcache/lru"
)

type Candidate struct {
	Name  string `json:"name"`
	Type  string `json:"type"`
	Class string `json:"class"`
}

type FileEntry struct {
	Name       string            // file name
	Source     string            // file source
	Candidates map[int]Candidate // decl offset => Candidate
	mu         sync.RWMutex
}

func NewFileEntry(name, source string) *FileEntry {
	return &FileEntry{
		Name:       name,
		Source:     source,
		Candidates: make(map[int]Candidate),
	}
}

func (f *FileEntry) Get(pos int) (c Candidate, ok bool) {
	f.mu.RLock()
	if f.Candidates != nil {
		c, ok = f.Candidates[pos]
	}
	f.mu.RUnlock()
	return
}

func (f *FileEntry) Add(pos int, c Candidate) {
	f.mu.Lock()
	if f.Candidates == nil {
		f.Candidates = make(map[int]Candidate)
	}
	f.Candidates[pos] = c
	f.mu.Unlock()
}

type Cache struct {
	cache *lru.Cache
	mu    sync.Mutex
}

func New() *Cache {
	return &Cache{cache: lru.New(128)}
}

func (c *Cache) Get(name, source string, pos int) (Candidate, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	v, ok := c.cache.Get(name)
	if !ok {
		return Candidate{}, false
	}
	e := v.(*FileEntry)
	if e.Source != source {
		return Candidate{}, false
	}
	return e.Get(pos)
}

func (c *Cache) Add(name, source string, pos int, can Candidate) {
	c.mu.Lock()
	defer c.mu.Unlock()
	var e *FileEntry
	if v, ok := c.cache.Get(name); ok {
		e = v.(*FileEntry)
		if e.Source != source {
			c.cache.Remove(name)
			e = nil
		}
	}
	if e == nil {
		e = NewFileEntry(name, source)
	}
	e.Add(pos, can)
	c.cache.Add(name, e)
}

func main() {
	c := New()
	c.Add("name", "source", 1, Candidate{Name: "c1", Type: "t1", Class: "c1"})
	fmt.Println(c.Get("name", "source", 1))
	c.Add("name", "source new", 1, Candidate{Name: "c1", Type: "t1", Class: "c1"})
	fmt.Println(c.Get("name", "source", 1))
	fmt.Println(c.Get("name", "source new", 1))
}
*/
<commit_msg>gocode: cache calltip responses<commit_after>package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"unicode/utf8"

	"github.com/charlievieth/gocode"
)

type GoCode struct {
	Autoinst      bool
	InstallSuffix string
	Env           map[string]string
	Home          string
	Dir           string
	Builtins      bool
	Fn            string
	Src           string
	Pos           int
	calltip       bool `json:"-"` // ignore
}

// Separate type so we can init Calltip to true
type GoCodeCalltip struct {
	GoCode
}

func (g *GoCodeCalltip) Init() {
	g.calltip = true
}

type GoCodeResponse struct {
	Candidates []gocode.Candidate
}

var lastCalltip struct {
	sync.Mutex
	Path string
	Src  string
	Off  int
	Res  GoCodeResponse
}

func useLastCalltip(path, src string, off int) (res GoCodeResponse, ok bool) {
	lastCalltip.Lock()
	if off == lastCalltip.Off && path == lastCalltip.Path && src == lastCalltip.Src {
		res = lastCalltip.Res
		ok = true
	}
	lastCalltip.Unlock()
	return
}

func setLastCalltip(path, src string, off int, res GoCodeResponse) {
	lastCalltip.Lock()
	if off != lastCalltip.Off || path != lastCalltip.Path || src != lastCalltip.Src {
		lastCalltip.Off = off
		lastCalltip.Path = path
		lastCalltip.Src = src
		lastCalltip.Res = res
	}
	lastCalltip.Unlock()
	return
}

func (g *GoCode) Call() (interface{}, string) {
	off, err := g.bytePos()
	if err != nil {
		return g.response(nil, err, false)
	}
	path := g.filepath()
	if g.calltip {
		if res, ok := useLastCalltip(path, g.Src, off); ok {
			return res, ""
		}
		gr, err := g.calltips(path, []byte(g.Src), off)
		res, errStr := g.response(gr, err, false)
		setLastCalltip(path, g.Src, off, res)
		return res, errStr
	}
	return g.response(g.complete(path, []byte(g.Src), off), nil, true)
}

var NoGocodeCandidates = []gocode.Candidate{}

func (g *GoCode) response(res []gocode.Candidate, err error, install bool) (GoCodeResponse, string) {
	if res == nil || len(res) == 0 {
		if install && g.Autoinst {
			autoInstall(AutoInstOptions{
				Src:           g.Src,
				Env:           g.Env,
				InstallSuffix: g.InstallSuffix,
			})
		}
		res = NoGocodeCandidates
	}
	var errStr string
	if err != nil {
		errStr = err.Error()
	}
	return GoCodeResponse{Candidates: res}, errStr
}

// Matching GoSublime's behavior here...
func (g *GoCode) filepath() string {
	if filepath.IsAbs(g.Fn) {
		return filepath.Clean(g.Fn)
	}
	var base string
	if g.Dir != "" {
		base = g.Dir
	} else {
		base = g.Home
	}
	var name string
	if g.Fn != "" {
		name = g.Fn
	} else {
		name = "_.go"
	}
	return filepath.Join(base, name)
}

func (g *GoCode) complete(filename string, src []byte, offset int) []gocode.Candidate {
	conf := gocode.Config{
		GOROOT:        g.GOROOT(),
		GOPATH:        g.GOPATH(),
		InstallSuffix: g.InstallSuffix,
		Builtins:      g.Builtins,
	}
	return conf.Complete(src, filename, offset)
}

func (g *GoCode) GOPATH() string {
	if g.Env != nil {
		if s, ok := g.Env["GOPATH"]; ok && s != "" {
			return s
		}
	}
	return os.Getenv("GOPATH")
}

func (g *GoCode) GOROOT() string {
	if g.Env != nil {
		if s, ok := g.Env["GOROOT"]; ok && s != "" {
			return s
		}
	}
	return runtime.GOROOT()
}

// TODO: cache results and potentially cache AST ad file set.
func (g *GoCode) calltips(filename string, src []byte, offset int) ([]gocode.Candidate, error) {
	fset := token.NewFileSet()
	af, err := parser.ParseFile(fset, filename, src, 0)
	if af == nil {
		return nil, err
	}
	pos, err := g.convertOffset(af, fset, offset)
	if err != nil {
		return nil, err
	}
	id, err := g.findCaller(af, fset, pos)
	if err != nil {
		return nil, err
	}
	end := fset.Position(id.End())
	if !end.IsValid() {
		return nil, errors.New("calltip: invalid end pos")
	}
	if err := g.validLine(fset, pos, end); err != nil {
		return nil, err
	}
	cl := g.complete(filename, src, end.Offset)
	for i := 0; i < len(cl); i++ {
		if strings.EqualFold(id.Name, cl[i].Name) {
			return cl[i : i+1], nil
		}
	}
	return nil, errors.New("calltip: no candidates")
}

// Matching GoSublime's behavior here, see if this can be removed.
func (g *GoCode) validLine(fset *token.FileSet, off token.Pos, end token.Position) error {
	line := fset.Position(off).Line
	if end.Line == line || line == 0 {
		return nil
	}
	return fmt.Errorf("calltip: line mismatch end (%d) line (%d)", end.Line, line)
}

func (g *GoCode) convertOffset(af *ast.File, fset *token.FileSet, offset int) (token.Pos, error) {
	f := fset.File(af.Pos())
	if f == nil {
		return token.NoPos, errors.New("calltip: ast file not in fileset")
	}
	// Prevent Pos from panicking
	if offset > f.Size() {
		return token.NoPos, errors.New("calltip: illegal file offset")
	}
	return f.Pos(offset), nil
}

func (g *GoCode) findCaller(af *ast.File, fset *token.FileSet, pos token.Pos) (*ast.Ident, error) {
	v := calltipVisitor{
		pos:  pos,
		fset: fset,
	}
	ast.Walk(&v, af)
	if v.expr == nil || v.expr.Fun == nil {
		return nil, errors.New("calltip: nil CallExpr")
	}
	switch x := v.expr.Fun.(type) {
	case *ast.Ident:
		return x, nil
	case *ast.SelectorExpr:
		return x.Sel, nil
	}
	return nil, errors.New("calltip: invalid CallExpr.Fun")
}

// calltipVisitor satisfies the ast.Visitor interface.
type calltipVisitor struct {
	pos  token.Pos
	fset *token.FileSet
	expr *ast.CallExpr
}

func (v *calltipVisitor) Visit(node ast.Node) (w ast.Visitor) {
	if node == nil || v.expr != nil {
		return v
	}
	if x, ok := node.(*ast.CallExpr); ok {
		pos := x.Pos()
		end := x.End()
		if pos.IsValid() && end.IsValid() {
			if pos <= v.pos && v.pos <= end {
				v.expr = x
				return nil
			}
		}
	}
	return v
}

func (g *GoCode) bytePos() (int, error) {
	s := g.Src
	off := g.Pos
	if len(s) == 0 || len(s) <= off || off < 0 {
		return -1, errors.New("gocode: nil source")
	}
	i := 0
	var n int
	for n = 0; i < len(s) && n < off; n++ {
		if s[i] < utf8.RuneSelf {
			i++
		} else {
			_, size := utf8.DecodeRuneInString(s[i:])
			i += size
		}
	}
	if n == off && i < len(s) {
		return i, nil
	}
	return -1, fmt.Errorf("gocode: invalid offset: %d", g.Pos)
}

func init() {
	registry.Register("gocode_complete", func(b *Broker) Caller {
		return &GoCode{}
	})

	registry.Register("gocode_calltip", func(b *Broker) Caller {
		return &GoCode{calltip: true}
	})
}

// Initial implementation of calltips cache
/*

import (
	"fmt"
	"sync"

	"github.com/golang/groupcache/lru"
)

type Candidate struct {
	Name  string `json:"name"`
	Type  string `json:"type"`
	Class string `json:"class"`
}

type FileEntry struct {
	Name       string            // file name
	Source     string            // file source
	Candidates map[int]Candidate // decl offset => Candidate
	mu         sync.RWMutex
}

func NewFileEntry(name, source string) *FileEntry {
	return &FileEntry{
		Name:       name,
		Source:     source,
		Candidates: make(map[int]Candidate),
	}
}

func (f *FileEntry) Get(pos int) (c Candidate, ok bool) {
	f.mu.RLock()
	if f.Candidates != nil {
		c, ok = f.Candidates[pos]
	}
	f.mu.RUnlock()
	return
}

func (f *FileEntry) Add(pos int, c Candidate) {
	f.mu.Lock()
	if f.Candidates == nil {
		f.Candidates = make(map[int]Candidate)
	}
	f.Candidates[pos] = c
	f.mu.Unlock()
}

type Cache struct {
	cache *lru.Cache
	mu    sync.Mutex
}

func New() *Cache {
	return &Cache{cache: lru.New(128)}
}

func (c *Cache) Get(name, source string, pos int) (Candidate, bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	v, ok := c.cache.Get(name)
	if !ok {
		return Candidate{}, false
	}
	e := v.(*FileEntry)
	if e.Source != source {
		return Candidate{}, false
	}
	return e.Get(pos)
}

func (c *Cache) Add(name, source string, pos int, can Candidate) {
	c.mu.Lock()
	defer c.mu.Unlock()
	var e *FileEntry
	if v, ok := c.cache.Get(name); ok {
		e = v.(*FileEntry)
		if e.Source != source {
			c.cache.Remove(name)
			e = nil
		}
	}
	if e == nil {
		e = NewFileEntry(name, source)
	}
	e.Add(pos, can)
	c.cache.Add(name, e)
}

func main() {
	c := New()
	c.Add("name", "source", 1, Candidate{Name: "c1", Type: "t1", Class: "c1"})
	fmt.Println(c.Get("name", "source", 1))
	c.Add("name", "source new", 1, Candidate{Name: "c1", Type: "t1", Class: "c1"})
	fmt.Println(c.Get("name", "source", 1))
	fmt.Println(c.Get("name", "source new", 1))
}
*/
<|endoftext|>


<commit_before>package main

import (
	"go/ast"
	"go/parser"
	"go/token"
	"gosubli.me/something-borrowed/gocode"
	"io/ioutil"
	"path/filepath"
	"strings"
)

type mGocode struct {
	Autoinst bool
	Env      map[string]string
	Home     string
	Dir      string
	Builtins bool
	Fn       string
	Src      string
	Pos      int

	calltip bool
}

type calltipVisitor struct {
	offset int
	fset   *token.FileSet
	x      *ast.CallExpr
}

func (v *calltipVisitor) Visit(node ast.Node) (w ast.Visitor) {
	if node != nil {
		if x, ok := node.(*ast.CallExpr); ok {
			a := v.fset.Position(node.Pos())
			b := v.fset.Position(node.End())

			if (a.IsValid() && v.offset >= a.Offset) && (!b.IsValid() || v.offset <= b.Offset) {
				v.x = x
			}
		}
	}
	return v
}

func (m *mGocode) Call() (interface{}, string) {
	if m.Src == "" {
		// this is here for testing, the client should always send the src
		s, _ := ioutil.ReadFile(m.Fn)
		m.Src = string(s)
	}

	if m.Src == "" {
		return nil, "No source"
	}

	pos := 0
	for i, _ := range m.Src {
		pos += 1
		if pos > m.Pos {
			pos = i
			break
		}
	}

	src := []byte(m.Src)
	fn := m.Fn
	if !filepath.IsAbs(fn) {
		fn = filepath.Join(orString(m.Dir, m.Home), orString(fn, "_.go"))
	}

	res := struct {
		Candidates []gocode.MargoCandidate
	}{}

	if m.calltip {
		res.Candidates = m.calltips(src, fn, pos)
	} else {
		res.Candidates = m.completions(src, fn, pos)
	}

	if m.Autoinst && len(res.Candidates) == 0 {
		autoInstall(AutoInstOptions{
			Src: m.Src,
			Env: m.Env,
		})
	}

	return res, ""
}

func (g *mGocode) completions(src []byte, fn string, pos int) []gocode.MargoCandidate {
	c := gocode.MargoConfig{}
	c.Builtins = g.Builtins
	c.GOROOT, c.GOPATHS = envRootList(g.Env)
	return gocode.Margo.Complete(c, src, fn, pos)
}

func (m *mGocode) calltips(src []byte, fn string, offset int) []gocode.MargoCandidate {
	id, fset, af := identAtOffset(src, offset)
	if id != nil {
		cp := fset.Position(id.End())
		if cp.IsValid() {
			line := offsetLine(fset, af, offset)
			cr := cp.Offset
			cl := m.completions(src, fn, cr)

			if (cp.Line == line || line == 0) && len(cl) > 0 {
				for i, c := range cl {
					if strings.EqualFold(id.Name, c.Name) {
						return cl[i : i+1]
					}
				}
			}
		}
	}

	return []gocode.MargoCandidate{}
}

func identAtOffset(src []byte, offset int) (id *ast.Ident, fset *token.FileSet, af *ast.File) {
	fset = token.NewFileSet()
	af, _ = parser.ParseFile(fset, "<stdin>", src, 0)

	if af == nil {
		return
	}

	vis := &calltipVisitor{
		offset: offset,
		fset:   fset,
	}
	ast.Walk(vis, af)

	if vis.x != nil && vis.x.Fun != nil {
		switch v := vis.x.Fun.(type) {
		case *ast.Ident:
			id = v
		case *ast.SelectorExpr:
			id = v.Sel
		}
	}
	return
}

func offsetLine(fset *token.FileSet, af *ast.File, offset int) (line int) {
	defer func() {
		if err := recover(); err != nil {
			line = 0
		}
	}()
	return fset.File(af.Pos()).Position(token.Pos(offset)).Line
}

func init() {
	registry.Register("gocode_complete", func(b *Broker) Caller {
		return &mGocode{}
	})

	registry.Register("gocode_calltip", func(b *Broker) Caller {
		return &mGocode{calltip: true}
	})
}
<commit_msg>* replace broken byte pos calcuation with a call to bytePos<commit_after>package main

import (
	"go/ast"
	"go/parser"
	"go/token"
	"gosubli.me/something-borrowed/gocode"
	"io/ioutil"
	"path/filepath"
	"strings"
)

type mGocode struct {
	Autoinst bool
	Env      map[string]string
	Home     string
	Dir      string
	Builtins bool
	Fn       string
	Src      string
	Pos      int

	calltip bool
}

type calltipVisitor struct {
	offset int
	fset   *token.FileSet
	x      *ast.CallExpr
}

func (v *calltipVisitor) Visit(node ast.Node) (w ast.Visitor) {
	if node != nil {
		if x, ok := node.(*ast.CallExpr); ok {
			a := v.fset.Position(node.Pos())
			b := v.fset.Position(node.End())

			if (a.IsValid() && v.offset >= a.Offset) && (!b.IsValid() || v.offset <= b.Offset) {
				v.x = x
			}
		}
	}
	return v
}

func (m *mGocode) Call() (interface{}, string) {
	if m.Src == "" {
		// this is here for testing, the client should always send the src
		s, _ := ioutil.ReadFile(m.Fn)
		m.Src = string(s)
	}

	if m.Src == "" {
		return nil, "No source"
	}

	pos := bytePos(m.Src, m.Pos)
	if m.Pos < 0 {
		return nil, "Invalid offset"
	}

	src := []byte(m.Src)
	fn := m.Fn
	if !filepath.IsAbs(fn) {
		fn = filepath.Join(orString(m.Dir, m.Home), orString(fn, "_.go"))
	}

	res := struct {
		Candidates []gocode.MargoCandidate
	}{}

	if m.calltip {
		res.Candidates = m.calltips(src, fn, pos)
	} else {
		res.Candidates = m.completions(src, fn, pos)
	}

	if m.Autoinst && len(res.Candidates) == 0 {
		autoInstall(AutoInstOptions{
			Src: m.Src,
			Env: m.Env,
		})
	}

	return res, ""
}

func (g *mGocode) completions(src []byte, fn string, pos int) []gocode.MargoCandidate {
	c := gocode.MargoConfig{}
	c.Builtins = g.Builtins
	c.GOROOT, c.GOPATHS = envRootList(g.Env)
	return gocode.Margo.Complete(c, src, fn, pos)
}

func (m *mGocode) calltips(src []byte, fn string, offset int) []gocode.MargoCandidate {
	id, fset, af := identAtOffset(src, offset)
	if id != nil {
		cp := fset.Position(id.End())
		if cp.IsValid() {
			line := offsetLine(fset, af, offset)
			cr := cp.Offset
			cl := m.completions(src, fn, cr)

			if (cp.Line == line || line == 0) && len(cl) > 0 {
				for i, c := range cl {
					if strings.EqualFold(id.Name, c.Name) {
						return cl[i : i+1]
					}
				}
			}
		}
	}

	return []gocode.MargoCandidate{}
}

func identAtOffset(src []byte, offset int) (id *ast.Ident, fset *token.FileSet, af *ast.File) {
	fset = token.NewFileSet()
	af, _ = parser.ParseFile(fset, "<stdin>", src, 0)

	if af == nil {
		return
	}

	vis := &calltipVisitor{
		offset: offset,
		fset:   fset,
	}
	ast.Walk(vis, af)

	if vis.x != nil && vis.x.Fun != nil {
		switch v := vis.x.Fun.(type) {
		case *ast.Ident:
			id = v
		case *ast.SelectorExpr:
			id = v.Sel
		}
	}
	return
}

func offsetLine(fset *token.FileSet, af *ast.File, offset int) (line int) {
	defer func() {
		if err := recover(); err != nil {
			line = 0
		}
	}()
	return fset.File(af.Pos()).Position(token.Pos(offset)).Line
}

func init() {
	registry.Register("gocode_complete", func(b *Broker) Caller {
		return &mGocode{}
	})

	registry.Register("gocode_calltip", func(b *Broker) Caller {
		return &mGocode{calltip: true}
	})
}
<|endoftext|>