<commit_before>// Copyright 2013-present Greg Hurrell. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"syscall"
)

type IntFlag struct {
	provided bool
	value    int
}

// From flag.Value interface.
func (f *IntFlag) Set(s string) error {
	i, err := strconv.Atoi(s)
	f.provided = true
	f.value = i
	if err != nil {
		return err
	}
	return nil
}

// From flag.Value interface.
func (f *IntFlag) String() string {
	return strconv.Itoa(f.value)
}

// From json.Unmarsheler interface.
func (f *IntFlag) UnmarshalJSON(b []byte) error {
	i, err := strconv.Atoi(string(b))
	if err != nil {
		return err
	}
	*f = IntFlag{provided: true, value: i}
	return nil
}

type StringFlag struct {
	provided bool
	value    string
}

// From flag.Value interface.
func (f *StringFlag) Set(s string) error {
	f.provided = true
	f.value = s
	return nil
}

// From to flag.Value interface.
func (f *StringFlag) String() string {
	return f.value
}

// From json.Unmarsheler interface.
func (f *StringFlag) UnmarshalJSON(b []byte) error {
	var raw string
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	*f = StringFlag{provided: true, value: raw}
	return nil
}

type Options struct {
	Address    StringFlag
	Config     StringFlag
	Logfile    StringFlag
	Executable StringFlag
	Flags      StringFlag
	Port       IntFlag
}

var version = "unknown"

var config Options   // Options read from disk.
var defaults Options // Default options.
var flags Options    // Options set via commandline flags.
var settings Options // Result of merging: flags > config > defaults.
var showHelp bool
var showVersion bool

func printVersion() {
	fmt.Fprintf(os.Stderr, "clipper version: %s (%s)\n", version, runtime.GOOS)
}

func initFlags() {
	const (
		flagsUsage      = "arguments passed to clipboard executable"
		configFileUsage = "path to (JSON) config file"
		executableUsage = "program called to write to clipboard"
		helpUsage       = "show usage information"
		listenAddrUsage = "address to bind to (default loopback interface)"
		listenPortUsage = "port to listen on"
		logFileUsage    = "path to logfile"
		versionUsage    = "show version information"
	)

	flag.BoolVar(&showHelp, "h", false, helpUsage)
	flag.BoolVar(&showHelp, "help", false, helpUsage)
	flag.Var(&flags.Port, "p", listenPortUsage)
	flag.Var(&flags.Port, "port", listenPortUsage)
	flag.Var(&flags.Address, "a", listenAddrUsage)
	flag.Var(&flags.Address, "address", listenAddrUsage)
	flag.Var(&flags.Config, "c", configFileUsage)
	flag.Var(&flags.Config, "config", configFileUsage)
	flag.Var(&flags.Executable, "e", executableUsage)
	flag.Var(&flags.Executable, "executable", executableUsage)
	flag.Var(&flags.Flags, "f", flagsUsage)
	flag.Var(&flags.Flags, "flags", flagsUsage)
	flag.Var(&flags.Logfile, "l", logFileUsage)
	flag.Var(&flags.Logfile, "logfile", logFileUsage)
	flag.BoolVar(&showVersion, "v", false, versionUsage)
	flag.BoolVar(&showVersion, "version", false, versionUsage)
}

func setDefaults() {
	defaults.Address = StringFlag{value: ""} // IPv4/IPv6 loopback.
	defaults.Port = IntFlag{value: 8377}

	if runtime.GOOS == "linux" {
		defaults.Config = StringFlag{value: "~/.config/clipper/clipper.json"}
		defaults.Logfile = StringFlag{value: "~/.config/clipper/logs/clipper.log"}
		defaults.Executable = StringFlag{value: "xclip"}
		defaults.Flags = StringFlag{value: "-selection clipboard"}
	} else {
		defaults.Config = StringFlag{value: "~/.clipper.json"}
		defaults.Logfile = StringFlag{value: "~/Library/Logs/com.wincent.clipper.log"}
		defaults.Executable = StringFlag{value: "pbcopy"}
		defaults.Flags = StringFlag{value: ""}
	}
}

func mergeSettings() {
	flag.Parse()

	var expandedPath string
	if flags.Config.provided {
		expandedPath = expandPath(flags.Config.value)
	} else {
		expandedPath = expandPath(defaults.Config.value)
	}

	if configData, err := ioutil.ReadFile(expandedPath); err != nil {
		if flags.Config.provided {
			// User explicitly asked for a config file and it wasn't there; fail hard.
			log.Fatal(err)
		} else {
			// Default config file unreadable (probably missing); just warn.
			log.Print(err)
		}
	} else {
		if err = json.Unmarshal(configData, &config); err != nil {
			log.Fatal(err)
		}
	}

	// Final merge into settings object.
	if flags.Address.provided {
		settings.Address = flags.Address
	} else if config.Address.provided {
		settings.Address = config.Address
	} else {
		settings.Address = defaults.Address
	}
	if flags.Logfile.provided {
		settings.Logfile = flags.Logfile
	} else if config.Logfile.provided {
		settings.Logfile = config.Logfile
	} else {
		settings.Logfile = defaults.Logfile
	}
	if flags.Port.provided || config.Port.provided {
		if isPath(settings.Address.value) {
			log.Print("--port option ignored when listening on UNIX domain socket")
		}
	}
	if flags.Port.provided {
		settings.Port = flags.Port
	} else if config.Port.provided {
		settings.Port = config.Port
	} else {
		settings.Port = defaults.Port
	}
	if flags.Executable.provided {
		settings.Executable = flags.Executable
	} else if config.Executable.provided {
		settings.Executable = config.Executable
	} else {
		settings.Executable = defaults.Executable
	}
	if flags.Flags.provided {
		settings.Flags = flags.Flags
	} else if config.Flags.provided {
		settings.Flags = config.Flags
	} else {
		settings.Flags = defaults.Flags
	}
}

func main() {
	syscall.Umask(0077)
	// Set this up before we even know where our logfile is, in case we have to
	// bail early and print something to stderr.
	log.SetPrefix("clipper: ")
	// Set default values per GOOS.
	setDefaults()
	// Setup flags subsystem.
	initFlags()

	flag.Parse()
	if flag.NArg() != 0 {
		// Additional command-line options not supported.
		flag.Usage()
		os.Exit(1)
	}
	if showHelp {
		printVersion()
		flag.Usage()
		os.Exit(0)
	}
	if showVersion {
		printVersion()
		os.Exit(0)
	}

	// Merge flags -> config -> defaults.
	mergeSettings()

	if runtime.GOOS == "linux" {
		configDir := expandPath(filepath.Dir(settings.Logfile.value))
		os.MkdirAll(configDir, 0700)
	}
	expandedPath := expandPath(settings.Logfile.value)
	outfile, err := os.OpenFile(expandedPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()
	log.SetOutput(outfile)

	if _, err := exec.LookPath(settings.Executable.value); err != nil {
		log.Fatal(err)
	}

	var addr string
	var listeners []net.Listener
	if isPath(settings.Address.value) {
		addr = expandPath(settings.Address.value)
	} else {
		addr = settings.Address.value
	}
	if strings.HasPrefix(addr, "/") {
		// Check to see if there is a pre-existing or stale socket present.
		if _, err := os.Stat(addr); !os.IsNotExist(err) {
			// Socket already exists.
			if _, err = net.Dial("unix", addr); err == nil {
				// Socket is live!
				log.Fatal("Live socket already exists at: " + addr)
			}

			// Likely a stale socket left over after a crash.
			log.Print("Dead socket found at: " + addr + " (removing)")
			if err = os.Remove(addr); err != nil {
				log.Fatal(err)
			}
		}

		log.Print("Starting UNIX domain socket server at ", addr)
		listeners = append(listeners, listen("unix", addr, -1))
	} else {
		if addr == "" {
			log.Print("Starting TCP server on loopback interface")
			listeners = append(listeners, listen("tcp4", "127.0.0.1", settings.Port.value))
			listeners = append(listeners, listen("tcp6", "[::1]", settings.Port.value))
		} else {
			log.Print("Starting TCP server on ", addr)
			listeners = append(listeners, listen("tcp", settings.Address.value, settings.Port.value))
		}
	}

	listeners = filter(listeners, func(l net.Listener) bool {
		return l != nil
	})
	if len(listeners) == 0 {
		log.Fatal("Failed to establish a listener")
	}

	for i := range listeners {
		if listeners[i] != nil {
			defer listeners[i].Close()
			go func(listener net.Listener) {
				for {
					conn, err := listener.Accept()
					if err != nil {
						log.Print(err)
						return
					}

					go handleConnection(conn)
				}
			}(listeners[i])
		}
	}

	// Need to catch signals in order for `defer`-ed clean-up items to run.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
	sig := <-c
	log.Print("Got signal ", sig)
}

func listen(listenType string, addr string, port int) net.Listener {
	if port >= 0 {
		addr = fmt.Sprintf("%s:%d", addr, port)
	}
	listener, err := net.Listen(listenType, addr)
	if err != nil {
		log.Print(err)
	}
	return listener
}

func filter(ls []net.Listener, fn func(net.Listener) bool) []net.Listener {
	var out []net.Listener
	for i := range ls {
		if fn(ls[i]) {
			out = append(out, ls[i])
		}
	}
	return out
}

// Returns true for things which look like paths (start with "~", "." or "/").
func isPath(path string) bool {
	return strings.HasPrefix(path, "~") ||
		strings.HasPrefix(path, ".") ||
		strings.HasPrefix(path, "/")
}

func expandPath(path string) string {
	expanded := pathByExpandingTildeInPath(path)
	result, err := filepath.Abs(expanded)
	if err != nil {
		log.Fatal(err)
	}
	return result
}

func pathByExpandingTildeInPath(path string) string {
	if strings.HasPrefix(path, "~") {
		user, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		path = user.HomeDir + path[1:]
	}
	return path
}

func handleConnection(conn net.Conn) {
	defer log.Print("Connection closed")
	defer conn.Close()

	var args []string
	if settings.Flags.value != "" {
		whitespace := regexp.MustCompile("\\s+")
		args = whitespace.Split(strings.TrimSpace(settings.Flags.value), -1)
	}
	cmd := exec.Command(settings.Executable.value, args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] pipe init: %v\n", err)
		return
	}

	if err = cmd.Start(); err != nil {
		log.Printf("[ERROR] process start: %v\n", err)
		return
	}

	if copied, err := io.Copy(stdin, conn); err != nil {
		log.Printf("[ERROR] pipe copy: %v\n", err)
	} else {
		log.Print("Echoed ", copied, " bytes")
	}
	stdin.Close()

	if err = cmd.Wait(); err != nil {
		log.Printf("[ERROR] wait: %v\n", err)
	}
}
<commit_msg>refactor: make log file directory on all platforms<commit_after>// Copyright 2013-present Greg Hurrell. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net"
	"os"
	"os/exec"
	"os/signal"
	"os/user"
	"path/filepath"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"syscall"
)

type IntFlag struct {
	provided bool
	value    int
}

// From flag.Value interface.
func (f *IntFlag) Set(s string) error {
	i, err := strconv.Atoi(s)
	f.provided = true
	f.value = i
	if err != nil {
		return err
	}
	return nil
}

// From flag.Value interface.
func (f *IntFlag) String() string {
	return strconv.Itoa(f.value)
}

// From json.Unmarsheler interface.
func (f *IntFlag) UnmarshalJSON(b []byte) error {
	i, err := strconv.Atoi(string(b))
	if err != nil {
		return err
	}
	*f = IntFlag{provided: true, value: i}
	return nil
}

type StringFlag struct {
	provided bool
	value    string
}

// From flag.Value interface.
func (f *StringFlag) Set(s string) error {
	f.provided = true
	f.value = s
	return nil
}

// From to flag.Value interface.
func (f *StringFlag) String() string {
	return f.value
}

// From json.Unmarsheler interface.
func (f *StringFlag) UnmarshalJSON(b []byte) error {
	var raw string
	if err := json.Unmarshal(b, &raw); err != nil {
		return err
	}
	*f = StringFlag{provided: true, value: raw}
	return nil
}

type Options struct {
	Address    StringFlag
	Config     StringFlag
	Logfile    StringFlag
	Executable StringFlag
	Flags      StringFlag
	Port       IntFlag
}

var version = "unknown"

var config Options   // Options read from disk.
var defaults Options // Default options.
var flags Options    // Options set via commandline flags.
var settings Options // Result of merging: flags > config > defaults.
var showHelp bool
var showVersion bool

func printVersion() {
	fmt.Fprintf(os.Stderr, "clipper version: %s (%s)\n", version, runtime.GOOS)
}

func initFlags() {
	const (
		flagsUsage      = "arguments passed to clipboard executable"
		configFileUsage = "path to (JSON) config file"
		executableUsage = "program called to write to clipboard"
		helpUsage       = "show usage information"
		listenAddrUsage = "address to bind to (default loopback interface)"
		listenPortUsage = "port to listen on"
		logFileUsage    = "path to logfile"
		versionUsage    = "show version information"
	)

	flag.BoolVar(&showHelp, "h", false, helpUsage)
	flag.BoolVar(&showHelp, "help", false, helpUsage)
	flag.Var(&flags.Port, "p", listenPortUsage)
	flag.Var(&flags.Port, "port", listenPortUsage)
	flag.Var(&flags.Address, "a", listenAddrUsage)
	flag.Var(&flags.Address, "address", listenAddrUsage)
	flag.Var(&flags.Config, "c", configFileUsage)
	flag.Var(&flags.Config, "config", configFileUsage)
	flag.Var(&flags.Executable, "e", executableUsage)
	flag.Var(&flags.Executable, "executable", executableUsage)
	flag.Var(&flags.Flags, "f", flagsUsage)
	flag.Var(&flags.Flags, "flags", flagsUsage)
	flag.Var(&flags.Logfile, "l", logFileUsage)
	flag.Var(&flags.Logfile, "logfile", logFileUsage)
	flag.BoolVar(&showVersion, "v", false, versionUsage)
	flag.BoolVar(&showVersion, "version", false, versionUsage)
}

func setDefaults() {
	defaults.Address = StringFlag{value: ""} // IPv4/IPv6 loopback.
	defaults.Port = IntFlag{value: 8377}

	if runtime.GOOS == "linux" {
		defaults.Config = StringFlag{value: "~/.config/clipper/clipper.json"}
		defaults.Logfile = StringFlag{value: "~/.config/clipper/logs/clipper.log"}
		defaults.Executable = StringFlag{value: "xclip"}
		defaults.Flags = StringFlag{value: "-selection clipboard"}
	} else {
		defaults.Config = StringFlag{value: "~/.clipper.json"}
		defaults.Logfile = StringFlag{value: "~/Library/Logs/com.wincent.clipper.log"}
		defaults.Executable = StringFlag{value: "pbcopy"}
		defaults.Flags = StringFlag{value: ""}
	}
}

func mergeSettings() {
	flag.Parse()

	var expandedPath string
	if flags.Config.provided {
		expandedPath = expandPath(flags.Config.value)
	} else {
		expandedPath = expandPath(defaults.Config.value)
	}

	if configData, err := ioutil.ReadFile(expandedPath); err != nil {
		if flags.Config.provided {
			// User explicitly asked for a config file and it wasn't there; fail hard.
			log.Fatal(err)
		} else {
			// Default config file unreadable (probably missing); just warn.
			log.Print(err)
		}
	} else {
		if err = json.Unmarshal(configData, &config); err != nil {
			log.Fatal(err)
		}
	}

	// Final merge into settings object.
	if flags.Address.provided {
		settings.Address = flags.Address
	} else if config.Address.provided {
		settings.Address = config.Address
	} else {
		settings.Address = defaults.Address
	}
	if flags.Logfile.provided {
		settings.Logfile = flags.Logfile
	} else if config.Logfile.provided {
		settings.Logfile = config.Logfile
	} else {
		settings.Logfile = defaults.Logfile
	}
	if flags.Port.provided || config.Port.provided {
		if isPath(settings.Address.value) {
			log.Print("--port option ignored when listening on UNIX domain socket")
		}
	}
	if flags.Port.provided {
		settings.Port = flags.Port
	} else if config.Port.provided {
		settings.Port = config.Port
	} else {
		settings.Port = defaults.Port
	}
	if flags.Executable.provided {
		settings.Executable = flags.Executable
	} else if config.Executable.provided {
		settings.Executable = config.Executable
	} else {
		settings.Executable = defaults.Executable
	}
	if flags.Flags.provided {
		settings.Flags = flags.Flags
	} else if config.Flags.provided {
		settings.Flags = config.Flags
	} else {
		settings.Flags = defaults.Flags
	}
}

func main() {
	syscall.Umask(0077)
	// Set this up before we even know where our logfile is, in case we have to
	// bail early and print something to stderr.
	log.SetPrefix("clipper: ")
	// Set default values per GOOS.
	setDefaults()
	// Setup flags subsystem.
	initFlags()

	flag.Parse()
	if flag.NArg() != 0 {
		// Additional command-line options not supported.
		flag.Usage()
		os.Exit(1)
	}
	if showHelp {
		printVersion()
		flag.Usage()
		os.Exit(0)
	}
	if showVersion {
		printVersion()
		os.Exit(0)
	}

	// Merge flags -> config -> defaults.
	mergeSettings()

	expandedPath := expandPath(settings.Logfile.value)
	logDir := filepath.Dir(expandedPath)
	if err := os.MkdirAll(logDir, 0700); err != nil {
		log.Fatal(err)
	}
	outfile, err := os.OpenFile(expandedPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
	if err != nil {
		log.Fatal(err)
	}
	defer outfile.Close()
	log.SetOutput(outfile)

	if _, err := exec.LookPath(settings.Executable.value); err != nil {
		log.Fatal(err)
	}

	var addr string
	var listeners []net.Listener
	if isPath(settings.Address.value) {
		addr = expandPath(settings.Address.value)
	} else {
		addr = settings.Address.value
	}
	if strings.HasPrefix(addr, "/") {
		// Check to see if there is a pre-existing or stale socket present.
		if _, err := os.Stat(addr); !os.IsNotExist(err) {
			// Socket already exists.
			if _, err = net.Dial("unix", addr); err == nil {
				// Socket is live!
				log.Fatal("Live socket already exists at: " + addr)
			}

			// Likely a stale socket left over after a crash.
			log.Print("Dead socket found at: " + addr + " (removing)")
			if err = os.Remove(addr); err != nil {
				log.Fatal(err)
			}
		}

		log.Print("Starting UNIX domain socket server at ", addr)
		listeners = append(listeners, listen("unix", addr, -1))
	} else {
		if addr == "" {
			log.Print("Starting TCP server on loopback interface")
			listeners = append(listeners, listen("tcp4", "127.0.0.1", settings.Port.value))
			listeners = append(listeners, listen("tcp6", "[::1]", settings.Port.value))
		} else {
			log.Print("Starting TCP server on ", addr)
			listeners = append(listeners, listen("tcp", settings.Address.value, settings.Port.value))
		}
	}

	listeners = filter(listeners, func(l net.Listener) bool {
		return l != nil
	})
	if len(listeners) == 0 {
		log.Fatal("Failed to establish a listener")
	}

	for i := range listeners {
		if listeners[i] != nil {
			defer listeners[i].Close()
			go func(listener net.Listener) {
				for {
					conn, err := listener.Accept()
					if err != nil {
						log.Print(err)
						return
					}

					go handleConnection(conn)
				}
			}(listeners[i])
		}
	}

	// Need to catch signals in order for `defer`-ed clean-up items to run.
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
	sig := <-c
	log.Print("Got signal ", sig)
}

func listen(listenType string, addr string, port int) net.Listener {
	if port >= 0 {
		addr = fmt.Sprintf("%s:%d", addr, port)
	}
	listener, err := net.Listen(listenType, addr)
	if err != nil {
		log.Print(err)
	}
	return listener
}

func filter(ls []net.Listener, fn func(net.Listener) bool) []net.Listener {
	var out []net.Listener
	for i := range ls {
		if fn(ls[i]) {
			out = append(out, ls[i])
		}
	}
	return out
}

// Returns true for things which look like paths (start with "~", "." or "/").
func isPath(path string) bool {
	return strings.HasPrefix(path, "~") ||
		strings.HasPrefix(path, ".") ||
		strings.HasPrefix(path, "/")
}

func expandPath(path string) string {
	expanded := pathByExpandingTildeInPath(path)
	result, err := filepath.Abs(expanded)
	if err != nil {
		log.Fatal(err)
	}
	return result
}

func pathByExpandingTildeInPath(path string) string {
	if strings.HasPrefix(path, "~") {
		user, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		path = user.HomeDir + path[1:]
	}
	return path
}

func handleConnection(conn net.Conn) {
	defer log.Print("Connection closed")
	defer conn.Close()

	var args []string
	if settings.Flags.value != "" {
		whitespace := regexp.MustCompile("\\s+")
		args = whitespace.Split(strings.TrimSpace(settings.Flags.value), -1)
	}
	cmd := exec.Command(settings.Executable.value, args...)
	stdin, err := cmd.StdinPipe()
	if err != nil {
		log.Printf("[ERROR] pipe init: %v\n", err)
		return
	}

	if err = cmd.Start(); err != nil {
		log.Printf("[ERROR] process start: %v\n", err)
		return
	}

	if copied, err := io.Copy(stdin, conn); err != nil {
		log.Printf("[ERROR] pipe copy: %v\n", err)
	} else {
		log.Print("Echoed ", copied, " bytes")
	}
	stdin.Close()

	if err = cmd.Wait(); err != nil {
		log.Printf("[ERROR] wait: %v\n", err)
	}
}
<|endoftext|>


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

package gitlab

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/google/go-querystring/query"
)

const (
	libraryVersion = "0.1"
	defaultBaseURL = "https://gitlab.com/api/v3/"
	userAgent      = "go-gitlab/" + libraryVersion
)

// AccessLevel represents a premission level within GitLab.
//
// GitLab API docs: http://doc.gitlab.com/ce/permissions/permissions.html
type AccessLevel int

// List of available access levels
//
// GitLab API docs: http://doc.gitlab.com/ce/permissions/permissions.html
const (
	GuestPermissions     AccessLevel = 10
	ReporterPermissions  AccessLevel = 20
	DeveloperPermissions AccessLevel = 30
	MasterPermissions    AccessLevel = 40
	OwnerPermission      AccessLevel = 50
)

// VisibilityLevel represents a visibility level within GitLab.
//
// GitLab API docs: http://doc.gitlab.com/ce/...?
type VisibilityLevel int

// List of available visibility levels
//
// GitLab API docs: http://doc.gitlab.com/ce/...?
const (
	PrivateVisibility  VisibilityLevel = 0
	InternalVisibility VisibilityLevel = 10
	PublicVisibility   VisibilityLevel = 20
)

// A Client manages communication with the GitLab API.
type Client struct {
	// HTTP client used to communicate with the API.
	client *http.Client

	// Base URL for API requests. Defaults to the public GitLab API, but can be
	// set to a domain endpoint to use with aself hosted GitLab server. baseURL
	// should always be specified with a trailing slash.
	baseURL *url.URL

	// Private token used to make authenticated API calls.
	token string

	// User agent used when communicating with the GitLab API.
	UserAgent string

	// Services used for talking to different parts of the GitLab API.
	Branches        *BranchesService
	Commits         *CommitsService
	DeployKeys      *DeployKeysService
	Groups          *GroupsService
	Issues          *IssuesService
	Labels          *LabelsService
	MergeRequests   *MergeRequestsService
	Milestones      *MilestonesService
	Namespaces      *NamespacesService
	Notes           *NotesService
	Projects        *ProjectsService
	ProjectSnippets *ProjectSnippetsService
	Repositories    *RepositoriesService
	RepositoryFiles *RepositoryFilesService
	Services        *ServicesService
	Session         *SessionService
	Settings        *SettingsService
	SystemHooks     *SystemHooksService
	Users           *UsersService
}

// ListOptions specifies the optional parameters to various List methods that
// support pagination.
type ListOptions struct {
	// For paginated result sets, page of results to retrieve.
	Page int `url:"page,omitempty"`

	// For paginated result sets, the number of results to include per page.
	PerPage int `url:"per_page,omitempty"`
}

// NewClient returns a new GitLab API client. If a nil httpClient is
// provided, http.DefaultClient will be used. To use API methods which require
// authentication, provide a valid private token.
func NewClient(httpClient *http.Client, token string) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	c := &Client{client: httpClient, token: token, UserAgent: userAgent}
	c.SetBaseURL(defaultBaseURL)

	c.Branches = &BranchesService{client: c}
	c.Commits = &CommitsService{client: c}
	c.DeployKeys = &DeployKeysService{client: c}
	c.Groups = &GroupsService{client: c}
	c.Issues = &IssuesService{client: c}
	c.Labels = &LabelsService{client: c}
	c.MergeRequests = &MergeRequestsService{client: c}
	c.Milestones = &MilestonesService{client: c}
	c.Notes = &NotesService{client: c}
	c.Namespaces = &NamespacesService{client: c}
	c.Projects = &ProjectsService{client: c}
	c.ProjectSnippets = &ProjectSnippetsService{client: c}
	c.Repositories = &RepositoriesService{client: c}
	c.RepositoryFiles = &RepositoryFilesService{client: c}
	c.Services = &ServicesService{client: c}
	c.Session = &SessionService{client: c}
	c.Settings = &SettingsService{client: c}
	c.SystemHooks = &SystemHooksService{client: c}
	c.Users = &UsersService{client: c}

	return c
}

// BaseURL return a copy of the baseURL.
func (c *Client) BaseURL() *url.URL {
	u := *c.baseURL
	return &u
}

// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr
// should always be specified with a trailing slash.
func (c *Client) SetBaseURL(urlStr string) error {
	// Make sure the given URL end with a slash
	if !strings.HasSuffix(urlStr, "/") {
		urlStr += "/"
	}

	var err error
	c.baseURL, err = url.Parse(urlStr)
	if err != nil {
		return err
	}

	// Set the encoded opaque data
	c.baseURL.Opaque = fmt.Sprintf("//%s%s", c.baseURL.Host, c.baseURL.Path)

	return nil
}

// NewRequest creates an API request. A relative URL path can be provided in
// urlStr, in which case it is resolved relative to the base URL of the Client.
// Relative URL paths should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body.
func (c *Client) NewRequest(method, path string, opt interface{}) (*http.Request, error) {
	u := *c.baseURL
	u.Opaque += path

	q, err := query.Values(opt)
	if err != nil {
		return nil, err
	}
	u.RawQuery = q.Encode()

	req := &http.Request{
		Method:     method,
		URL:        &u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       u.Host,
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("PRIVATE-TOKEN", c.token)
	if c.UserAgent != "" {
		req.Header.Set("User-Agent", c.UserAgent)
	}

	return req, nil
}

// Response is a GitLab API response. This wraps the standard http.Response
// returned from GitLab and provides convenient access to things like
// pagination links.
type Response struct {
	*http.Response

	// These fields provide the page values for paginating through a set of
	// results.  Any or all of these may be set to the zero value for
	// responses that are not part of a paginated set, or for which there
	// are no additional pages.

	NextPage  int
	PrevPage  int
	FirstPage int
	LastPage  int
}

// newResponse creats a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.populatePageValues()
	return response
}

// populatePageValues parses the HTTP Link response headers and populates the
// various pagination link values in the Reponse.
func (r *Response) populatePageValues() {
	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
		for _, link := range strings.Split(links[0], ",") {
			segments := strings.Split(strings.TrimSpace(link), ";")

			// link must at least have href and rel
			if len(segments) < 2 {
				continue
			}

			// ensure href is properly formatted
			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
				continue
			}

			// try to pull out page parameter
			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
			if err != nil {
				continue
			}
			page := url.Query().Get("page")
			if page == "" {
				continue
			}

			for _, segment := range segments[1:] {
				switch strings.TrimSpace(segment) {
				case `rel="next"`:
					r.NextPage, _ = strconv.Atoi(page)
				case `rel="prev"`:
					r.PrevPage, _ = strconv.Atoi(page)
				case `rel="first"`:
					r.FirstPage, _ = strconv.Atoi(page)
				case `rel="last"`:
					r.LastPage, _ = strconv.Atoi(page)
				}

			}
		}
	}
}

// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer
// interface, the raw response body will be written to v, without attempting to
// first decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	response := newResponse(resp)

	err = CheckResponse(resp)
	if err != nil {
		// even though there was an error, we still return the response
		// in case the caller wants to inspect it further
		return response, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			io.Copy(w, resp.Body)
		} else {
			err = json.NewDecoder(resp.Body).Decode(v)
		}
	}
	return response, err
}

// Helper function to accept and format both the project ID or name as project
// identifier for all API calls.
func parseID(id interface{}) (string, error) {
	switch v := id.(type) {
	case int:
		return strconv.Itoa(v), nil
	case string:
		return v, nil
	default:
		return "", errors.New("the ID must be an int or a string")
	}
}

// An ErrorResponse reports one or more errors caused by an API request.
//
// GitLab API docs:
// http://doc.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
	Message  string         `json:"message"` // error message
	Errors   []Error        `json:"errors"`  // more detail on individual errors
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %v %+v",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode, r.Message, r.Errors)
}

// An Error reports more details on an individual error in an ErrorResponse.
// These are the possible validation error codes:
//
//     missing:
//         resource does not exist
//     missing_field:
//         a required field on a resource has not been set
//     invalid:
//         the formatting of a field is invalid
//     already_exists:
//         another resource has the same valid as this field
//
// GitLab API docs:
// http://doc.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
type Error struct {
	Resource string `json:"resource"` // resource on which the error occurred
	Field    string `json:"field"`    // field on which the error occurred
	Code     string `json:"code"`     // validation error code
}

func (e *Error) Error() string {
	return fmt.Sprintf("%v error caused by %v field on %v resource",
		e.Code, e.Field, e.Resource)
}

// CheckResponse checks the API response for errors, and returns them if
// present.  A response is considered an error if it has a status code outside
// the 200 range.  API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse.  Any other
// response body will be silently ignored.
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// parseBoolResponse determines the boolean result from a GitLab API response.
// Several GitLab API methods return boolean responses indicated by the HTTP
// status code in the response (true indicated by a 204, false indicated by a
// 404). This helper function will determine that result and hide the 404 error
// if present. Any other error will be returned through as-is.
func parseBoolResponse(err error) (bool, error) {
	if err == nil {
		return true, nil
	}

	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
		// Simply false. In this one case, we do not pass the error through.
		return false, nil
	}

	// some other real error occurred
	return false, err
}

// cloneRequest returns a clone of the provided *http.Request. The clone is a
// shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool {
	p := new(bool)
	*p = v
	return p
}

// Int is a helper routine that allocates a new int32 value
// to store v and returns a pointer to it, but unlike Int32
// its argument value is an int.
func Int(v int) *int {
	p := new(int)
	*p = v
	return p
}

// String is a helper routine that allocates a new string value
// to store v and returns a pointer to it.
func String(v string) *string {
	p := new(string)
	*p = v
	return p
}
<commit_msg>Some error handling fixes<commit_after>//
// Copyright 2015, Sander van Harmelen
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

package gitlab

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"

	"github.com/google/go-querystring/query"
)

const (
	libraryVersion = "0.1"
	defaultBaseURL = "https://gitlab.com/api/v3/"
	userAgent      = "go-gitlab/" + libraryVersion
)

// AccessLevel represents a premission level within GitLab.
//
// GitLab API docs: http://doc.gitlab.com/ce/permissions/permissions.html
type AccessLevel int

// List of available access levels
//
// GitLab API docs: http://doc.gitlab.com/ce/permissions/permissions.html
const (
	GuestPermissions     AccessLevel = 10
	ReporterPermissions  AccessLevel = 20
	DeveloperPermissions AccessLevel = 30
	MasterPermissions    AccessLevel = 40
	OwnerPermission      AccessLevel = 50
)

// VisibilityLevel represents a visibility level within GitLab.
//
// GitLab API docs: http://doc.gitlab.com/ce/...?
type VisibilityLevel int

// List of available visibility levels
//
// GitLab API docs: http://doc.gitlab.com/ce/...?
const (
	PrivateVisibility  VisibilityLevel = 0
	InternalVisibility VisibilityLevel = 10
	PublicVisibility   VisibilityLevel = 20
)

// A Client manages communication with the GitLab API.
type Client struct {
	// HTTP client used to communicate with the API.
	client *http.Client

	// Base URL for API requests. Defaults to the public GitLab API, but can be
	// set to a domain endpoint to use with aself hosted GitLab server. baseURL
	// should always be specified with a trailing slash.
	baseURL *url.URL

	// Private token used to make authenticated API calls.
	token string

	// User agent used when communicating with the GitLab API.
	UserAgent string

	// Services used for talking to different parts of the GitLab API.
	Branches        *BranchesService
	Commits         *CommitsService
	DeployKeys      *DeployKeysService
	Groups          *GroupsService
	Issues          *IssuesService
	Labels          *LabelsService
	MergeRequests   *MergeRequestsService
	Milestones      *MilestonesService
	Namespaces      *NamespacesService
	Notes           *NotesService
	Projects        *ProjectsService
	ProjectSnippets *ProjectSnippetsService
	Repositories    *RepositoriesService
	RepositoryFiles *RepositoryFilesService
	Services        *ServicesService
	Session         *SessionService
	Settings        *SettingsService
	SystemHooks     *SystemHooksService
	Users           *UsersService
}

// ListOptions specifies the optional parameters to various List methods that
// support pagination.
type ListOptions struct {
	// For paginated result sets, page of results to retrieve.
	Page int `url:"page,omitempty"`

	// For paginated result sets, the number of results to include per page.
	PerPage int `url:"per_page,omitempty"`
}

// NewClient returns a new GitLab API client. If a nil httpClient is
// provided, http.DefaultClient will be used. To use API methods which require
// authentication, provide a valid private token.
func NewClient(httpClient *http.Client, token string) *Client {
	if httpClient == nil {
		httpClient = http.DefaultClient
	}

	c := &Client{client: httpClient, token: token, UserAgent: userAgent}
	if err := c.SetBaseURL(defaultBaseURL); err != nil {
		// should never happen since defaultBaseURL is our constant
		panic(err)
	}

	c.Branches = &BranchesService{client: c}
	c.Commits = &CommitsService{client: c}
	c.DeployKeys = &DeployKeysService{client: c}
	c.Groups = &GroupsService{client: c}
	c.Issues = &IssuesService{client: c}
	c.Labels = &LabelsService{client: c}
	c.MergeRequests = &MergeRequestsService{client: c}
	c.Milestones = &MilestonesService{client: c}
	c.Notes = &NotesService{client: c}
	c.Namespaces = &NamespacesService{client: c}
	c.Projects = &ProjectsService{client: c}
	c.ProjectSnippets = &ProjectSnippetsService{client: c}
	c.Repositories = &RepositoriesService{client: c}
	c.RepositoryFiles = &RepositoryFilesService{client: c}
	c.Services = &ServicesService{client: c}
	c.Session = &SessionService{client: c}
	c.Settings = &SettingsService{client: c}
	c.SystemHooks = &SystemHooksService{client: c}
	c.Users = &UsersService{client: c}

	return c
}

// BaseURL return a copy of the baseURL.
func (c *Client) BaseURL() *url.URL {
	u := *c.baseURL
	return &u
}

// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr
// should always be specified with a trailing slash.
func (c *Client) SetBaseURL(urlStr string) error {
	// Make sure the given URL end with a slash
	if !strings.HasSuffix(urlStr, "/") {
		urlStr += "/"
	}

	var err error
	c.baseURL, err = url.Parse(urlStr)
	if err != nil {
		return err
	}

	// Set the encoded opaque data
	c.baseURL.Opaque = fmt.Sprintf("//%s%s", c.baseURL.Host, c.baseURL.Path)

	return nil
}

// NewRequest creates an API request. A relative URL path can be provided in
// urlStr, in which case it is resolved relative to the base URL of the Client.
// Relative URL paths should always be specified without a preceding slash. If
// specified, the value pointed to by body is JSON encoded and included as the
// request body.
func (c *Client) NewRequest(method, path string, opt interface{}) (*http.Request, error) {
	u := *c.baseURL
	u.Opaque += path

	q, err := query.Values(opt)
	if err != nil {
		return nil, err
	}
	u.RawQuery = q.Encode()

	req := &http.Request{
		Method:     method,
		URL:        &u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(http.Header),
		Host:       u.Host,
	}

	req.Header.Set("Accept", "application/json")
	req.Header.Set("PRIVATE-TOKEN", c.token)
	if c.UserAgent != "" {
		req.Header.Set("User-Agent", c.UserAgent)
	}

	return req, nil
}

// Response is a GitLab API response. This wraps the standard http.Response
// returned from GitLab and provides convenient access to things like
// pagination links.
type Response struct {
	*http.Response

	// These fields provide the page values for paginating through a set of
	// results.  Any or all of these may be set to the zero value for
	// responses that are not part of a paginated set, or for which there
	// are no additional pages.

	NextPage  int
	PrevPage  int
	FirstPage int
	LastPage  int
}

// newResponse creats a new Response for the provided http.Response.
func newResponse(r *http.Response) *Response {
	response := &Response{Response: r}
	response.populatePageValues()
	return response
}

// populatePageValues parses the HTTP Link response headers and populates the
// various pagination link values in the Reponse.
func (r *Response) populatePageValues() {
	if links, ok := r.Response.Header["Link"]; ok && len(links) > 0 {
		for _, link := range strings.Split(links[0], ",") {
			segments := strings.Split(strings.TrimSpace(link), ";")

			// link must at least have href and rel
			if len(segments) < 2 {
				continue
			}

			// ensure href is properly formatted
			if !strings.HasPrefix(segments[0], "<") || !strings.HasSuffix(segments[0], ">") {
				continue
			}

			// try to pull out page parameter
			url, err := url.Parse(segments[0][1 : len(segments[0])-1])
			if err != nil {
				continue
			}
			page := url.Query().Get("page")
			if page == "" {
				continue
			}

			for _, segment := range segments[1:] {
				switch strings.TrimSpace(segment) {
				case `rel="next"`:
					r.NextPage, _ = strconv.Atoi(page)
				case `rel="prev"`:
					r.PrevPage, _ = strconv.Atoi(page)
				case `rel="first"`:
					r.FirstPage, _ = strconv.Atoi(page)
				case `rel="last"`:
					r.LastPage, _ = strconv.Atoi(page)
				}

			}
		}
	}
}

// Do sends an API request and returns the API response. The API response is
// JSON decoded and stored in the value pointed to by v, or returned as an
// error if an API error has occurred. If v implements the io.Writer
// interface, the raw response body will be written to v, without attempting to
// first decode it.
func (c *Client) Do(req *http.Request, v interface{}) (*Response, error) {
	resp, err := c.client.Do(req)
	if err != nil {
		return nil, err
	}

	defer resp.Body.Close()

	response := newResponse(resp)

	err = CheckResponse(resp)
	if err != nil {
		// even though there was an error, we still return the response
		// in case the caller wants to inspect it further
		return response, err
	}

	if v != nil {
		if w, ok := v.(io.Writer); ok {
			_, err = io.Copy(w, resp.Body)
		} else {
			err = json.NewDecoder(resp.Body).Decode(v)
		}
	}
	return response, err
}

// Helper function to accept and format both the project ID or name as project
// identifier for all API calls.
func parseID(id interface{}) (string, error) {
	switch v := id.(type) {
	case int:
		return strconv.Itoa(v), nil
	case string:
		return v, nil
	default:
		return "", errors.New("the ID must be an int or a string")
	}
}

// An ErrorResponse reports one or more errors caused by an API request.
//
// GitLab API docs:
// http://doc.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
type ErrorResponse struct {
	Response *http.Response // HTTP response that caused this error
	Message  string         `json:"message"` // error message
	Errors   []Error        `json:"errors"`  // more detail on individual errors
}

func (r *ErrorResponse) Error() string {
	return fmt.Sprintf("%v %v: %d %v %+v",
		r.Response.Request.Method, r.Response.Request.URL,
		r.Response.StatusCode, r.Message, r.Errors)
}

// An Error reports more details on an individual error in an ErrorResponse.
// These are the possible validation error codes:
//
//     missing:
//         resource does not exist
//     missing_field:
//         a required field on a resource has not been set
//     invalid:
//         the formatting of a field is invalid
//     already_exists:
//         another resource has the same valid as this field
//
// GitLab API docs:
// http://doc.gitlab.com/ce/api/README.html#data-validation-and-error-reporting
type Error struct {
	Resource string `json:"resource"` // resource on which the error occurred
	Field    string `json:"field"`    // field on which the error occurred
	Code     string `json:"code"`     // validation error code
}

func (e *Error) Error() string {
	return fmt.Sprintf("%v error caused by %v field on %v resource",
		e.Code, e.Field, e.Resource)
}

// CheckResponse checks the API response for errors, and returns them if
// present.  A response is considered an error if it has a status code outside
// the 200 range.  API error responses are expected to have either no response
// body, or a JSON response body that maps to ErrorResponse.  Any other
// response body will be silently ignored.
func CheckResponse(r *http.Response) error {
	if c := r.StatusCode; 200 <= c && c <= 299 {
		return nil
	}
	errorResponse := &ErrorResponse{Response: r}
	data, err := ioutil.ReadAll(r.Body)
	if err == nil && data != nil {
		json.Unmarshal(data, errorResponse)
	}
	return errorResponse
}

// parseBoolResponse determines the boolean result from a GitLab API response.
// Several GitLab API methods return boolean responses indicated by the HTTP
// status code in the response (true indicated by a 204, false indicated by a
// 404). This helper function will determine that result and hide the 404 error
// if present. Any other error will be returned through as-is.
func parseBoolResponse(err error) (bool, error) {
	if err == nil {
		return true, nil
	}

	if err, ok := err.(*ErrorResponse); ok && err.Response.StatusCode == http.StatusNotFound {
		// Simply false. In this one case, we do not pass the error through.
		return false, nil
	}

	// some other real error occurred
	return false, err
}

// cloneRequest returns a clone of the provided *http.Request. The clone is a
// shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
	// shallow copy of the struct
	r2 := new(http.Request)
	*r2 = *r
	// deep copy of the Header
	r2.Header = make(http.Header)
	for k, s := range r.Header {
		r2.Header[k] = s
	}
	return r2
}

// Bool is a helper routine that allocates a new bool value
// to store v and returns a pointer to it.
func Bool(v bool) *bool {
	p := new(bool)
	*p = v
	return p
}

// Int is a helper routine that allocates a new int32 value
// to store v and returns a pointer to it, but unlike Int32
// its argument value is an int.
func Int(v int) *int {
	p := new(int)
	*p = v
	return p
}

// String is a helper routine that allocates a new string value
// to store v and returns a pointer to it.
func String(v string) *string {
	p := new(string)
	*p = v
	return p
}
<|endoftext|>


<commit_before>package Bloco0

import (
	"github.com/chapzin/parse-efd-fiscal/tools"
	"github.com/jinzhu/gorm"
	"time"
)

// Estrutura criada usando layout Guia Prático EFD-ICMS/IPI – Versão 2.0.20 Atualização: 07/12/2016
type Reg0190 struct {
	gorm.Model
	Reg   string `gorm:"type:varchar(4)"`
	Unid  string `gorm:"type:varchar(6)"`
	Descr string
	DtIni time.Time `gorm:"type:date"`
	DtFin time.Time `gorm:"type:date"`
	Cnpj  string    `gorm:"type:varchar(14)"`
}

func (Reg0190) TableName() string {
	return "reg_0190"
}

type iReg0190 interface {
	GetReg0190() Reg0190
}

// Implementando Interface do Sped Reg0190
type Reg0190Sped struct {
	Ln      []string
	Reg0000 Reg0000
}

func (s Reg0190Sped) GetReg0190() Reg0190 {
	reg0190 := Reg0190{
		Reg:   s.Ln[1],
		Unid:  s.Ln[2],
		Descr: s.Ln[3],
		DtIni: s.Reg0000.DtIni,
		DtFin: s.Reg0000.DtFin,
		Cnpj:  s.Reg0000.Cnpj,
	}
	return reg0190
}

type Reg0190Xml struct {
	Data string
}

func (x Reg0190Xml) GetReg0190() Reg0190 {
	reg0190 := Reg0190{
		Reg:   "0190",
		Unid:  x.Data,
		Descr: "Importado Xml",
		DtIni: tools.ConvertDataNull(),
		DtFin: tools.ConvertDataNull(),
		Cnpj:  "",
	}
	return reg0190
}

func CreateReg0190(read iReg0190) Reg0190 {
	return read.GetReg0190()
}
<commit_msg>good build pass<commit_after>package Bloco0

import (
	"github.com/chapzin/parse-efd-fiscal/tools"
	"github.com/jinzhu/gorm"
	"time"
)

// Estrutura criada usando layout Guia Prático EFD-ICMS/IPI – Versão 2.0.20 Atualização: 07/12/2016
// Estrutura modelo do banco de dados Registro 0190
type Reg0190 struct {
	gorm.Model
	Reg   string `gorm:"type:varchar(4)"`
	Unid  string `gorm:"type:varchar(6)"`
	Descr string
	DtIni time.Time `gorm:"type:date"`
	DtFin time.Time `gorm:"type:date"`
	Cnpj  string    `gorm:"type:varchar(14)"`
}

func (Reg0190) TableName() string {
	return "reg_0190"
}

type iReg0190 interface {
	GetReg0190() Reg0190
}

// Implementando Interface do Sped Reg0190
type Reg0190Sped struct {
	Ln      []string
	Reg0000 Reg0000
}

func (s Reg0190Sped) GetReg0190() Reg0190 {
	reg0190 := Reg0190{
		Reg:   s.Ln[1],
		Unid:  s.Ln[2],
		Descr: s.Ln[3],
		DtIni: s.Reg0000.DtIni,
		DtFin: s.Reg0000.DtFin,
		Cnpj:  s.Reg0000.Cnpj,
	}
	return reg0190
}

type Reg0190Xml struct {
	Data string
}

func (x Reg0190Xml) GetReg0190() Reg0190 {
	reg0190 := Reg0190{
		Reg:   "0190",
		Unid:  x.Data,
		Descr: "Importado Xml",
		DtIni: tools.ConvertDataNull(),
		DtFin: tools.ConvertDataNull(),
		Cnpj:  "",
	}
	return reg0190
}

func CreateReg0190(read iReg0190) Reg0190 {
	return read.GetReg0190()
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"log"
	"strconv"
	"time"
)

const (
	BTCMARKETS_API_URL             = "https://api.btcmarkets.net"
	BTCMARKETS_API_VERSION         = "0"
	BTCMARKETS_ACCOUNT_BALANCE     = "/account/balance"
	BTCMARKETS_ORDER_CREATE        = "/order/create"
	BTCMARKETS_ORDER_CANCEL        = "/order/cancel"
	BTCMARKETS_ORDER_HISTORY       = "/order/history"
	BTCMARKETS_ORDER_OPEN          = "/order/open"
	BTCMARKETS_ORDER_TRADE_HISTORY = "/order/trade/history"
	BTCMARKETS_ORDER_DETAIL        = "/order/detail"
)

type BTCMarkets struct {
	Name                    string
	Enabled                 bool
	Verbose                 bool
	Websocket               bool
	RESTPollingDelay        time.Duration
	Fee                     float64
	Ticker                  map[string]BTCMarketsTicker
	AuthenticatedAPISupport bool
	APIKey, APISecret       string
	BaseCurrencies          []string
	AvailablePairs          []string
	EnabledPairs            []string
}

type BTCMarketsTicker struct {
	BestBID    float64
	BestAsk    float64
	LastPrice  float64
	Currency   string
	Instrument string
	Timestamp  int64
}

type BTCMarketsTrade struct {
	TradeID int64   `json:"tid"`
	Amount  float64 `json:"amount"`
	Price   float64 `json:"price"`
	Date    int64   `json:"date"`
}

type BTCMarketsOrderbook struct {
	Currency   string      `json:"currency"`
	Instrument string      `json:"instrument"`
	Timestamp  int64       `json:"timestamp"`
	Asks       [][]float64 `json:"asks"`
	Bids       [][]float64 `json:"bids"`
}

type BTCMarketsTradeResponse struct {
	ID           int64   `json:"id"`
	CreationTime float64 `json:"creationTime"`
	Description  string  `json:"description"`
	Price        float64 `json:"price"`
	Volume       float64 `json:"volume"`
	Fee          float64 `json:"fee"`
}

type BTCMarketsOrderResponse struct {
	ID              float64 `json:"id"`
	Currency        string  `json:"currency"`
	Instrument      string  `json:"instrument"`
	OrderSide       string  `json:"orderSide"`
	OrderType       string  `json:"ordertype"`
	CreationTime    float64 `json:"creationTime"`
	Status          string  `json:"status"`
	ErrorMessage    string  `json:"errorMessage"`
	Price           float64 `json:"price"`
	Volume          float64 `json:"volume"`
	OpenVolume      float64 `json:"openVolume"`
	ClientRequestId string  `json:"clientRequestId"`
}

func (b *BTCMarkets) SetDefaults() {
	b.Name = "BTC Markets"
	b.Enabled = true
	b.Fee = 0.85
	b.Verbose = false
	b.Websocket = false
	b.RESTPollingDelay = 10
	b.Ticker = make(map[string]BTCMarketsTicker)
}

func (b *BTCMarkets) GetName() string {
	return b.Name
}

func (b *BTCMarkets) SetEnabled(enabled bool) {
	b.Enabled = enabled
}

func (b *BTCMarkets) IsEnabled() bool {
	return b.Enabled
}

func (b *BTCMarkets) SetAPIKeys(apiKey, apiSecret string) {
	if !b.AuthenticatedAPISupport {
		return
	}

	b.APIKey = apiKey
	result, err := Base64Decode(apiSecret)

	if err != nil {
		log.Printf("%s unable to decode secret key.\n", b.GetName())
		b.Enabled = false
		return
	}

	b.APISecret = string(result)
}

func (b *BTCMarkets) GetFee() float64 {
	return b.Fee
}

func (b *BTCMarkets) Run() {
	if b.Verbose {
		log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
		log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
	}

	for b.Enabled {
		for _, x := range b.EnabledPairs {
			currency := x
			go func() {
				ticker, err := b.GetTicker(currency)
				if err != nil {
					log.Println(err)
					return
				}
				b.Ticker[currency] = ticker
				BTCMarketsLastUSD, _ := ConvertCurrency(ticker.LastPrice, "AUD", "USD")
				BTCMarketsBestBidUSD, _ := ConvertCurrency(ticker.BestBID, "AUD", "USD")
				BTCMarketsBestAskUSD, _ := ConvertCurrency(ticker.BestAsk, "AUD", "USD")
				log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", currency, BTCMarketsLastUSD, ticker.LastPrice, BTCMarketsBestBidUSD, ticker.BestBID, BTCMarketsBestAskUSD, ticker.BestAsk)
				AddExchangeInfo(b.GetName(), currency[0:3], currency[3:], ticker.LastPrice, 0)
				AddExchangeInfo(b.GetName(), currency[0:3], "USD", BTCMarketsLastUSD, 0)
			}()
		}
		time.Sleep(time.Second * b.RESTPollingDelay)
	}
}

func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) {
	ticker := BTCMarketsTicker{}
	path := fmt.Sprintf("/market/%s/AUD/tick", symbol)
	err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &ticker)
	if err != nil {
		return BTCMarketsTicker{}, err
	}
	return ticker, nil
}

func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) {
	orderbook := BTCMarketsOrderbook{}
	path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol)
	err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &orderbook)
	if err != nil {
		return BTCMarketsOrderbook{}, err
	}
	return orderbook, nil
}

func (b *BTCMarkets) GetTrades(symbol, since string) ([]BTCMarketsTrade, error) {
	trades := []BTCMarketsTrade{}
	path := ""
	if len(since) > 0 {
		path = fmt.Sprintf("/market/%s/AUD/trades?since=%s", symbol, since)
	} else {
		path = fmt.Sprintf("/market/%s/AUD/trades", symbol)
	}
	err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &trades)
	if err != nil {
		return nil, err
	}
	return trades, nil
}

func (b *BTCMarkets) Order(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) {
	type Order struct {
		Currency        string `json:"currency"`
		Instrument      string `json:"instrument"`
		Price           int64  `json:"price"`
		Volume          int64  `json:"volume"`
		OrderSide       string `json:"orderSide"`
		OrderType       string `json:"ordertype"`
		ClientRequestId string `json:"clientRequestId"`
	}
	order := Order{}
	order.Currency = currency
	order.Instrument = instrument
	order.Price = price
	order.Volume = amount
	order.OrderSide = orderSide
	order.OrderType = orderType
	order.ClientRequestId = clientReq

	JSONPayload, err := JSONEncode(order)
	if err != nil {
		return 0, err
	}

	type Response struct {
		Success         bool   `json:"success"`
		ErrorCode       int    `json:"errorCode"`
		ErrorMessage    string `json:"errorMessage"`
		ID              int    `json:"id"`
		ClientRequestID string `json:"clientRequestId"`
	}
	var resp Response

	err = b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CREATE, JSONPayload, &resp)

	if err != nil {
		return 0, err
	}

	if !resp.Success {
		return 0, fmt.Errorf("%s Unable to place order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
	}
	return resp.ID, nil
}

func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) {
	type CancelOrder struct {
		OrderIDs []int64 `json:"orderIds"`
	}
	orders := CancelOrder{}
	orders.OrderIDs = append(orders.OrderIDs, orderID...)

	JSONPayload, err := JSONEncode(orders)
	if err != nil {
		return false, err
	}

	type Response struct {
		Success      bool   `json:"success"`
		ErrorCode    int    `json:"errorCode"`
		ErrorMessage string `json:"errorMessage"`
		Responses    []struct {
			Success      bool   `json:"success"`
			ErrorCode    int    `json:"errorCode"`
			ErrorMessage string `json:"errorMessage"`
			ID           int64  `json:"id"`
		}
		ClientRequestID string `json:"clientRequestId"`
	}
	var resp Response

	err = b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CANCEL, JSONPayload, &resp)

	if err != nil {
		return false, err
	}

	if !resp.Success {
		return false, fmt.Errorf("%s Unable to cancel order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
	}

	ordersToBeCancelled := len(orderID)
	ordersCancelled := 0
	for _, y := range resp.Responses {
		if y.Success {
			ordersCancelled++
			log.Printf("%s Cancelled order %d.\n", b.GetName(), y.ID)
		} else {
			log.Printf("%s Unable to cancel order %d. Error message: %s\n", b.GetName(), y.ID, y.ErrorMessage)
		}
	}

	if ordersCancelled == ordersToBeCancelled {
		return true, nil
	} else {
		return false, fmt.Errorf("%s Unable to cancel order(s).", b.GetName())
	}
}

func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) {
	request := make(map[string]interface{})
	request["currency"] = currency
	request["instrument"] = instrument
	request["limit"] = limit
	request["since"] = since

	JSONPayload, err := JSONEncode(request)
	if err != nil {
		log.Println(err)
		return
	}

	path := BTCMARKETS_ORDER_OPEN
	if historic {
		path = BTCMARKETS_ORDER_HISTORY
	}

	err = b.SendAuthenticatedRequest("POST", path, JSONPayload, nil)

	if err != nil {
		log.Println(err)
	}
}

func (b *BTCMarkets) GetOrderDetail(orderID []int64) {
	type OrderDetail struct {
		OrderIDs []int64 `json:"orderIds"`
	}
	orders := OrderDetail{}
	orders.OrderIDs = append(orders.OrderIDs, orderID...)

	JSONPayload, err := JSONEncode(orders)
	if err != nil {
		log.Println(err)
		return
	}

	err = b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_DETAIL, JSONPayload, nil)

	if err != nil {
		log.Println(err)
	}
}

func (b *BTCMarkets) GetAccountBalance() {
	type Balance struct {
		Balance      float64 `json:"balance"`
		PendingFunds float64 `json:"pendingFunds"`
		Currency     string  `json:"currency"`
	}

	balance := []Balance{}
	err := b.SendAuthenticatedRequest("GET", BTCMARKETS_ACCOUNT_BALANCE, nil, &balance)

	if err != nil {
		log.Println(err)
	}
}

func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data []byte, result interface{}) error {
	nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
	request := ""

	if data != nil {
		request = path + "\n" + nonce + "\n" + string(data)
	} else {
		request = path + "\n" + nonce + "\n"
	}

	hmac := GetHMAC(HASH_SHA512, []byte(request), []byte(b.APISecret))

	if b.Verbose {
		log.Printf("Sending %s request to URL %s with params %s\n", reqType, BTCMARKETS_API_URL+path, request)
	}

	headers := make(map[string]string)
	headers["Accept"] = "application/json"
	headers["Accept-Charset"] = "UTF-8"
	headers["Content-Type"] = "application/json"
	headers["apikey"] = b.APIKey
	headers["timestamp"] = nonce
	headers["signature"] = Base64Encode(hmac)

	resp, err := SendHTTPRequest(reqType, BTCMARKETS_API_URL+path, headers, bytes.NewBuffer(data))

	if err != nil {
		return err
	}

	if b.Verbose {
		log.Printf("Recieved raw: %s\n", resp)
	}

	err = JSONDecode([]byte(resp), &result)

	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Improved BTCMarkets API support.<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"log"
	"net/url"
	"strconv"
	"time"
)

const (
	BTCMARKETS_API_URL             = "https://api.btcmarkets.net"
	BTCMARKETS_API_VERSION         = "0"
	BTCMARKETS_ACCOUNT_BALANCE     = "/account/balance"
	BTCMARKETS_ORDER_CREATE        = "/order/create"
	BTCMARKETS_ORDER_CANCEL        = "/order/cancel"
	BTCMARKETS_ORDER_HISTORY       = "/order/history"
	BTCMARKETS_ORDER_OPEN          = "/order/open"
	BTCMARKETS_ORDER_TRADE_HISTORY = "/order/trade/history"
	BTCMARKETS_ORDER_DETAIL        = "/order/detail"
	BTCMARKETS_SATOSHI_PER_BTC     = 100000000
)

type BTCMarkets struct {
	Name                    string
	Enabled                 bool
	Verbose                 bool
	Websocket               bool
	RESTPollingDelay        time.Duration
	Fee                     float64
	Ticker                  map[string]BTCMarketsTicker
	AuthenticatedAPISupport bool
	APIKey, APISecret       string
	BaseCurrencies          []string
	AvailablePairs          []string
	EnabledPairs            []string
}

type BTCMarketsTicker struct {
	BestBID    float64
	BestAsk    float64
	LastPrice  float64
	Currency   string
	Instrument string
	Timestamp  int64
}

type BTCMarketsTrade struct {
	TradeID int64   `json:"tid"`
	Amount  float64 `json:"amount"`
	Price   float64 `json:"price"`
	Date    int64   `json:"date"`
}

type BTCMarketsOrderbook struct {
	Currency   string      `json:"currency"`
	Instrument string      `json:"instrument"`
	Timestamp  int64       `json:"timestamp"`
	Asks       [][]float64 `json:"asks"`
	Bids       [][]float64 `json:"bids"`
}

type BTCMarketsTradeResponse struct {
	ID           int64   `json:"id"`
	CreationTime float64 `json:"creationTime"`
	Description  string  `json:"description"`
	Price        float64 `json:"price"`
	Volume       float64 `json:"volume"`
	Fee          float64 `json:"fee"`
}

type BTCMarketsOrder struct {
	ID              int64                     `json:"id"`
	Currency        string                    `json:"currency"`
	Instrument      string                    `json:"instrument"`
	OrderSide       string                    `json:"orderSide"`
	OrderType       string                    `json:"ordertype"`
	CreationTime    float64                   `json:"creationTime"`
	Status          string                    `json:"status"`
	ErrorMessage    string                    `json:"errorMessage"`
	Price           float64                   `json:"price"`
	Volume          float64                   `json:"volume"`
	OpenVolume      float64                   `json:"openVolume"`
	ClientRequestId string                    `json:"clientRequestId"`
	Trades          []BTCMarketsTradeResponse `json:"trades"`
}

func (b *BTCMarkets) SetDefaults() {
	b.Name = "BTC Markets"
	b.Enabled = true
	b.Fee = 0.85
	b.Verbose = false
	b.Websocket = false
	b.RESTPollingDelay = 10
	b.Ticker = make(map[string]BTCMarketsTicker)
}

func (b *BTCMarkets) GetName() string {
	return b.Name
}

func (b *BTCMarkets) SetEnabled(enabled bool) {
	b.Enabled = enabled
}

func (b *BTCMarkets) IsEnabled() bool {
	return b.Enabled
}

func (b *BTCMarkets) SetAPIKeys(apiKey, apiSecret string) {
	if !b.AuthenticatedAPISupport {
		return
	}

	b.APIKey = apiKey
	result, err := Base64Decode(apiSecret)

	if err != nil {
		log.Printf("%s unable to decode secret key.\n", b.GetName())
		b.Enabled = false
		return
	}

	b.APISecret = string(result)
}

func (b *BTCMarkets) GetFee() float64 {
	return b.Fee
}

func (b *BTCMarkets) Run() {
	if b.Verbose {
		log.Printf("%s polling delay: %ds.\n", b.GetName(), b.RESTPollingDelay)
		log.Printf("%s %d currencies enabled: %s.\n", b.GetName(), len(b.EnabledPairs), b.EnabledPairs)
	}

	for b.Enabled {
		for _, x := range b.EnabledPairs {
			currency := x
			go func() {
				ticker, err := b.GetTicker(currency)
				if err != nil {
					log.Println(err)
					return
				}
				b.Ticker[currency] = ticker
				BTCMarketsLastUSD, _ := ConvertCurrency(ticker.LastPrice, "AUD", "USD")
				BTCMarketsBestBidUSD, _ := ConvertCurrency(ticker.BestBID, "AUD", "USD")
				BTCMarketsBestAskUSD, _ := ConvertCurrency(ticker.BestAsk, "AUD", "USD")
				log.Printf("BTC Markets %s: Last %f (%f) Bid %f (%f) Ask %f (%f)\n", currency, BTCMarketsLastUSD, ticker.LastPrice, BTCMarketsBestBidUSD, ticker.BestBID, BTCMarketsBestAskUSD, ticker.BestAsk)
				AddExchangeInfo(b.GetName(), currency[0:3], currency[3:], ticker.LastPrice, 0)
				AddExchangeInfo(b.GetName(), currency[0:3], "USD", BTCMarketsLastUSD, 0)
			}()
		}
		time.Sleep(time.Second * b.RESTPollingDelay)
	}
}

func (b *BTCMarkets) GetTicker(symbol string) (BTCMarketsTicker, error) {
	ticker := BTCMarketsTicker{}
	path := fmt.Sprintf("/market/%s/AUD/tick", symbol)
	err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &ticker)
	if err != nil {
		return BTCMarketsTicker{}, err
	}
	return ticker, nil
}

func (b *BTCMarkets) GetOrderbook(symbol string) (BTCMarketsOrderbook, error) {
	orderbook := BTCMarketsOrderbook{}
	path := fmt.Sprintf("/market/%s/AUD/orderbook", symbol)
	err := SendHTTPGetRequest(BTCMARKETS_API_URL+path, true, &orderbook)
	if err != nil {
		return BTCMarketsOrderbook{}, err
	}
	return orderbook, nil
}

func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]BTCMarketsTrade, error) {
	trades := []BTCMarketsTrade{}
	path := EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", BTCMARKETS_API_URL, symbol), values)
	err := SendHTTPGetRequest(path, true, &trades)
	if err != nil {
		return nil, err
	}
	return trades, nil
}

func (b *BTCMarkets) Order(currency, instrument string, price, amount int64, orderSide, orderType, clientReq string) (int, error) {
	type Order struct {
		Currency        string `json:"currency"`
		Instrument      string `json:"instrument"`
		Price           int64  `json:"price"`
		Volume          int64  `json:"volume"`
		OrderSide       string `json:"orderSide"`
		OrderType       string `json:"ordertype"`
		ClientRequestId string `json:"clientRequestId"`
	}
	order := Order{}
	order.Currency = currency
	order.Instrument = instrument
	order.Price = price * BTCMARKETS_SATOSHI_PER_BTC
	order.Volume = amount * BTCMARKETS_SATOSHI_PER_BTC
	order.OrderSide = orderSide
	order.OrderType = orderType
	order.ClientRequestId = clientReq

	type Response struct {
		Success         bool   `json:"success"`
		ErrorCode       int    `json:"errorCode"`
		ErrorMessage    string `json:"errorMessage"`
		ID              int    `json:"id"`
		ClientRequestID string `json:"clientRequestId"`
	}
	var resp Response

	err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CREATE, order, &resp)

	if err != nil {
		return 0, err
	}

	if !resp.Success {
		return 0, fmt.Errorf("%s Unable to place order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
	}
	return resp.ID, nil
}

func (b *BTCMarkets) CancelOrder(orderID []int64) (bool, error) {
	type CancelOrder struct {
		OrderIDs []int64 `json:"orderIds"`
	}
	orders := CancelOrder{}
	orders.OrderIDs = append(orders.OrderIDs, orderID...)

	type Response struct {
		Success      bool   `json:"success"`
		ErrorCode    int    `json:"errorCode"`
		ErrorMessage string `json:"errorMessage"`
		Responses    []struct {
			Success      bool   `json:"success"`
			ErrorCode    int    `json:"errorCode"`
			ErrorMessage string `json:"errorMessage"`
			ID           int64  `json:"id"`
		}
		ClientRequestID string `json:"clientRequestId"`
	}
	var resp Response

	err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_CANCEL, orders, &resp)

	if err != nil {
		return false, err
	}

	if !resp.Success {
		return false, fmt.Errorf("%s Unable to cancel order. Error message: %s\n", b.GetName(), resp.ErrorMessage)
	}

	ordersToBeCancelled := len(orderID)
	ordersCancelled := 0
	for _, y := range resp.Responses {
		if y.Success {
			ordersCancelled++
			log.Printf("%s Cancelled order %d.\n", b.GetName(), y.ID)
		} else {
			log.Printf("%s Unable to cancel order %d. Error message: %s\n", b.GetName(), y.ID, y.ErrorMessage)
		}
	}

	if ordersCancelled == ordersToBeCancelled {
		return true, nil
	} else {
		return false, fmt.Errorf("%s Unable to cancel order(s).", b.GetName())
	}
}

func (b *BTCMarkets) GetOrders(currency, instrument string, limit, since int64, historic bool) ([]BTCMarketsOrder, error) {
	request := make(map[string]interface{})
	request["currency"] = currency
	request["instrument"] = instrument
	request["limit"] = limit
	request["since"] = since

	path := BTCMARKETS_ORDER_OPEN
	if historic {
		path = BTCMARKETS_ORDER_HISTORY
	}

	type response struct {
		Success      bool              `json:"success"`
		ErrorCode    int               `json:"errorCode"`
		ErrorMessage string            `json:"errorMessage"`
		Orders       []BTCMarketsOrder `json:"orders"`
	}

	resp := response{}
	err := b.SendAuthenticatedRequest("POST", path, request, &resp)

	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, errors.New(resp.ErrorMessage)
	}

	for i := range resp.Orders {
		resp.Orders[i].Price = resp.Orders[i].Price / BTCMARKETS_SATOSHI_PER_BTC
		resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / BTCMARKETS_SATOSHI_PER_BTC
		resp.Orders[i].Volume = resp.Orders[i].Volume / BTCMARKETS_SATOSHI_PER_BTC

		for x := range resp.Orders[i].Trades {
			resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / BTCMARKETS_SATOSHI_PER_BTC
			resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / BTCMARKETS_SATOSHI_PER_BTC
			resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / BTCMARKETS_SATOSHI_PER_BTC
		}
	}
	return resp.Orders, nil
}

func (b *BTCMarkets) GetOrderDetail(orderID []int64) ([]BTCMarketsOrder, error) {
	type OrderDetail struct {
		OrderIDs []int64 `json:"orderIds"`
	}
	orders := OrderDetail{}
	orders.OrderIDs = append(orders.OrderIDs, orderID...)

	type response struct {
		Success      bool              `json:"success"`
		ErrorCode    int               `json:"errorCode"`
		ErrorMessage string            `json:"errorMessage"`
		Orders       []BTCMarketsOrder `json:"orders"`
	}

	resp := response{}
	err := b.SendAuthenticatedRequest("POST", BTCMARKETS_ORDER_DETAIL, orders, &resp)

	if err != nil {
		return nil, err
	}

	if !resp.Success {
		return nil, errors.New(resp.ErrorMessage)
	}

	for i := range resp.Orders {
		resp.Orders[i].Price = resp.Orders[i].Price / BTCMARKETS_SATOSHI_PER_BTC
		resp.Orders[i].OpenVolume = resp.Orders[i].OpenVolume / BTCMARKETS_SATOSHI_PER_BTC
		resp.Orders[i].Volume = resp.Orders[i].Volume / BTCMARKETS_SATOSHI_PER_BTC

		for x := range resp.Orders[i].Trades {
			resp.Orders[i].Trades[x].Fee = resp.Orders[i].Trades[x].Fee / BTCMARKETS_SATOSHI_PER_BTC
			resp.Orders[i].Trades[x].Price = resp.Orders[i].Trades[x].Price / BTCMARKETS_SATOSHI_PER_BTC
			resp.Orders[i].Trades[x].Volume = resp.Orders[i].Trades[x].Volume / BTCMARKETS_SATOSHI_PER_BTC
		}
	}
	return resp.Orders, nil
}

type BTCMarketsAccountBalance struct {
	Balance      float64 `json:"balance"`
	PendingFunds float64 `json:"pendingFunds"`
	Currency     string  `json:"currency"`
}

func (b *BTCMarkets) GetAccountBalance() ([]BTCMarketsAccountBalance, error) {
	balance := []BTCMarketsAccountBalance{}
	err := b.SendAuthenticatedRequest("GET", BTCMARKETS_ACCOUNT_BALANCE, nil, &balance)

	if err != nil {
		return nil, err
	}

	for i := range balance {
		if balance[i].Currency == "LTC" || balance[i].Currency == "BTC" {
			balance[i].Balance = balance[i].Balance / BTCMARKETS_SATOSHI_PER_BTC
			balance[i].PendingFunds = balance[i].PendingFunds / BTCMARKETS_SATOSHI_PER_BTC
		}
	}
	return balance, nil
}

func (b *BTCMarkets) SendAuthenticatedRequest(reqType, path string, data interface{}, result interface{}) (err error) {
	nonce := strconv.FormatInt(time.Now().UnixNano(), 10)[0:13]
	request := ""
	payload := []byte("")

	if data != nil {
		payload, err = JSONEncode(data)
		if err != nil {
			return err
		}
		request = path + "\n" + nonce + "\n" + string(payload)
	} else {
		request = path + "\n" + nonce + "\n"
	}

	hmac := GetHMAC(HASH_SHA512, []byte(request), []byte(b.APISecret))

	if b.Verbose {
		log.Printf("Sending %s request to URL %s with params %s\n", reqType, BTCMARKETS_API_URL+path, request)
	}

	headers := make(map[string]string)
	headers["Accept"] = "application/json"
	headers["Accept-Charset"] = "UTF-8"
	headers["Content-Type"] = "application/json"
	headers["apikey"] = b.APIKey
	headers["timestamp"] = nonce
	headers["signature"] = Base64Encode(hmac)

	resp, err := SendHTTPRequest(reqType, BTCMARKETS_API_URL+path, headers, bytes.NewBuffer(payload))

	if err != nil {
		return err
	}

	if b.Verbose {
		log.Printf("Recieved raw: %s\n", resp)
	}

	err = JSONDecode([]byte(resp), &result)

	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package cl11

import (
	"fmt"
	clw "github.com/rdwilliamson/clw11"
	"strings"
	"unsafe"
)

type Device struct {
	ID                       DeviceID
	Available                bool
	CompilerAvailable        bool
	LittleEndian             bool
	ErrorCorrectionSupport   bool
	ImageSupport             bool
	UnifiedHostMemory        bool
	AddressBits              uint32
	GlobalMemCachelineSize   uint32
	MaxClockFrequency        uint32
	MaxComputeUnits          uint32
	MaxConstantArgs          uint32
	MaxReadImageArgs         uint32
	MaxSamplers              uint32
	MaxWorkItemDimensions    uint32
	MaxWriteImageArgs        uint32
	MemBaseAddrAlign         uint32
	MinDataTypeAlignSize     uint32
	VendorID                 uint32
	PreferredVectorWidths    VectorWidths
	NativeVectorWidths       VectorWidths
	Extensions               string
	Name                     string
	Profile                  string
	Vendor                   string
	Version                  string
	OpenclCVersion           string
	DriverVersion            string
	GlobalMemCacheSize       uint64
	GlobalMemSize            uint64
	LocalMemSize             uint64
	MaxConstantBufferSize    uint64
	MaxMemAllocSize          uint64
	Image2dMaxHeight         uint
	Image2dMaxWidth          uint
	Image3dMaxDepth          uint
	Image3dMaxHeight         uint
	Image3dMaxWidth          uint
	MaxParameterSize         uint
	MaxWorkGroupSize         uint
	ProfilingTimerResolution uint

	MaxWorkItemSizes []uint

	SingleFpConfig FPConfig
	DoubleFpConfig FPConfig
}

type DeviceID clw.DeviceID

// Bitfield.
const (
	FPDenorm = iota
	FPFma
	FPInfNan
	FPRoundToInf
	FPRoundToNearest
	FPRoundToZero
	FPBits
)

type VectorWidths struct {
	Char   uint8
	Short  uint8
	Int    uint8
	Long   uint8
	Float  uint8
	Double uint8
	Half   uint8
}

type FPConfig uint8

func (v FPConfig) String() string {
	var configStrings []string
	if v&FPDenorm != 0 {
		configStrings = append(configStrings, "CL_FP_DENORM")
	}
	if v&FPFma != 0 {
		configStrings = append(configStrings, "CL_FP_FMA")
	}
	if v&FPInfNan != 0 {
		configStrings = append(configStrings, "CL_FP_INF_NAN")
	}
	if v&FPRoundToInf != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_INF")
	}
	if v&FPRoundToNearest != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_NEAREST")
	}
	if v&FPRoundToZero != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_ZERO")
	}
	return "(" + strings.Join(configStrings, "|") + ")"
}

type MemCache uint8

type LocalMem uint8

type ExecCapabilities uint8

func (p *Platform) GetDevices() ([]Device, error) {

	if p.Devices != nil {
		return p.Devices, nil
	}

	var numEntries clw.Uint
	err := clw.GetDeviceIDs(clw.PlatformID(p.ID), clw.DeviceTypeAll, 0, nil, &numEntries)
	if err != nil {
		return nil, err
	}

	deviceIDs := make([]clw.DeviceID, numEntries)
	err = clw.GetDeviceIDs(clw.PlatformID(p.ID), clw.DeviceTypeAll, numEntries, &deviceIDs[0], nil)
	if err != nil {
		return nil, err
	}

	p.Devices = make([]Device, len(deviceIDs))
	for i := range p.Devices {

		p.Devices[i].ID = DeviceID(deviceIDs[i])

		err = p.Devices[i].getAllInfo()
		if err != nil {
			return nil, err
		}
	}

	return p.Devices, nil
}

func (d *Device) getAllInfo() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = r.(error)
		}
	}()

	d.Available = d.getBool(clw.DeviceAvailable)
	d.CompilerAvailable = d.getBool(clw.DeviceCompilerAvailable)
	d.LittleEndian = d.getBool(clw.DeviceEndianLittle)
	d.ErrorCorrectionSupport = d.getBool(clw.DeviceErrorCorrectionSupport)
	d.ImageSupport = d.getBool(clw.DeviceImageSupport)
	d.UnifiedHostMemory = d.getBool(clw.DeviceHostUnifiedMemory)

	d.AddressBits = d.getUint(clw.DeviceAddressBits)
	d.GlobalMemCachelineSize = d.getUint(clw.DeviceGlobalMemCachelineSize)
	d.MaxClockFrequency = d.getUint(clw.DeviceMaxClockFrequency)
	d.MaxComputeUnits = d.getUint(clw.DeviceMaxComputeUnits)
	d.MaxConstantArgs = d.getUint(clw.DeviceMaxConstantArgs)
	d.MaxReadImageArgs = d.getUint(clw.DeviceMaxReadImageArgs)
	d.MaxSamplers = d.getUint(clw.DeviceMaxSamplers)
	d.MaxWorkItemDimensions = d.getUint(clw.DeviceMaxWorkItemDimensions)
	d.MaxWriteImageArgs = d.getUint(clw.DeviceMaxWriteImageArgs)
	d.MemBaseAddrAlign = d.getUint(clw.DeviceMemBaseAddrAlign)
	d.MinDataTypeAlignSize = d.getUint(clw.DeviceMinDataTypeAlignSize)
	d.VendorID = d.getUint(clw.DeviceVendorID)

	d.PreferredVectorWidths.Char = uint8(d.getUint(clw.DevicePreferredVectorWidthChar))
	d.PreferredVectorWidths.Short = uint8(d.getUint(clw.DevicePreferredVectorWidthShort))
	d.PreferredVectorWidths.Int = uint8(d.getUint(clw.DevicePreferredVectorWidthInt))
	d.PreferredVectorWidths.Long = uint8(d.getUint(clw.DevicePreferredVectorWidthLong))
	d.PreferredVectorWidths.Float = uint8(d.getUint(clw.DevicePreferredVectorWidthFloat))
	d.PreferredVectorWidths.Double = uint8(d.getUint(clw.DevicePreferredVectorWidthDouble))
	d.PreferredVectorWidths.Half = uint8(d.getUint(clw.DevicePreferredVectorWidthHalf))
	d.NativeVectorWidths.Char = uint8(d.getUint(clw.DeviceNativeVectorWidthChar))
	d.NativeVectorWidths.Short = uint8(d.getUint(clw.DeviceNativeVectorWidthShort))
	d.NativeVectorWidths.Int = uint8(d.getUint(clw.DeviceNativeVectorWidthInt))
	d.NativeVectorWidths.Long = uint8(d.getUint(clw.DeviceNativeVectorWidthLong))
	d.NativeVectorWidths.Float = uint8(d.getUint(clw.DeviceNativeVectorWidthFloat))
	d.NativeVectorWidths.Double = uint8(d.getUint(clw.DeviceNativeVectorWidthDouble))
	d.NativeVectorWidths.Half = uint8(d.getUint(clw.DeviceNativeVectorWidthHalf))

	d.Extensions = d.getString(clw.DeviceExtensions)
	d.Name = d.getString(clw.DeviceName)
	d.Profile = d.getString(clw.DeviceProfile)
	d.Vendor = d.getString(clw.DeviceVendor)
	d.Version = d.getString(clw.DeviceVersion)
	d.OpenclCVersion = d.getString(clw.DeviceOpenclCVersion)
	d.DriverVersion = d.getString(clw.DriverVersion)

	d.GlobalMemCacheSize = d.getUlong(clw.DeviceGlobalMemCacheSize)
	d.GlobalMemSize = d.getUlong(clw.DeviceGlobalMemSize)
	d.LocalMemSize = d.getUlong(clw.DeviceLocalMemSize)
	d.MaxConstantBufferSize = d.getUlong(clw.DeviceMaxConstantBufferSize)
	d.MaxMemAllocSize = d.getUlong(clw.DeviceMaxMemAllocSize)

	d.Image2dMaxHeight = d.getSize(clw.DeviceImage2dMaxHeight)
	d.Image2dMaxWidth = d.getSize(clw.DeviceImage2dMaxWidth)
	d.Image3dMaxDepth = d.getSize(clw.DeviceImage3dMaxDepth)
	d.Image3dMaxHeight = d.getSize(clw.DeviceImage3dMaxHeight)
	d.Image3dMaxWidth = d.getSize(clw.DeviceImage3dMaxWidth)
	d.MaxParameterSize = d.getSize(clw.DeviceMaxParameterSize)
	d.MaxWorkGroupSize = d.getSize(clw.DeviceMaxWorkGroupSize)
	d.ProfilingTimerResolution = d.getSize(clw.DeviceProfilingTimerResolution)

	d.MaxWorkItemSizes = d.getSizeArray(clw.DeviceMaxWorkItemSizes)

	d.SingleFpConfig = d.getFpConfig(clw.DeviceSingleFpConfig)
	d.DoubleFpConfig = d.getFpConfig(clw.DeviceDoubleFpConfig)

	return
}

func (d *Device) getInfo(paramName clw.DeviceInfo) (interface{}, error) {

	switch paramName {

	// exec_capabilities
	case clw.DeviceExecutionCapabilities:

	// mem_cache_type
	case clw.DeviceGlobalMemCacheType:

	// device_type
	case clw.DeviceTypeInfo:

	// command_queue_properties
	case clw.DeviceQueueProperties:

	// local_mem_type
	case clw.DeviceLocalMemTypeInfo:
	}

	return nil, nil
}

func (d *Device) getBool(paramName clw.DeviceInfo) bool {
	var paramValue clw.Bool
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return clw.ToGoBool(paramValue)
}

func (d *Device) getUint(paramName clw.DeviceInfo) uint32 {
	var paramValue clw.Uint
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint32(paramValue)
}

func (d *Device) getString(paramName clw.DeviceInfo) string {
	var paramValueSize clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, 0, nil, &paramValueSize)
	if err != nil {
		panic(err)
	}

	buffer := make([]byte, paramValueSize)
	err = clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, paramValueSize, unsafe.Pointer(&buffer[0]), nil)
	if err != nil {
		panic(err)
	}

	// Trim space and trailing \0.
	return strings.TrimSpace(string(buffer[:len(buffer)-1]))
}

func (d *Device) getUlong(paramName clw.DeviceInfo) uint64 {
	var paramValue clw.Ulong
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint64(paramValue)
}

func (d *Device) getSize(paramName clw.DeviceInfo) uint {
	var paramValue clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint(paramValue)
}

func (d *Device) getSizeArray(paramName clw.DeviceInfo) []uint {
	var paramValueSize clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, 0, nil, &paramValueSize)
	if err != nil {
		panic(err)
	}

	var a clw.Size
	buffer := make([]clw.Size, paramValueSize/clw.Size(unsafe.Sizeof(a)))
	err = clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, paramValueSize, unsafe.Pointer(&buffer[0]), nil)
	if err != nil {
		panic(err)
	}

	results := make([]uint, len(buffer))
	for i := range results {
		results[i] = uint(buffer[i])
	}

	return results
}

func (d *Device) getFpConfig(paramName clw.DeviceInfo) FPConfig {
	var paramValue clw.DeviceFPConfig
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}

	var result FPConfig
	if paramValue&clw.FPDenorm != 0 {
		result |= FPDenorm
	}
	if paramValue&clw.FPFma != 0 {
		result |= FPFma
	}
	if paramValue&clw.FPInfNan != 0 {
		result |= FPInfNan
	}
	if paramValue&clw.FPRoundToInf != 0 {
		result |= FPRoundToInf
	}
	if paramValue&clw.FPRoundToNearest != 0 {
		result |= FPRoundToNearest
	}
	if paramValue&clw.FPRoundToZero != 0 {
		result |= FPRoundToZero
	}
	return result
}
<commit_msg>Added execution capabilities.<commit_after>package cl11

import (
	"fmt"
	clw "github.com/rdwilliamson/clw11"
	"strings"
	"unsafe"
)

type Device struct {
	ID                       DeviceID
	Available                bool
	CompilerAvailable        bool
	LittleEndian             bool
	ErrorCorrectionSupport   bool
	ImageSupport             bool
	UnifiedHostMemory        bool
	AddressBits              uint32
	GlobalMemCachelineSize   uint32
	MaxClockFrequency        uint32
	MaxComputeUnits          uint32
	MaxConstantArgs          uint32
	MaxReadImageArgs         uint32
	MaxSamplers              uint32
	MaxWorkItemDimensions    uint32
	MaxWriteImageArgs        uint32
	MemBaseAddrAlign         uint32
	MinDataTypeAlignSize     uint32
	VendorID                 uint32
	PreferredVectorWidths    VectorWidths
	NativeVectorWidths       VectorWidths
	Extensions               string
	Name                     string
	Profile                  string
	Vendor                   string
	Version                  string
	OpenclCVersion           string
	DriverVersion            string
	GlobalMemCacheSize       uint64
	GlobalMemSize            uint64
	LocalMemSize             uint64
	MaxConstantBufferSize    uint64
	MaxMemAllocSize          uint64
	Image2dMaxHeight         uint
	Image2dMaxWidth          uint
	Image3dMaxDepth          uint
	Image3dMaxHeight         uint
	Image3dMaxWidth          uint
	MaxParameterSize         uint
	MaxWorkGroupSize         uint
	ProfilingTimerResolution uint
	MaxWorkItemSizes         []uint
	SingleFpConfig           FPConfig
	DoubleFpConfig           FPConfig
	ExecCapabilities         ExecCapabilities
}

type DeviceID clw.DeviceID

// Bitfield.
const (
	FPDenorm = iota
	FPFma
	FPInfNan
	FPRoundToInf
	FPRoundToNearest
	FPRoundToZero
	FPBits
)

type VectorWidths struct {
	Char   uint8
	Short  uint8
	Int    uint8
	Long   uint8
	Float  uint8
	Double uint8
	Half   uint8
}

type FPConfig uint8

func (fpConfig FPConfig) String() string {
	var configStrings []string
	if fpConfig&FPDenorm != 0 {
		configStrings = append(configStrings, "CL_FP_DENORM")
	}
	if fpConfig&FPFma != 0 {
		configStrings = append(configStrings, "CL_FP_FMA")
	}
	if fpConfig&FPInfNan != 0 {
		configStrings = append(configStrings, "CL_FP_INF_NAN")
	}
	if fpConfig&FPRoundToInf != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_INF")
	}
	if fpConfig&FPRoundToNearest != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_NEAREST")
	}
	if fpConfig&FPRoundToZero != 0 {
		configStrings = append(configStrings, "CL_FP_ROUND_TO_ZERO")
	}
	return "(" + strings.Join(configStrings, "|") + ")"
}

type MemCache uint8

type LocalMem uint8

type ExecCapabilities uint8

// Bitfield.
const (
	ExecKernel = iota
	ExecNativeKernel
)

func (exec ExecCapabilities) String() string {
	var execStrings []string
	if exec&ExecKernel != 0 {
		execStrings = append(execStrings, "CL_EXEC_KERNEL")
	}
	if exec&ExecNativeKernel != 0 {
		execStrings = append(execStrings, "CL_EXEC_NATIVE_KERNEL")
	}
	return "(" + strings.Join(execStrings, "|") + ")"
}

func (p *Platform) GetDevices() ([]Device, error) {

	if p.Devices != nil {
		return p.Devices, nil
	}

	var numEntries clw.Uint
	err := clw.GetDeviceIDs(clw.PlatformID(p.ID), clw.DeviceTypeAll, 0, nil, &numEntries)
	if err != nil {
		return nil, err
	}

	deviceIDs := make([]clw.DeviceID, numEntries)
	err = clw.GetDeviceIDs(clw.PlatformID(p.ID), clw.DeviceTypeAll, numEntries, &deviceIDs[0], nil)
	if err != nil {
		return nil, err
	}

	p.Devices = make([]Device, len(deviceIDs))
	for i := range p.Devices {

		p.Devices[i].ID = DeviceID(deviceIDs[i])

		err = p.Devices[i].getAllInfo()
		if err != nil {
			return nil, err
		}
	}

	return p.Devices, nil
}

func (d *Device) getAllInfo() (err error) {
	defer func() {
		if r := recover(); r != nil {
			err = r.(error)
		}
	}()

	d.Available = d.getBool(clw.DeviceAvailable)
	d.CompilerAvailable = d.getBool(clw.DeviceCompilerAvailable)
	d.LittleEndian = d.getBool(clw.DeviceEndianLittle)
	d.ErrorCorrectionSupport = d.getBool(clw.DeviceErrorCorrectionSupport)
	d.ImageSupport = d.getBool(clw.DeviceImageSupport)
	d.UnifiedHostMemory = d.getBool(clw.DeviceHostUnifiedMemory)

	d.AddressBits = d.getUint(clw.DeviceAddressBits)
	d.GlobalMemCachelineSize = d.getUint(clw.DeviceGlobalMemCachelineSize)
	d.MaxClockFrequency = d.getUint(clw.DeviceMaxClockFrequency)
	d.MaxComputeUnits = d.getUint(clw.DeviceMaxComputeUnits)
	d.MaxConstantArgs = d.getUint(clw.DeviceMaxConstantArgs)
	d.MaxReadImageArgs = d.getUint(clw.DeviceMaxReadImageArgs)
	d.MaxSamplers = d.getUint(clw.DeviceMaxSamplers)
	d.MaxWorkItemDimensions = d.getUint(clw.DeviceMaxWorkItemDimensions)
	d.MaxWriteImageArgs = d.getUint(clw.DeviceMaxWriteImageArgs)
	d.MemBaseAddrAlign = d.getUint(clw.DeviceMemBaseAddrAlign)
	d.MinDataTypeAlignSize = d.getUint(clw.DeviceMinDataTypeAlignSize)
	d.VendorID = d.getUint(clw.DeviceVendorID)

	d.PreferredVectorWidths.Char = uint8(d.getUint(clw.DevicePreferredVectorWidthChar))
	d.PreferredVectorWidths.Short = uint8(d.getUint(clw.DevicePreferredVectorWidthShort))
	d.PreferredVectorWidths.Int = uint8(d.getUint(clw.DevicePreferredVectorWidthInt))
	d.PreferredVectorWidths.Long = uint8(d.getUint(clw.DevicePreferredVectorWidthLong))
	d.PreferredVectorWidths.Float = uint8(d.getUint(clw.DevicePreferredVectorWidthFloat))
	d.PreferredVectorWidths.Double = uint8(d.getUint(clw.DevicePreferredVectorWidthDouble))
	d.PreferredVectorWidths.Half = uint8(d.getUint(clw.DevicePreferredVectorWidthHalf))
	d.NativeVectorWidths.Char = uint8(d.getUint(clw.DeviceNativeVectorWidthChar))
	d.NativeVectorWidths.Short = uint8(d.getUint(clw.DeviceNativeVectorWidthShort))
	d.NativeVectorWidths.Int = uint8(d.getUint(clw.DeviceNativeVectorWidthInt))
	d.NativeVectorWidths.Long = uint8(d.getUint(clw.DeviceNativeVectorWidthLong))
	d.NativeVectorWidths.Float = uint8(d.getUint(clw.DeviceNativeVectorWidthFloat))
	d.NativeVectorWidths.Double = uint8(d.getUint(clw.DeviceNativeVectorWidthDouble))
	d.NativeVectorWidths.Half = uint8(d.getUint(clw.DeviceNativeVectorWidthHalf))

	d.Extensions = d.getString(clw.DeviceExtensions)
	d.Name = d.getString(clw.DeviceName)
	d.Profile = d.getString(clw.DeviceProfile)
	d.Vendor = d.getString(clw.DeviceVendor)
	d.Version = d.getString(clw.DeviceVersion)
	d.OpenclCVersion = d.getString(clw.DeviceOpenclCVersion)
	d.DriverVersion = d.getString(clw.DriverVersion)

	d.GlobalMemCacheSize = d.getUlong(clw.DeviceGlobalMemCacheSize)
	d.GlobalMemSize = d.getUlong(clw.DeviceGlobalMemSize)
	d.LocalMemSize = d.getUlong(clw.DeviceLocalMemSize)
	d.MaxConstantBufferSize = d.getUlong(clw.DeviceMaxConstantBufferSize)
	d.MaxMemAllocSize = d.getUlong(clw.DeviceMaxMemAllocSize)

	d.Image2dMaxHeight = d.getSize(clw.DeviceImage2dMaxHeight)
	d.Image2dMaxWidth = d.getSize(clw.DeviceImage2dMaxWidth)
	d.Image3dMaxDepth = d.getSize(clw.DeviceImage3dMaxDepth)
	d.Image3dMaxHeight = d.getSize(clw.DeviceImage3dMaxHeight)
	d.Image3dMaxWidth = d.getSize(clw.DeviceImage3dMaxWidth)
	d.MaxParameterSize = d.getSize(clw.DeviceMaxParameterSize)
	d.MaxWorkGroupSize = d.getSize(clw.DeviceMaxWorkGroupSize)
	d.ProfilingTimerResolution = d.getSize(clw.DeviceProfilingTimerResolution)

	d.MaxWorkItemSizes = d.getSizeArray(clw.DeviceMaxWorkItemSizes)

	d.SingleFpConfig = d.getFpConfig(clw.DeviceSingleFpConfig)
	d.DoubleFpConfig = d.getFpConfig(clw.DeviceDoubleFpConfig)

	d.ExecCapabilities = d.getExecCapabilities(clw.DeviceExecutionCapabilities)

	return
}

func (d *Device) getInfo(paramName clw.DeviceInfo) (interface{}, error) {

	switch paramName {

	// mem_cache_type
	case clw.DeviceGlobalMemCacheType:

	// device_type
	case clw.DeviceTypeInfo:

	// command_queue_properties
	case clw.DeviceQueueProperties:

	// local_mem_type
	case clw.DeviceLocalMemTypeInfo:
	}

	return nil, nil
}

func (d *Device) getBool(paramName clw.DeviceInfo) bool {
	var paramValue clw.Bool
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return clw.ToGoBool(paramValue)
}

func (d *Device) getUint(paramName clw.DeviceInfo) uint32 {
	var paramValue clw.Uint
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint32(paramValue)
}

func (d *Device) getString(paramName clw.DeviceInfo) string {
	var paramValueSize clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, 0, nil, &paramValueSize)
	if err != nil {
		panic(err)
	}

	buffer := make([]byte, paramValueSize)
	err = clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, paramValueSize, unsafe.Pointer(&buffer[0]), nil)
	if err != nil {
		panic(err)
	}

	// Trim space and trailing \0.
	return strings.TrimSpace(string(buffer[:len(buffer)-1]))
}

func (d *Device) getUlong(paramName clw.DeviceInfo) uint64 {
	var paramValue clw.Ulong
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint64(paramValue)
}

func (d *Device) getSize(paramName clw.DeviceInfo) uint {
	var paramValue clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}
	return uint(paramValue)
}

func (d *Device) getSizeArray(paramName clw.DeviceInfo) []uint {
	var paramValueSize clw.Size
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, 0, nil, &paramValueSize)
	if err != nil {
		panic(err)
	}

	var a clw.Size
	buffer := make([]clw.Size, paramValueSize/clw.Size(unsafe.Sizeof(a)))
	err = clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, paramValueSize, unsafe.Pointer(&buffer[0]), nil)
	if err != nil {
		panic(err)
	}

	results := make([]uint, len(buffer))
	for i := range results {
		results[i] = uint(buffer[i])
	}

	return results
}

func (d *Device) getFpConfig(paramName clw.DeviceInfo) FPConfig {
	var paramValue clw.DeviceFPConfig
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}

	var result FPConfig
	if paramValue&clw.FPDenorm != 0 {
		result |= FPDenorm
	}
	if paramValue&clw.FPFma != 0 {
		result |= FPFma
	}
	if paramValue&clw.FPInfNan != 0 {
		result |= FPInfNan
	}
	if paramValue&clw.FPRoundToInf != 0 {
		result |= FPRoundToInf
	}
	if paramValue&clw.FPRoundToNearest != 0 {
		result |= FPRoundToNearest
	}
	if paramValue&clw.FPRoundToZero != 0 {
		result |= FPRoundToZero
	}
	return result
}

func (d *Device) getExecCapabilities(paramName clw.DeviceInfo) ExecCapabilities {
	var paramValue clw.DeviceExecCapabilities
	err := clw.GetDeviceInfo(clw.DeviceID(d.ID), paramName, clw.Size(unsafe.Sizeof(paramValue)),
		unsafe.Pointer(&paramValue), nil)
	if err != nil {
		panic(err)
	}

	var result ExecCapabilities
	if paramValue&clw.ExecKernel != 0 {
		result |= ExecKernel
	}
	if paramValue&clw.ExecNativeKernel != 0 {
		result |= ExecNativeKernel
	}
	return result
}
<|endoftext|>