<commit_before>package commands

import (
	"errors"
	"fmt"
	"os"
	fp "path/filepath"
	"strings"
	"syscall"

	"github.com/codegangsta/cli"

	"github.com/yuuki1/droot/log"
	"github.com/yuuki1/droot/osutil"
)

var CommandArgRun = "--root ROOT_DIR [--user USER] [--group GROUP] [--bind SRC-PATH[:DEST-PATH]] [--robind SRC-PATH[:DEST-PATH]] COMMAND"
var CommandRun = cli.Command{
	Name:   "run",
	Usage:  "Run an extracted docker image from s3",
	Action: fatalOnError(doRun),
	Flags: []cli.Flag{
		cli.StringFlag{Name: "root, r", Usage: "Root directory path for chrooting"},
		cli.StringFlag{Name: "user, u", Usage: "User (ID or name) to switch before running the program"},
		cli.StringFlag{Name: "group, g", Usage: "Group (ID or name) to switch to"},
		cli.StringSliceFlag{
			Name:  "bind, b",
			Value: &cli.StringSlice{},
			Usage: "Bind mount directory (can be specifies multiple times)",
		},
		cli.StringSliceFlag{
			Name:  "robind",
			Value: &cli.StringSlice{},
			Usage: "Readonly bind mount directory (can be specifies multiple times)",
		},
		cli.BoolFlag{
			Name:  "copy-files, cp",
			Usage: "Copy host from containersuch as /etc/hosts, /etc/group, /etc/passwd, /etc/hosts",
		},
	},
}

var copyFiles = []string{
	"etc/group",
	"etc/passwd",
	"etc/resolv.conf",
	"etc/hosts",
}

var keepCaps = map[uint]bool{
	0:  true, // CAP_CHOWN
	1:  true, // CAP_DAC_OVERRIDE
	2:  true, // CAP_DAC_READ_SEARCH
	3:  true, // CAP_FOWNER
	6:  true, // CAP_SETGID
	7:  true, // CAP_SETUID
	10: true, // CAP_NET_BIND_SERVICE
}

func doRun(c *cli.Context) error {
	command := c.Args()
	if len(command) < 1 {
		cli.ShowCommandHelp(c, "run")
		return errors.New("command required")
	}

	rootDir := c.String("root")
	if rootDir == "" {
		cli.ShowCommandHelp(c, "run")
		return errors.New("--root option required")
	}

	if !osutil.ExistsDir(rootDir) {
		return fmt.Errorf("No such directory %s", rootDir)
	}

	// copy files
	if c.Bool("copy-files") {
		for _, f := range copyFiles {
			if err := osutil.Cp(fp.Join("/", f), fp.Join(rootDir, f)); err != nil {
				return err
			}
		}
	}

	// bind the directories
	if err := bindSystemMount(rootDir); err != nil {
		return err
	}

	for _, dir := range c.StringSlice("bind") {
		if err := bindMount(dir, rootDir, false); err != nil {
			return err
		}
	}
	for _, dir := range c.StringSlice("robind") {
		if err := bindMount(dir, rootDir, true); err != nil {
			return err
		}
	}

	// create symlinks
	if err := osutil.Symlink("../run/lock", fp.Join(rootDir, "/var/lock")); err != nil {
		return err
	}

	if err := createDevices(rootDir); err != nil {
		return err
	}

	log.Debug("chroot", rootDir, command)

	if err := syscall.Chroot(rootDir); err != nil {
		return err
	}
	if err := syscall.Chdir("/"); err != nil {
		return err
	}

	log.Debug("drop capabilities")
	if err := osutil.DropCapabilities(keepCaps); err != nil {
		return err
	}

	if group := c.String("group"); group != "" {
		log.Debug("setgid", group)
		if err := osutil.SetGroup(group); err != nil {
			return err
		}
	}
	if user := c.String("user"); user != "" {
		log.Debug("setuid", user)
		if err := osutil.SetUser(user); err != nil {
			return err
		}
	}

	return osutil.Execv(command[0], command[0:], os.Environ())
}

func bindMount(bindDir string, rootDir string, readonly bool) error {
	var srcDir, destDir string

	d := strings.SplitN(bindDir, ":", 2)
	if len(d) < 2 {
		srcDir = d[0]
	} else {
		srcDir, destDir = d[0], d[1]
	}
	if destDir == "" {
		destDir = srcDir
	}

	ok, err := osutil.IsDirEmpty(srcDir)
	if err != nil {
		return err
	}
	if ok {
		if _, err := os.Create(fp.Join(srcDir, ".droot.keep")); err != nil {
			return err
		}
	}

	containerDir := fp.Join(rootDir, destDir)

	if err := os.MkdirAll(containerDir, os.FileMode(0755)); err != nil {
		return err
	}

	ok, err = osutil.IsDirEmpty(containerDir)
	if err != nil {
		return err
	}
	if ok {
		if err := osutil.BindMount(srcDir, containerDir); err != nil {
			return err
		}
		log.Debug("bind mount", bindDir, "to", containerDir)

		if readonly {
			if err := osutil.RObindMount(srcDir, containerDir); err != nil {
				return err
			}
			log.Debug("robind mount", bindDir, "to", containerDir)
		}
	}

	return nil
}

func bindSystemMount(rootDir string) error {
	procDir := fp.Join(rootDir, "/proc")
	if ok, err := osutil.Mounted(procDir); !ok && err == nil {
		if err := osutil.RunCmd("mount", "-t", "proc", "none", procDir); err != nil {
			return err
		}
	}

	sysDir := fp.Join(rootDir, "/sys")
	if ok, err := osutil.Mounted(sysDir); !ok && err == nil {
		if err := osutil.RunCmd("mount", "--rbind", "/sys", fp.Join(rootDir, "/sys")); err != nil {
			return err
		}
	}

	return nil
}

func createDevices(rootDir string) error {
	if err := osutil.Mknod(fp.Join(rootDir, os.DevNull), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil {
		return err
	}

	if err := osutil.Mknod(fp.Join(rootDir, "/dev/zero"), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil {
		return err
	}

	for _, f := range []string{"/dev/random", "/dev/urandom"} {
		if err := osutil.Mknod(fp.Join(rootDir, f), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+9); err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>Add --no-capability option to run command<commit_after>package commands

import (
	"errors"
	"fmt"
	"os"
	fp "path/filepath"
	"strings"
	"syscall"

	"github.com/codegangsta/cli"

	"github.com/yuuki1/droot/log"
	"github.com/yuuki1/droot/osutil"
)

var CommandArgRun = "--root ROOT_DIR [--user USER] [--group GROUP] [--bind SRC-PATH[:DEST-PATH]] [--robind SRC-PATH[:DEST-PATH]] -- COMMAND"
var CommandRun = cli.Command{
	Name:   "run",
	Usage:  "Run an extracted docker image from s3",
	Action: fatalOnError(doRun),
	Flags: []cli.Flag{
		cli.StringFlag{Name: "root, r", Usage: "Root directory path for chrooting"},
		cli.StringFlag{Name: "user, u", Usage: "User (ID or name) to switch before running the program"},
		cli.StringFlag{Name: "group, g", Usage: "Group (ID or name) to switch to"},
		cli.StringSliceFlag{
			Name:  "bind, b",
			Value: &cli.StringSlice{},
			Usage: "Bind mount directory (can be specifies multiple times)",
		},
		cli.StringSliceFlag{
			Name:  "robind",
			Value: &cli.StringSlice{},
			Usage: "Readonly bind mount directory (can be specifies multiple times)",
		},
		cli.BoolFlag{
			Name:  "copy-files, cp",
			Usage: "Copy host from containersuch as /etc/hosts, /etc/group, /etc/passwd, /etc/hosts",
		},
		cli.BoolFlag{Name: "no-capability", Usage: "Provide COMMAND's process in chroot with root permission (dangerous)"},
	},
}

var copyFiles = []string{
	"etc/group",
	"etc/passwd",
	"etc/resolv.conf",
	"etc/hosts",
}

var keepCaps = map[uint]bool{
	0:  true, // CAP_CHOWN
	1:  true, // CAP_DAC_OVERRIDE
	2:  true, // CAP_DAC_READ_SEARCH
	3:  true, // CAP_FOWNER
	6:  true, // CAP_SETGID
	7:  true, // CAP_SETUID
	10: true, // CAP_NET_BIND_SERVICE
}

func doRun(c *cli.Context) error {
	command := c.Args()
	if len(command) < 1 {
		cli.ShowCommandHelp(c, "run")
		return errors.New("command required")
	}

	rootDir := c.String("root")
	if rootDir == "" {
		cli.ShowCommandHelp(c, "run")
		return errors.New("--root option required")
	}

	if !osutil.ExistsDir(rootDir) {
		return fmt.Errorf("No such directory %s", rootDir)
	}

	// copy files
	if c.Bool("copy-files") {
		for _, f := range copyFiles {
			if err := osutil.Cp(fp.Join("/", f), fp.Join(rootDir, f)); err != nil {
				return err
			}
		}
	}

	// bind the directories
	if err := bindSystemMount(rootDir); err != nil {
		return err
	}

	for _, dir := range c.StringSlice("bind") {
		if err := bindMount(dir, rootDir, false); err != nil {
			return err
		}
	}
	for _, dir := range c.StringSlice("robind") {
		if err := bindMount(dir, rootDir, true); err != nil {
			return err
		}
	}

	// create symlinks
	if err := osutil.Symlink("../run/lock", fp.Join(rootDir, "/var/lock")); err != nil {
		return err
	}

	if err := createDevices(rootDir); err != nil {
		return err
	}

	log.Debug("chroot", rootDir, command)

	if err := syscall.Chroot(rootDir); err != nil {
		return err
	}
	if err := syscall.Chdir("/"); err != nil {
		return err
	}

	if !c.Bool("no-capability") {
		log.Debug("drop capabilities")
		if err := osutil.DropCapabilities(keepCaps); err != nil {
			return err
		}
	}

	if group := c.String("group"); group != "" {
		log.Debug("setgid", group)
		if err := osutil.SetGroup(group); err != nil {
			return err
		}
	}
	if user := c.String("user"); user != "" {
		log.Debug("setuid", user)
		if err := osutil.SetUser(user); err != nil {
			return err
		}
	}

	return osutil.Execv(command[0], command[0:], os.Environ())
}

func bindMount(bindDir string, rootDir string, readonly bool) error {
	var srcDir, destDir string

	d := strings.SplitN(bindDir, ":", 2)
	if len(d) < 2 {
		srcDir = d[0]
	} else {
		srcDir, destDir = d[0], d[1]
	}
	if destDir == "" {
		destDir = srcDir
	}

	ok, err := osutil.IsDirEmpty(srcDir)
	if err != nil {
		return err
	}
	if ok {
		if _, err := os.Create(fp.Join(srcDir, ".droot.keep")); err != nil {
			return err
		}
	}

	containerDir := fp.Join(rootDir, destDir)

	if err := os.MkdirAll(containerDir, os.FileMode(0755)); err != nil {
		return err
	}

	ok, err = osutil.IsDirEmpty(containerDir)
	if err != nil {
		return err
	}
	if ok {
		if err := osutil.BindMount(srcDir, containerDir); err != nil {
			return err
		}
		log.Debug("bind mount", bindDir, "to", containerDir)

		if readonly {
			if err := osutil.RObindMount(srcDir, containerDir); err != nil {
				return err
			}
			log.Debug("robind mount", bindDir, "to", containerDir)
		}
	}

	return nil
}

func bindSystemMount(rootDir string) error {
	procDir := fp.Join(rootDir, "/proc")
	if ok, err := osutil.Mounted(procDir); !ok && err == nil {
		if err := osutil.RunCmd("mount", "-t", "proc", "none", procDir); err != nil {
			return err
		}
	}

	sysDir := fp.Join(rootDir, "/sys")
	if ok, err := osutil.Mounted(sysDir); !ok && err == nil {
		if err := osutil.RunCmd("mount", "--rbind", "/sys", fp.Join(rootDir, "/sys")); err != nil {
			return err
		}
	}

	return nil
}

func createDevices(rootDir string) error {
	if err := osutil.Mknod(fp.Join(rootDir, os.DevNull), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil {
		return err
	}

	if err := osutil.Mknod(fp.Join(rootDir, "/dev/zero"), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+3); err != nil {
		return err
	}

	for _, f := range []string{"/dev/random", "/dev/urandom"} {
		if err := osutil.Mknod(fp.Join(rootDir, f), syscall.S_IFCHR|uint32(os.FileMode(0666)), 1*256+9); err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package commands

import (
	"io"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"

	"github.com/codegangsta/cli"
	"github.com/docker/machine/drivers"
	"github.com/docker/machine/ssh"
)

func cmdSsh(c *cli.Context) {
	var (
		err error
	)
	name := c.Args().First()

	certInfo := getCertPathInfo(c)
	defaultStore, err := getDefaultStore(
		c.GlobalString("storage-path"),
		certInfo.CaCertPath,
		certInfo.CaKeyPath,
	)
	if err != nil {
		log.Fatal(err)
	}

	mcn, err := newMcn(defaultStore)
	if err != nil {
		log.Fatal(err)
	}

	if name == "" {
		host, err := mcn.GetActive()
		if err != nil {
			log.Fatalf("unable to get active host: %v", err)
		}

		if host == nil {
			log.Fatalf("There is no active host. Please set it with %s active <machine name>.", c.App.Name)
		}

		name = host.Name
	}

	host, err := mcn.Get(name)
	if err != nil {
		log.Fatal(err)
	}

	_, err = host.GetURL()
	if err != nil {
		if err == drivers.ErrHostIsNotRunning {
			log.Fatalf("%s is not running. Please start this with docker-machine start %s", host.Name, host.Name)
		} else {
			log.Fatalf("Unexpected error getting machine url: %s", err)
		}
	}

	var output ssh.Output

	if len(c.Args()) <= 1 {
		err = host.CreateSSHShell()
	} else {
		output, err = host.RunSSHCommand(strings.Join(c.Args()[1:], " "))

		io.Copy(os.Stderr, output.Stderr)
		io.Copy(os.Stdout, output.Stdout)
	}

	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Fix for SSH command<commit_after>package commands

import (
	"io"
	"os"
	"strings"

	log "github.com/Sirupsen/logrus"

	"github.com/codegangsta/cli"
	"github.com/docker/machine/drivers"
	"github.com/docker/machine/ssh"
)

func cmdSsh(c *cli.Context) {
	var (
		err error
	)
	name := c.Args().First()

	certInfo := getCertPathInfo(c)
	defaultStore, err := getDefaultStore(
		c.GlobalString("storage-path"),
		certInfo.CaCertPath,
		certInfo.CaKeyPath,
	)
	if err != nil {
		log.Fatal(err)
	}

	mcn, err := newMcn(defaultStore)
	if err != nil {
		log.Fatal(err)
	}

	if name == "" {
		host, err := mcn.GetActive()
		if err != nil {
			log.Fatalf("unable to get active host: %v", err)
		}

		if host == nil {
			log.Fatalf("There is no active host. Please set it with %s active <machine name>.", c.App.Name)
		}

		name = host.Name
	}

	host, err := mcn.Get(name)
	if err != nil {
		log.Fatal(err)
	}

	_, err = host.GetURL()
	if err != nil {
		if err == drivers.ErrHostIsNotRunning {
			log.Fatalf("%s is not running. Please start this with docker-machine start %s", host.Name, host.Name)
		} else {
			log.Fatalf("Unexpected error getting machine url: %s", err)
		}
	}

	var output ssh.Output

	if len(c.Args()) <= 1 {
		err = host.CreateSSHShell()
	} else {
		var cmd string
		var args []string = c.Args()

		for i, arg := range args {
			if arg == "--" {
				i++
				cmd = strings.Join(args[i:], " ")
				break
			}
		}
		if len(cmd) == 0 {
			cmd = strings.Join(args[1:], " ")
		}
		output, err = host.RunSSHCommand(cmd)

		io.Copy(os.Stderr, output.Stderr)
		io.Copy(os.Stdout, output.Stdout)
	}

	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package proto

import (
	"testing"
	"text/scanner"
)

var startPosition = scanner.Position{Line: 1, Column: 1}

func TestCreateComment(t *testing.T) {
	c0 := newComment(startPosition, "")
	if got, want := len(c0.Lines), 1; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	c1 := newComment(startPosition, `hello
world`)
	if got, want := len(c1.Lines), 2; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Lines[0], "hello"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Lines[1], "world"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Cstyle, true; got != want {
		t.Errorf("got [%v] want [%v]", c1, want)
	}
}

func TestTakeLastComment(t *testing.T) {
	c0 := newComment(startPosition, "hi")
	c1 := newComment(startPosition, "there")
	_, l := takeLastComment([]Visitee{c0, c1})
	if got, want := len(l), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := l[0], c0; got != want {
		t.Errorf("got [%v] want [%v]", c1, want)
	}
}

func TestParseCommentWithEmptyLinesIndentAndTripleSlash(t *testing.T) {
	proto := `
	// comment 1
	// comment 2
	//
	// comment 3
	/// comment 4`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	//spew.Dump(def)
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 5; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], " comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want {
		t.Fatalf("got [%d] want [%d]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, false; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCStyleComment(t *testing.T) {
	proto := `
/*comment 1
comment 2

comment 3
  comment 4
*/`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[3], "comment 3"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], "  comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCStyleCommentWithIndent(t *testing.T) {
	t.Skip("See https://github.com/emicklei/proto/issues/53")
	proto := `
	/*comment 1
	comment 2

	comment 3
	  comment 4
	*/`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[3], "comment 3"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], "  comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCommentWithTripleSlash(t *testing.T) {
	proto := `
/// comment 1
`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	//spew.Dump(def)
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).ExtraSlash, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[0], " comment 1"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want {
		t.Fatalf("got [%d] want [%d]", got, want)
	}
}
<commit_msg>Add skipped test for one-line c-style comment<commit_after>// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package proto

import (
	"testing"
	"text/scanner"
)

var startPosition = scanner.Position{Line: 1, Column: 1}

func TestCreateComment(t *testing.T) {
	c0 := newComment(startPosition, "")
	if got, want := len(c0.Lines), 1; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	c1 := newComment(startPosition, `hello
world`)
	if got, want := len(c1.Lines), 2; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Lines[0], "hello"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Lines[1], "world"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := c1.Cstyle, true; got != want {
		t.Errorf("got [%v] want [%v]", c1, want)
	}
}

func TestTakeLastComment(t *testing.T) {
	c0 := newComment(startPosition, "hi")
	c1 := newComment(startPosition, "there")
	_, l := takeLastComment([]Visitee{c0, c1})
	if got, want := len(l), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := l[0], c0; got != want {
		t.Errorf("got [%v] want [%v]", c1, want)
	}
}

func TestParseCommentWithEmptyLinesIndentAndTripleSlash(t *testing.T) {
	proto := `
	// comment 1
	// comment 2
	//
	// comment 3
	/// comment 4`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	//spew.Dump(def)
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 5; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], " comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want {
		t.Fatalf("got [%d] want [%d]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, false; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCStyleComment(t *testing.T) {
	proto := `
/*comment 1
comment 2

comment 3
  comment 4
*/`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[3], "comment 3"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], "  comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCStyleCommentWithIndent(t *testing.T) {
	t.Skip("See https://github.com/emicklei/proto/issues/53")
	proto := `
	/*comment 1
	comment 2

	comment 3
	  comment 4
	*/`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[3], "comment 3"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[4], "  comment 4"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestParseCStyleOneLineComment(t *testing.T) {
	t.Skip("See https://github.com/emicklei/proto/issues/54")
	proto := `/* comment 1 */`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}

	if got, want := len(def.Elements[0].(*Comment).Lines), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[0], "/* comment 1 */"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

func TestParseCommentWithTripleSlash(t *testing.T) {
	proto := `
/// comment 1
`
	p := newParserOn(proto)
	def, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	//spew.Dump(def)
	if got, want := len(def.Elements), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).ExtraSlash, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Lines[0], " comment 1"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want {
		t.Fatalf("got [%d] want [%d]", got, want)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math"
	"math/big"
	"net/http"
	"os"
	"path"
	"strconv"

	"github.com/gorilla/handlers"
	_ "github.com/mattn/go-sqlite3"
)

const (
	listenAddr     = "127.0.0.1:9292"
	bigThumbSize   = 1000
	smallThumbSize = 200
)

var (
	db           *sql.DB
	getSetStmt   *sql.Stmt
	getPhotoStmt *sql.Stmt
)

type Set struct {
	Id             int
	Name           string
	PhotosCount    int
	TakenAt        sql.NullString
	ThumbPhotoId   int
	ThumbPhotoPath string
}

type Photo struct {
	Aperture      sql.NullFloat64
	Camera        sql.NullString
	ExposureComp  sql.NullInt64
	ExposureTime  sql.NullFloat64
	Flash         sql.NullString
	FocalLength   sql.NullFloat64
	FocalLength35 sql.NullInt64
	Height        int64
	ISO           sql.NullInt64
	Id            int
	Lat           sql.NullFloat64
	Lens          sql.NullString
	Lng           sql.NullFloat64
	NextPhotoId   sql.NullInt64
	Path          string
	PrevPhotoId   sql.NullInt64
	SetId         int
	Size          int
	TakenAt       sql.NullString
	Width         int64
}

func thumbURL(photoPath, suffix string) string {
	identifier := fmt.Sprintf("%x", md5.Sum([]byte(photoPath)))
	return fmt.Sprintf("/thumbs/%s_%s.jpg", identifier, suffix)
}

func (s *Set) ThumbURL() string {
	return thumbURL(s.ThumbPhotoPath, "small")
}

func (s *Set) MarshalJSON() ([]byte, error) { // implements Marshaler
	setMap := map[string]interface{}{
		"id":             s.Id,
		"name":           s.Name,
		"photos_count":   s.PhotosCount,
		"thumb_photo_id": s.ThumbPhotoId,
		"thumb_url":      s.ThumbURL(),
	}
	setMap["taken_at"], _ = s.TakenAt.Value()
	return json.Marshal(setMap)
}

func (p *Photo) AspectRatio() [2]int64 {
	gcd := new(big.Int).GCD(nil, nil, big.NewInt(p.Width), big.NewInt(p.Height)).Int64()
	return [2]int64{p.Width / gcd, p.Height / gcd}
}

func (p *Photo) BigThumbHeight() int64 {
	if p.Orientation() == "portrait" {
		if p.Height < bigThumbSize {
			return p.Height
		}

		return bigThumbSize
	}

	aspectRatio := p.AspectRatio()
	return int64(math.Floor((float64(aspectRatio[1])/float64(aspectRatio[0]))*float64(p.BigThumbWidth()) + .5))
}

func (p *Photo) BigThumbWidth() int64 {
	if p.Orientation() == "portrait" {
		aspectRatio := p.AspectRatio()
		return int64(math.Floor((float64(aspectRatio[0])/float64(aspectRatio[1]))*float64(p.BigThumbHeight()) + .5))
	}

	if p.Width < bigThumbSize {
		return p.Width
	}

	return bigThumbSize
}

func (p *Photo) Filename() string {
	return path.Base(p.Path)
}

func (p *Photo) Orientation() string {
	if p.Height > p.Width {
		return "portrait"
	}
	return "landscape"
}

func (p *Photo) ThumbURL(suffix string) string {
	return thumbURL(p.Path, suffix)
}

func (p *Photo) MarshalJSON() ([]byte, error) { // implements Marshaler
	photoMap := map[string]interface{}{
		"aspect_ratio":     p.AspectRatio(),
		"big_thumb_height": p.BigThumbHeight(),
		"big_thumb_url":    p.ThumbURL("big"),
		"big_thumb_width":  p.BigThumbWidth(),
		"filename":         p.Filename(),
		"height":           p.Height,
		"id":               p.Id,
		"orientation":      p.Orientation(),
		"path":             p.Path,
		"set_id":           p.SetId,
		"size":             p.Size,
		"small_thumb_url":  p.ThumbURL("small"),
		"width":            p.Width,
	}

	photoMap["aperture"], _ = p.Aperture.Value()
	photoMap["camera"], _ = p.Camera.Value()
	photoMap["exposure_comp"], _ = p.ExposureComp.Value()
	photoMap["exposure_time"], _ = p.ExposureTime.Value()
	photoMap["flash"], _ = p.Flash.Value()
	photoMap["focal_length"], _ = p.FocalLength.Value()
	photoMap["focal_length_35"], _ = p.FocalLength35.Value()
	photoMap["iso"], _ = p.ISO.Value()
	photoMap["lat"], _ = p.Lat.Value()
	photoMap["lens"], _ = p.Lens.Value()
	photoMap["lng"], _ = p.Lng.Value()
	photoMap["next_photo_id"], _ = p.NextPhotoId.Value()
	photoMap["prev_photo_id"], _ = p.PrevPhotoId.Value()
	photoMap["taken_at"], _ = p.TakenAt.Value()

	return json.Marshal(photoMap)
}

func badRequest(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusBadRequest)
	fmt.Fprintln(w, http.StatusBadRequest, "Bad Request")
}

func requireParam(param string, w http.ResponseWriter, r *http.Request) error {
	if len(r.URL.Query()[param]) == 0 {
		badRequest(w, r)
		return errors.New(fmt.Sprintf("missing %s parameter", param))
	}
	return nil
}

func getSetById(setId int) (*Set, error) {
	set := Set{}
	row := getSetStmt.QueryRow(setId)
	err := row.Scan(
		&set.Id,
		&set.Name,
		&set.PhotosCount,
		&set.TakenAt,
		&set.ThumbPhotoId,
		&set.ThumbPhotoPath,
	)
	return &set, err
}

func getPhotoById(photoId int) (*Photo, error) {
	photo := Photo{}
	row := getPhotoStmt.QueryRow(photoId)
	err := row.Scan(
		&photo.Aperture,
		&photo.Camera,
		&photo.ExposureComp,
		&photo.ExposureTime,
		&photo.Flash,
		&photo.FocalLength,
		&photo.FocalLength35,
		&photo.Height,
		&photo.Id,
		&photo.ISO,
		&photo.Lat,
		&photo.Lens,
		&photo.Lng,
		&photo.NextPhotoId,
		&photo.Path,
		&photo.PrevPhotoId,
		&photo.SetId,
		&photo.Size,
		&photo.TakenAt,
		&photo.Width,
	)
	return &photo, err
}

func getSetHandler(w http.ResponseWriter, r *http.Request) {
	if requireParam("id", w, r) != nil {
		return
	}

	setId, err := strconv.Atoi(r.URL.Query()["id"][0])
	if err != nil {
		log.Fatalln("Failed to convert id to integer:", err)
	}

	set, err := getSetById(setId)
	if err == sql.ErrNoRows { // set does not exist
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(set)
}

func getPhotoHandler(w http.ResponseWriter, r *http.Request) {
	if requireParam("id", w, r) != nil {
		return
	}

	photoId, err := strconv.Atoi(r.URL.Query()["id"][0])
	if err != nil {
		log.Fatalln("Failed to convert id to integer:", err)
	}

	photo, err := getPhotoById(photoId)
	if err == sql.ErrNoRows { // photo does not exist
		http.NotFound(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(photo)
}

func setupDatabase() {
	db, err := sql.Open("sqlite3", "thyme.db")
	if err != nil {
		log.Fatalln("Failed to open database:", err)
	}

	getSetStmt, err = db.Prepare(`
	SELECT
	sets.id, name, photos_count, sets.taken_at, thumb_photo_id, photos.path
	FROM sets
	JOIN photos ON sets.thumb_photo_id = photos.id
	WHERE sets.id = ?
	`)
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}

	getPhotoStmt, err = db.Prepare(`
	SELECT
	aperture, camera, exposure_comp, exposure_time, flash, focal_length,
	focal_length_35, height, id, iso, lat, lens, lng, next_photo_id, path,
	prev_photo_id, set_id, size, taken_at, width
	FROM photos
	WHERE id = ?
	`)
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}
}

func main() {
	setupDatabase()
	defer db.Close()
	defer getSetStmt.Close()
	defer getPhotoStmt.Close()

	http.Handle("/", http.FileServer(http.Dir("public"))) // static
	http.HandleFunc("/set", getSetHandler)
	http.HandleFunc("/photo", getPhotoHandler)

	fmt.Printf("Listening on http://%s\n", listenAddr)
	fmt.Println("Press Ctrl-C to exit")

	log.Fatal(http.ListenAndServe(listenAddr, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux)))
}
<commit_msg>Complete JSON API with responses for sets and photos<commit_after>package main

import (
	"crypto/md5"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"math"
	"math/big"
	"net/http"
	"os"
	"path"
	"strconv"

	"github.com/gorilla/handlers"
	_ "github.com/mattn/go-sqlite3"
)

const (
	listenAddr     = "127.0.0.1:9292"
	bigThumbSize   = 1000
	smallThumbSize = 200
)

var (
	db            *sql.DB
	getSetStmt    *sql.Stmt
	getSetsStmt   *sql.Stmt
	getPhotoStmt  *sql.Stmt
	getPhotosStmt *sql.Stmt
)

type Set struct {
	Id             int
	Name           string
	PhotosCount    int
	TakenAt        sql.NullString
	ThumbPhotoId   int
	ThumbPhotoPath string
}

type Photo struct {
	Aperture      sql.NullFloat64
	Camera        sql.NullString
	ExposureComp  sql.NullInt64
	ExposureTime  sql.NullFloat64
	Flash         sql.NullString
	FocalLength   sql.NullFloat64
	FocalLength35 sql.NullInt64
	Height        int64
	ISO           sql.NullInt64
	Id            int
	Lat           sql.NullFloat64
	Lens          sql.NullString
	Lng           sql.NullFloat64
	NextPhotoId   sql.NullInt64
	Path          string
	PrevPhotoId   sql.NullInt64
	SetId         int
	Size          int
	TakenAt       sql.NullString
	Width         int64
}

// used by scanSet and scanPhoto to accept row(s)
type RowScanner interface {
	Scan(dest ...interface{}) error
}

func thumbURL(photoPath, suffix string) string {
	identifier := fmt.Sprintf("%x", md5.Sum([]byte(photoPath)))
	return fmt.Sprintf("/thumbs/%s_%s.jpg", identifier, suffix)
}

func (s *Set) ThumbURL() string {
	return thumbURL(s.ThumbPhotoPath, "small")
}

func (s *Set) MarshalJSON() ([]byte, error) { // implements Marshaler
	setMap := map[string]interface{}{
		"id":             s.Id,
		"name":           s.Name,
		"photos_count":   s.PhotosCount,
		"thumb_photo_id": s.ThumbPhotoId,
		"thumb_url":      s.ThumbURL(),
	}
	setMap["taken_at"], _ = s.TakenAt.Value()
	return json.Marshal(setMap)
}

func (p *Photo) AspectRatio() [2]int64 {
	gcd := new(big.Int).GCD(nil, nil, big.NewInt(p.Width), big.NewInt(p.Height)).Int64()
	return [2]int64{p.Width / gcd, p.Height / gcd}
}

func (p *Photo) BigThumbHeight() int64 {
	if p.Orientation() == "portrait" {
		if p.Height < bigThumbSize {
			return p.Height
		}

		return bigThumbSize
	}

	aspectRatio := p.AspectRatio()
	return int64(math.Floor((float64(aspectRatio[1])/float64(aspectRatio[0]))*float64(p.BigThumbWidth()) + .5))
}

func (p *Photo) BigThumbWidth() int64 {
	if p.Orientation() == "portrait" {
		aspectRatio := p.AspectRatio()
		return int64(math.Floor((float64(aspectRatio[0])/float64(aspectRatio[1]))*float64(p.BigThumbHeight()) + .5))
	}

	if p.Width < bigThumbSize {
		return p.Width
	}

	return bigThumbSize
}

func (p *Photo) Filename() string {
	return path.Base(p.Path)
}

func (p *Photo) Orientation() string {
	if p.Height > p.Width {
		return "portrait"
	}
	return "landscape"
}

func (p *Photo) ThumbURL(suffix string) string {
	return thumbURL(p.Path, suffix)
}

func (p *Photo) MarshalJSON() ([]byte, error) { // implements Marshaler
	photoMap := map[string]interface{}{
		"aspect_ratio":     p.AspectRatio(),
		"big_thumb_height": p.BigThumbHeight(),
		"big_thumb_url":    p.ThumbURL("big"),
		"big_thumb_width":  p.BigThumbWidth(),
		"filename":         p.Filename(),
		"height":           p.Height,
		"id":               p.Id,
		"orientation":      p.Orientation(),
		"path":             p.Path,
		"set_id":           p.SetId,
		"size":             p.Size,
		"small_thumb_url":  p.ThumbURL("small"),
		"width":            p.Width,
	}

	photoMap["aperture"], _ = p.Aperture.Value()
	photoMap["camera"], _ = p.Camera.Value()
	photoMap["exposure_comp"], _ = p.ExposureComp.Value()
	photoMap["exposure_time"], _ = p.ExposureTime.Value()
	photoMap["flash"], _ = p.Flash.Value()
	photoMap["focal_length"], _ = p.FocalLength.Value()
	photoMap["focal_length_35"], _ = p.FocalLength35.Value()
	photoMap["iso"], _ = p.ISO.Value()
	photoMap["lat"], _ = p.Lat.Value()
	photoMap["lens"], _ = p.Lens.Value()
	photoMap["lng"], _ = p.Lng.Value()
	photoMap["next_photo_id"], _ = p.NextPhotoId.Value()
	photoMap["prev_photo_id"], _ = p.PrevPhotoId.Value()
	photoMap["taken_at"], _ = p.TakenAt.Value()

	return json.Marshal(photoMap)
}

func badRequest(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusBadRequest)
	fmt.Fprintln(w, http.StatusBadRequest, "Bad Request")
}

func internalServerError(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprintln(w, http.StatusInternalServerError, "Internal Server Error")
}

func requireParam(param string, w http.ResponseWriter, r *http.Request) error {
	if len(r.URL.Query()[param]) == 0 {
		badRequest(w, r)
		return errors.New(fmt.Sprintf("missing %s parameter", param))
	}
	return nil
}

func scanSet(row RowScanner, set *Set) error {
	return row.Scan(
		&set.Id,
		&set.Name,
		&set.PhotosCount,
		&set.TakenAt,
		&set.ThumbPhotoId,
		&set.ThumbPhotoPath,
	)
}

func getSetById(setId int) (set *Set, err error) {
	set = &Set{}
	row := getSetStmt.QueryRow(setId)
	err = scanSet(row, set)
	return
}

func getSets() (sets []*Set, err error) {
	rows, err := getSetsStmt.Query()
	if err != nil {
		return
	}
	defer rows.Close()

	for rows.Next() {
		set := Set{}
		if err = scanSet(rows, &set); err != nil {
			return
		}
		sets = append(sets, &set)
	}

	err = rows.Err()

	return
}

func scanPhoto(row RowScanner, photo *Photo) error {
	return row.Scan(
		&photo.Aperture,
		&photo.Camera,
		&photo.ExposureComp,
		&photo.ExposureTime,
		&photo.Flash,
		&photo.FocalLength,
		&photo.FocalLength35,
		&photo.Height,
		&photo.Id,
		&photo.ISO,
		&photo.Lat,
		&photo.Lens,
		&photo.Lng,
		&photo.NextPhotoId,
		&photo.Path,
		&photo.PrevPhotoId,
		&photo.SetId,
		&photo.Size,
		&photo.TakenAt,
		&photo.Width,
	)
}

func getPhotoById(photoId int) (photo *Photo, err error) {
	photo = &Photo{}
	row := getPhotoStmt.QueryRow(photoId)
	err = scanPhoto(row, photo)
	return
}

func getPhotosBySetId(setId int) (photos []*Photo, err error) {
	rows, err := getPhotosStmt.Query(setId)
	if err != nil {
		return
	}
	defer rows.Close()

	for rows.Next() {
		photo := Photo{}
		if err = scanPhoto(rows, &photo); err != nil {
			return
		}
		photos = append(photos, &photo)
	}

	err = rows.Err()

	return
}

func getSetHandler(w http.ResponseWriter, r *http.Request) {
	if requireParam("id", w, r) != nil {
		return
	}

	setId, err := strconv.Atoi(r.URL.Query()["id"][0])
	if err != nil {
		log.Fatalln("Failed to convert id to integer:", err)
	}

	set, err := getSetById(setId)
	if err == sql.ErrNoRows { // set does not exist
		http.NotFound(w, r)
		return
	}
	if err != nil {
		internalServerError(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(set)
}

func getSetsHandler(w http.ResponseWriter, r *http.Request) {
	sets, err := getSets()
	if err != nil {
		internalServerError(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(sets)
}

func getPhotoHandler(w http.ResponseWriter, r *http.Request) {
	if requireParam("id", w, r) != nil {
		return
	}

	photoId, err := strconv.Atoi(r.URL.Query()["id"][0])
	if err != nil {
		log.Fatalln("Failed to convert id to integer:", err)
	}

	photo, err := getPhotoById(photoId)
	if err == sql.ErrNoRows { // photo does not exist
		http.NotFound(w, r)
		return
	}
	if err != nil {
		internalServerError(w, r)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(photo)
}

func getPhotosHandler(w http.ResponseWriter, r *http.Request) {
	if requireParam("set_id", w, r) != nil {
		return
	}

	setId, err := strconv.Atoi(r.URL.Query()["set_id"][0])
	if err != nil {
		log.Fatalln("Failed to convert id to integer:", err)
	}

	photos, err := getPhotosBySetId(setId)
	if err != nil {
		internalServerError(w, r)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(photos)
}

func setupDatabase() {
	db, err := sql.Open("sqlite3", "thyme.db")
	if err != nil {
		log.Fatalln("Failed to open database:", err)
	}

	setAttrs := `sets.id, name, photos_count, sets.taken_at, thumb_photo_id,
	photos.path`

	getSetStmt, err = db.Prepare(fmt.Sprintf(`
	SELECT %s
	FROM sets
	JOIN photos ON sets.thumb_photo_id = photos.id
	WHERE sets.id = ?
	`, setAttrs))
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}

	getSetsStmt, err = db.Prepare(fmt.Sprintf(`
	SELECT %s
	FROM sets
	JOIN photos ON sets.thumb_photo_id = photos.id
	ORDER BY sets.taken_at DESC
	`, setAttrs))
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}

	photoAttrs := `aperture, camera, exposure_comp, exposure_time, flash,
	focal_length, focal_length_35, height, id, iso, lat, lens, lng,
	next_photo_id, path, prev_photo_id, set_id, size, taken_at, width`

	getPhotoStmt, err = db.Prepare(fmt.Sprintf(`
	SELECT %s
	FROM photos
	WHERE id = ?
	`, photoAttrs))
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}

	getPhotosStmt, err = db.Prepare(fmt.Sprintf(`
	SELECT %s
	FROM photos
	WHERE set_id = ?
	`, photoAttrs))
	if err != nil {
		log.Fatalln("Failed to access table:", err)
	}
}

func main() {
	setupDatabase()
	defer db.Close()
	defer getSetStmt.Close()
	defer getSetsStmt.Close()
	defer getPhotoStmt.Close()
	defer getPhotosStmt.Close()

	http.Handle("/", http.FileServer(http.Dir("public"))) // static
	http.HandleFunc("/set", getSetHandler)
	http.HandleFunc("/sets", getSetsHandler)
	http.HandleFunc("/photo", getPhotoHandler)
	http.HandleFunc("/photos", getPhotosHandler)

	fmt.Printf("Listening on http://%s\n", listenAddr)
	fmt.Println("Press Ctrl-C to exit")

	log.Fatal(http.ListenAndServe(listenAddr, handlers.LoggingHandler(os.Stdout, http.DefaultServeMux)))
}
<|endoftext|>


<commit_before>// The `fwd` package provides a buffered reader
// and writer. Each has methods that help improve
// the encoding/decoding performance of some binary
// protocols.
//
// The `fwd.Writer` and `fwd.Reader` type provide similar
// functionality to their counterparts in `bufio`, plus
// a few extra utility methods that simplify read-ahead
// and write-ahead. I wrote this package to improve serialization
// performance for http://github.com/tinylib/msgp,
// where it provided about a 2x speedup over `bufio` for certain
// workloads. However, care must be taken to understand the semantics of the
// extra methods provided by this package, as they allow
// the user to access and manipulate the buffer memory
// directly.
//
// The extra methods for `fwd.Reader` are `Peek`, `Skip`
// and `Next`. `(*fwd.Reader).Peek`, unlike `(*bufio.Reader).Peek`,
// will re-allocate the read buffer in order to accommodate arbitrarily
// large read-ahead. `(*fwd.Reader).Skip` skips the next `n` bytes
// in the stream, and uses the `io.Seeker` interface if the underlying
// stream implements it. `(*fwd.Reader).Next` returns a slice pointing
// to the next `n` bytes in the read buffer (like `Peek`), but also
// increments the read position. This allows users to process streams
// in arbitrary block sizes without having to manage appropriately-sized
// slices. Additionally, obviating the need to copy the data from the
// buffer to another location in memory can improve performance dramatically
// in CPU-bound applications.
//
// `fwd.Writer` only has one extra method, which is `(*fwd.Writer).Next`, which
// returns a slice pointing to the next `n` bytes of the writer, and increments
// the write position by the length of the returned slice. This allows users
// to write directly to the end of the buffer.
//
package fwd

import "io"

const (
	// DefaultReaderSize is the default size of the read buffer
	DefaultReaderSize = 2048

	// minimum read buffer; straight from bufio
	minReaderSize = 16
)

// NewReader returns a new *Reader that reads from 'r'
func NewReader(r io.Reader) *Reader {
	return NewReaderSize(r, DefaultReaderSize)
}

// NewReaderSize returns a new *Reader that
// reads from 'r' and has a buffer size 'n'
func NewReaderSize(r io.Reader, n int) *Reader {
	rd := &Reader{
		r:    r,
		data: make([]byte, 0, max(minReaderSize, n)),
	}
	if s, ok := r.(io.Seeker); ok {
		rd.rs = s
	}
	return rd
}

// Reader is a buffered look-ahead reader
type Reader struct {
	r io.Reader // underlying reader

	// data[n:len(data)] is buffered data; data[len(data):cap(data)] is free buffer space
	data  []byte // data
	n     int    // read offset
	state error  // last read error

	// if the reader past to NewReader was
	// also an io.Seeker, this is non-nil
	rs io.Seeker
}

// Reset resets the underlying reader
// and the read buffer.
func (r *Reader) Reset(rd io.Reader) {
	r.r = rd
	r.data = r.data[0:0]
	r.n = 0
	r.state = nil
	if s, ok := rd.(io.Seeker); ok {
		r.rs = s
	} else {
		r.rs = nil
	}
}

// more() does one read on the underlying reader
func (r *Reader) more() {
	// move data backwards so that
	// the read offset is 0; this way
	// we can supply the maximum number of
	// bytes to the reader
	if r.n != 0 {
		if r.n < len(r.data) {
			r.data = r.data[:copy(r.data[0:], r.data[r.n:])]
		} else {
			r.data = r.data[:0]
		}
		r.n = 0
	}
	var a int
	a, r.state = r.r.Read(r.data[len(r.data):cap(r.data)])
	if a == 0 && r.state == nil {
		r.state = io.ErrNoProgress
		return
	}
	r.data = r.data[:len(r.data)+a]
}

// pop error
func (r *Reader) err() (e error) {
	e, r.state = r.state, nil
	return
}

// pop error; EOF -> io.ErrUnexpectedEOF
func (r *Reader) noEOF() (e error) {
	e, r.state = r.state, nil
	if e == io.EOF {
		e = io.ErrUnexpectedEOF
	}
	return
}

// buffered bytes
func (r *Reader) buffered() int { return len(r.data) - r.n }

// Buffered returns the number of bytes currently in the buffer
func (r *Reader) Buffered() int { return len(r.data) - r.n }

// BufferSize returns the total size of the buffer
func (r *Reader) BufferSize() int { return cap(r.data) }

// Peek returns the next 'n' buffered bytes,
// reading from the underlying reader if necessary.
// It will only return a slice shorter than 'n' bytes
// if it also returns an error. Peek does not advance
// the reader. EOF errors are *not* returned as
// io.ErrUnexpectedEOF.
func (r *Reader) Peek(n int) ([]byte, error) {
	// in the degenerate case,
	// we may need to realloc
	// (the caller asked for more
	// bytes than the size of the buffer)
	if cap(r.data) < n {
		old := r.data[r.n:]
		r.data = make([]byte, n+r.buffered())
		r.data = r.data[:copy(r.data, old)]
		r.n = 0
	}

	// keep filling until
	// we hit an error or
	// read enough bytes
	for r.buffered() < n && r.state == nil {
		r.more()
	}

	// we must have hit an error
	if r.buffered() < n {
		return r.data[r.n:], r.err()
	}

	return r.data[r.n : r.n+n], nil
}

// Skip moves the reader forward 'n' bytes.
// Returns the number of bytes skipped and any
// errors encountered. It is analogous to Seek(n, 1).
// If the underlying reader implements io.Seeker, then
// that method will be used to skip forward.
//
// If the reader encounters
// an EOF before skipping 'n' bytes, it
// returns io.ErrUnexpectedEOF. If the
// underlying reader implements io.Seeker, then
// those rules apply instead. (Many implementations
// will not return `io.EOF` until the next call
// to Read.)
func (r *Reader) Skip(n int) (int, error) {

	// fast path
	if r.buffered() >= n {
		r.n += n
		return n, nil
	}

	// use seeker implementation
	// if we can
	if r.rs != nil {
		return r.skipSeek(n)
	}

	// loop on filling
	// and then erasing
	o := n
	for r.buffered() < n && r.state == nil {
		r.more()
		// we can skip forward
		// up to r.buffered() bytes
		step := min(r.buffered(), n)
		r.n += step
		n -= step
	}
	// at this point, n should be
	// 0 if everything went smoothly
	return o - n, r.noEOF()
}

// Next returns the next 'n' bytes in the stream.
// Unlike Peek, Next advances the reader position.
// The returned bytes point to the same
// data as the buffer, so the slice is
// only valid until the next reader method call.
// An EOF is considered an unexpected error.
// If an the returned slice is less than the
// length asked for, an error will be returned,
// and the reader position will not be incremented.
func (r *Reader) Next(n int) ([]byte, error) {

	// in case the buffer is too small
	if cap(r.data) < n {
		old := r.data[r.n:]
		r.data = make([]byte, n+r.buffered())
		r.data = r.data[:copy(r.data, old)]
		r.n = 0
	}

	// fill at least 'n' bytes
	for r.buffered() < n && r.state == nil {
		r.more()
	}

	if r.buffered() < n {
		return r.data[r.n:], r.noEOF()
	}
	out := r.data[r.n : r.n+n]
	r.n += n
	return out, nil
}

// skipSeek uses the io.Seeker to seek forward.
// only call this function when n > r.buffered()
func (r *Reader) skipSeek(n int) (int, error) {
	o := r.buffered()
	// first, clear buffer
	n -= o
	r.n = 0
	r.data = r.data[:0]

	// then seek forward remaning bytes
	i, err := r.rs.Seek(int64(n), 1)
	return int(i) + o, err
}

// Read implements `io.Reader`
func (r *Reader) Read(b []byte) (int, error) {
	// if we have data in the buffer, just
	// return that.
	if r.buffered() != 0 {
		x := copy(b, r.data[r.n:])
		r.n += x
		return x, nil
	}
	var n int
	// we have no buffered data; determine
	// whether or not to buffer or call
	// the underlying reader directly
	if len(b) >= cap(r.data) {
		n, r.state = r.r.Read(b)
	} else {
		r.more()
		n = copy(b, r.data)
		r.n = n
	}
	if n == 0 {
		return 0, r.err()
	}
	return n, nil
}

// ReadFull attempts to read len(b) bytes into
// 'b'. It returns the number of bytes read into
// 'b', and an error if it does not return len(b).
// EOF is considered an unexpected error.
func (r *Reader) ReadFull(b []byte) (int, error) {
	var n int  // read into b
	var nn int // scratch
	l := len(b)
	// either read buffered data,
	// or read directly for the underlying
	// buffer, or fetch more buffered data.
	for n < l && r.state == nil {
		if r.buffered() != 0 {
			nn = copy(b[n:], r.data[r.n:])
			n += nn
			r.n += nn
		} else if l-n > cap(r.data) {
			nn, r.state = r.r.Read(b[n:])
			n += nn
		} else {
			r.more()
		}
	}
	if n < l {
		return n, r.noEOF()
	}
	return n, nil
}

// ReadByte implements `io.ByteReader`
func (r *Reader) ReadByte() (byte, error) {
	for r.buffered() < 1 && r.state == nil {
		r.more()
	}
	if r.buffered() < 1 {
		return 0, r.err()
	}
	b := r.data[r.n]
	r.n++
	return b, nil
}

// WriteTo implements `io.WriterTo`
func (r *Reader) WriteTo(w io.Writer) (int64, error) {
	var (
		i   int64
		ii  int
		err error
	)
	// first, clear buffer
	if r.buffered() > 0 {
		ii, err = w.Write(r.data[r.n:])
		i += int64(ii)
		if err != nil {
			return i, err
		}
		r.data = r.data[0:0]
		r.n = 0
	}
	for r.state == nil {
		// here we just do
		// 1:1 reads and writes
		r.more()
		if r.buffered() > 0 {
			ii, err = w.Write(r.data)
			i += int64(ii)
			if err != nil {
				return i, err
			}
			r.data = r.data[0:0]
			r.n = 0
		}
	}
	if r.state != io.EOF {
		return i, r.err()
	}
	return i, nil
}

func min(a int, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a int, b int) int {
	if a < b {
		return b
	}
	return a
}
<commit_msg>Discard io.EOF when more than 0 bytes have been read<commit_after>// The `fwd` package provides a buffered reader
// and writer. Each has methods that help improve
// the encoding/decoding performance of some binary
// protocols.
//
// The `fwd.Writer` and `fwd.Reader` type provide similar
// functionality to their counterparts in `bufio`, plus
// a few extra utility methods that simplify read-ahead
// and write-ahead. I wrote this package to improve serialization
// performance for http://github.com/tinylib/msgp,
// where it provided about a 2x speedup over `bufio` for certain
// workloads. However, care must be taken to understand the semantics of the
// extra methods provided by this package, as they allow
// the user to access and manipulate the buffer memory
// directly.
//
// The extra methods for `fwd.Reader` are `Peek`, `Skip`
// and `Next`. `(*fwd.Reader).Peek`, unlike `(*bufio.Reader).Peek`,
// will re-allocate the read buffer in order to accommodate arbitrarily
// large read-ahead. `(*fwd.Reader).Skip` skips the next `n` bytes
// in the stream, and uses the `io.Seeker` interface if the underlying
// stream implements it. `(*fwd.Reader).Next` returns a slice pointing
// to the next `n` bytes in the read buffer (like `Peek`), but also
// increments the read position. This allows users to process streams
// in arbitrary block sizes without having to manage appropriately-sized
// slices. Additionally, obviating the need to copy the data from the
// buffer to another location in memory can improve performance dramatically
// in CPU-bound applications.
//
// `fwd.Writer` only has one extra method, which is `(*fwd.Writer).Next`, which
// returns a slice pointing to the next `n` bytes of the writer, and increments
// the write position by the length of the returned slice. This allows users
// to write directly to the end of the buffer.
//
package fwd

import "io"

const (
	// DefaultReaderSize is the default size of the read buffer
	DefaultReaderSize = 2048

	// minimum read buffer; straight from bufio
	minReaderSize = 16
)

// NewReader returns a new *Reader that reads from 'r'
func NewReader(r io.Reader) *Reader {
	return NewReaderSize(r, DefaultReaderSize)
}

// NewReaderSize returns a new *Reader that
// reads from 'r' and has a buffer size 'n'
func NewReaderSize(r io.Reader, n int) *Reader {
	rd := &Reader{
		r:    r,
		data: make([]byte, 0, max(minReaderSize, n)),
	}
	if s, ok := r.(io.Seeker); ok {
		rd.rs = s
	}
	return rd
}

// Reader is a buffered look-ahead reader
type Reader struct {
	r io.Reader // underlying reader

	// data[n:len(data)] is buffered data; data[len(data):cap(data)] is free buffer space
	data  []byte // data
	n     int    // read offset
	state error  // last read error

	// if the reader past to NewReader was
	// also an io.Seeker, this is non-nil
	rs io.Seeker
}

// Reset resets the underlying reader
// and the read buffer.
func (r *Reader) Reset(rd io.Reader) {
	r.r = rd
	r.data = r.data[0:0]
	r.n = 0
	r.state = nil
	if s, ok := rd.(io.Seeker); ok {
		r.rs = s
	} else {
		r.rs = nil
	}
}

// more() does one read on the underlying reader
func (r *Reader) more() {
	// move data backwards so that
	// the read offset is 0; this way
	// we can supply the maximum number of
	// bytes to the reader
	if r.n != 0 {
		if r.n < len(r.data) {
			r.data = r.data[:copy(r.data[0:], r.data[r.n:])]
		} else {
			r.data = r.data[:0]
		}
		r.n = 0
	}
	var a int
	a, r.state = r.r.Read(r.data[len(r.data):cap(r.data)])
	if a == 0 && r.state == nil {
		r.state = io.ErrNoProgress
		return
	} else if a > 0 && r.state == io.EOF {
		// discard the io.EOF if we read more than 0 bytes.
		// the next call to Read should return io.EOF again.
		r.state = nil
	}
	r.data = r.data[:len(r.data)+a]
}

// pop error
func (r *Reader) err() (e error) {
	e, r.state = r.state, nil
	return
}

// pop error; EOF -> io.ErrUnexpectedEOF
func (r *Reader) noEOF() (e error) {
	e, r.state = r.state, nil
	if e == io.EOF {
		e = io.ErrUnexpectedEOF
	}
	return
}

// buffered bytes
func (r *Reader) buffered() int { return len(r.data) - r.n }

// Buffered returns the number of bytes currently in the buffer
func (r *Reader) Buffered() int { return len(r.data) - r.n }

// BufferSize returns the total size of the buffer
func (r *Reader) BufferSize() int { return cap(r.data) }

// Peek returns the next 'n' buffered bytes,
// reading from the underlying reader if necessary.
// It will only return a slice shorter than 'n' bytes
// if it also returns an error. Peek does not advance
// the reader. EOF errors are *not* returned as
// io.ErrUnexpectedEOF.
func (r *Reader) Peek(n int) ([]byte, error) {
	// in the degenerate case,
	// we may need to realloc
	// (the caller asked for more
	// bytes than the size of the buffer)
	if cap(r.data) < n {
		old := r.data[r.n:]
		r.data = make([]byte, n+r.buffered())
		r.data = r.data[:copy(r.data, old)]
		r.n = 0
	}

	// keep filling until
	// we hit an error or
	// read enough bytes
	for r.buffered() < n && r.state == nil {
		r.more()
	}

	// we must have hit an error
	if r.buffered() < n {
		return r.data[r.n:], r.err()
	}

	return r.data[r.n : r.n+n], nil
}

// Skip moves the reader forward 'n' bytes.
// Returns the number of bytes skipped and any
// errors encountered. It is analogous to Seek(n, 1).
// If the underlying reader implements io.Seeker, then
// that method will be used to skip forward.
//
// If the reader encounters
// an EOF before skipping 'n' bytes, it
// returns io.ErrUnexpectedEOF. If the
// underlying reader implements io.Seeker, then
// those rules apply instead. (Many implementations
// will not return `io.EOF` until the next call
// to Read.)
func (r *Reader) Skip(n int) (int, error) {

	// fast path
	if r.buffered() >= n {
		r.n += n
		return n, nil
	}

	// use seeker implementation
	// if we can
	if r.rs != nil {
		return r.skipSeek(n)
	}

	// loop on filling
	// and then erasing
	o := n
	for r.buffered() < n && r.state == nil {
		r.more()
		// we can skip forward
		// up to r.buffered() bytes
		step := min(r.buffered(), n)
		r.n += step
		n -= step
	}
	// at this point, n should be
	// 0 if everything went smoothly
	return o - n, r.noEOF()
}

// Next returns the next 'n' bytes in the stream.
// Unlike Peek, Next advances the reader position.
// The returned bytes point to the same
// data as the buffer, so the slice is
// only valid until the next reader method call.
// An EOF is considered an unexpected error.
// If an the returned slice is less than the
// length asked for, an error will be returned,
// and the reader position will not be incremented.
func (r *Reader) Next(n int) ([]byte, error) {

	// in case the buffer is too small
	if cap(r.data) < n {
		old := r.data[r.n:]
		r.data = make([]byte, n+r.buffered())
		r.data = r.data[:copy(r.data, old)]
		r.n = 0
	}

	// fill at least 'n' bytes
	for r.buffered() < n && r.state == nil {
		r.more()
	}

	if r.buffered() < n {
		return r.data[r.n:], r.noEOF()
	}
	out := r.data[r.n : r.n+n]
	r.n += n
	return out, nil
}

// skipSeek uses the io.Seeker to seek forward.
// only call this function when n > r.buffered()
func (r *Reader) skipSeek(n int) (int, error) {
	o := r.buffered()
	// first, clear buffer
	n -= o
	r.n = 0
	r.data = r.data[:0]

	// then seek forward remaning bytes
	i, err := r.rs.Seek(int64(n), 1)
	return int(i) + o, err
}

// Read implements `io.Reader`
func (r *Reader) Read(b []byte) (int, error) {
	// if we have data in the buffer, just
	// return that.
	if r.buffered() != 0 {
		x := copy(b, r.data[r.n:])
		r.n += x
		return x, nil
	}
	var n int
	// we have no buffered data; determine
	// whether or not to buffer or call
	// the underlying reader directly
	if len(b) >= cap(r.data) {
		n, r.state = r.r.Read(b)
	} else {
		r.more()
		n = copy(b, r.data)
		r.n = n
	}
	if n == 0 {
		return 0, r.err()
	}
	return n, nil
}

// ReadFull attempts to read len(b) bytes into
// 'b'. It returns the number of bytes read into
// 'b', and an error if it does not return len(b).
// EOF is considered an unexpected error.
func (r *Reader) ReadFull(b []byte) (int, error) {
	var n int  // read into b
	var nn int // scratch
	l := len(b)
	// either read buffered data,
	// or read directly for the underlying
	// buffer, or fetch more buffered data.
	for n < l && r.state == nil {
		if r.buffered() != 0 {
			nn = copy(b[n:], r.data[r.n:])
			n += nn
			r.n += nn
		} else if l-n > cap(r.data) {
			nn, r.state = r.r.Read(b[n:])
			n += nn
		} else {
			r.more()
		}
	}
	if n < l {
		return n, r.noEOF()
	}
	return n, nil
}

// ReadByte implements `io.ByteReader`
func (r *Reader) ReadByte() (byte, error) {
	for r.buffered() < 1 && r.state == nil {
		r.more()
	}
	if r.buffered() < 1 {
		return 0, r.err()
	}
	b := r.data[r.n]
	r.n++
	return b, nil
}

// WriteTo implements `io.WriterTo`
func (r *Reader) WriteTo(w io.Writer) (int64, error) {
	var (
		i   int64
		ii  int
		err error
	)
	// first, clear buffer
	if r.buffered() > 0 {
		ii, err = w.Write(r.data[r.n:])
		i += int64(ii)
		if err != nil {
			return i, err
		}
		r.data = r.data[0:0]
		r.n = 0
	}
	for r.state == nil {
		// here we just do
		// 1:1 reads and writes
		r.more()
		if r.buffered() > 0 {
			ii, err = w.Write(r.data)
			i += int64(ii)
			if err != nil {
				return i, err
			}
			r.data = r.data[0:0]
			r.n = 0
		}
	}
	if r.state != io.EOF {
		return i, r.err()
	}
	return i, nil
}

func min(a int, b int) int {
	if a < b {
		return a
	}
	return b
}

func max(a int, b int) int {
	if a < b {
		return b
	}
	return a
}
<|endoftext|>


<commit_before>package gce

import "testing"

import "fmt"

func TestStartnode(t *testing.T) {
	var gce GCE
	start := map[string]string{
		"projectid": "sheltermap-1493101612061",
		"instance":  "sumesh-110",
		"Zone":      "us-east4-c",
	}
	resp, _ := gce.Startnode(start)

	response := resp.(map[string]interface{})

	if response["status"] == "200" {
		fmt.Println(" Test pass")
	} else {
		fmt.Println(" Test fail")
	}
}
<commit_msg>go test gce instance<commit_after>package gce

import "testing"

import "fmt"

func TestStartnode(t *testing.T) {
	var gce GCE
	start := map[string]string{
		"projectid": "sheltermap-1493101612061",
		"instance":  "sumesh-110",
		"Zone":      "us-east4-c",
	}
	resp, _ := gce.Startnode(start)

	response := resp.(map[string]interface{})

	if response["status"] == "200" {
		fmt.Println(" Test pass")
	} else {
		fmt.Println(" Test fail")
	}
}

func TestStopnode(t *testing.T) {
	var gce GCE

	stop := map[string]string{
  "projectid":"sheltermap-1493101612061",
	"instance":"instance-10",
	"Zone": "us-west1-c",
 }

 resp, _ := gce.Stopnode(stop)

 response := resp.(map[string]interface{})

 if response["status"] == "200" {
 	fmt.Println(" Test pass")
 } else {
 	fmt.Println(" Test fail")
 }

 }
<|endoftext|>


<commit_before>package braintree

import (
	"testing"

	"github.com/shopspring/decimal"
)

func TestCreateTransaction(t *testing.T) {
	t.Parallel()

	customer, err := bt.Customer().Create(CustomerInput{
		FirstName: "first",
		CreditCard: &CreditCardInput{
			PaymentMethodNonce: "fake-valid-visa-nonce",
		},
	})
	if err != nil {
		t.Fatalf("unexpected err: %s", err)
	}

	tests := []struct {
		name    string
		txInput TransactionInput
		wantErr error
	}{
		{
			name: "shouldWork",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(3),
				Options: &TransactionOptions{
					StoreInVaultOnSuccess: true,
				},
				PaymentMethodToken: customer.CreditCards[0].Token,
				Type:               TransactionTypeSale,
			},
			wantErr: nil,
		},
		{
			name: "withoutToken",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(3),
				Type:   TransactionTypeSale,
			},
			wantErr: &ValidationError{"", 91508, "Cannot determine payment method."},
		},
		{
			name: "paymentFailed",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(2000),
				Options: &TransactionOptions{
					StoreInVaultOnSuccess: true,
				},
				PaymentMethodToken: customer.CreditCards[0].Token,
				Type:               TransactionTypeSale,
			},
			wantErr: &ProcessorError{2000, "Do Not Honor"},
		},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			_, err := bt.Transaction().Create(test.txInput)
			compareErrors(t, err, test.wantErr)
		})
	}

	t.Run("duplicate", func(t *testing.T) {
		t.Parallel()

		txInput := TransactionInput{
			Amount: decimal.NewFromFloat(3.8),
			Options: &TransactionOptions{
				StoreInVaultOnSuccess: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		}

		if _, err := bt.Transaction().Create(txInput); err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		_, err := bt.Transaction().Create(txInput)
		wantErr := &GatewayError{"duplicate"}
		compareErrors(t, err, wantErr)
	})
}

func TestFindTransaction(t *testing.T) {
	t.Parallel()

	t.Run("existing", func(t *testing.T) {
		t.Parallel()

		transaction, err := bt.Transaction().Find("bx9a7av8")
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSettled {
			t.Errorf("transaction.Status: got %s, want %s", transaction.Status, TransactionStatusSettled)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Find("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}

func TestRefundTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.7),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Settle(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSettled {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusSettled, transaction.Status)
		}

		transaction2, err := bt.Transaction().Refund(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction2.Status != TransactionStatusSubmittedForSettlement {
			t.Errorf("transaction2.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}
		if transaction2.RefundedTransactionID != transaction.ID {
			t.Errorf("transaction2.RefundedTransactionID: expected %s, got %s", transaction.ID, transaction2.RefundedTransactionID)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Refund("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}

func TestSettleTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.6),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Settle(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSettled {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusSettled, transaction.Status)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Settle("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}

func TestVoidTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.5),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Void(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusVoided {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusVoided, transaction.Status)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Void("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}
<commit_msg>refactor TestFindTransaction<commit_after>package braintree

import (
	"errors"
	"testing"

	"github.com/shopspring/decimal"
)

func TestCreateTransaction(t *testing.T) {
	t.Parallel()

	customer, err := bt.Customer().Create(CustomerInput{
		FirstName: "first",
		CreditCard: &CreditCardInput{
			PaymentMethodNonce: "fake-valid-visa-nonce",
		},
	})
	if err != nil {
		t.Fatalf("unexpected err: %s", err)
	}

	tests := []struct {
		name    string
		txInput TransactionInput
		wantErr error
	}{
		{
			name: "shouldWork",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(3),
				Options: &TransactionOptions{
					StoreInVaultOnSuccess: true,
				},
				PaymentMethodToken: customer.CreditCards[0].Token,
				Type:               TransactionTypeSale,
			},
			wantErr: nil,
		},
		{
			name: "withoutToken",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(3),
				Type:   TransactionTypeSale,
			},
			wantErr: &ValidationError{"", 91508, "Cannot determine payment method."},
		},
		{
			name: "paymentFailed",
			txInput: TransactionInput{
				Amount: decimal.NewFromFloat(2000),
				Options: &TransactionOptions{
					StoreInVaultOnSuccess: true,
				},
				PaymentMethodToken: customer.CreditCards[0].Token,
				Type:               TransactionTypeSale,
			},
			wantErr: &ProcessorError{2000, "Do Not Honor"},
		},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			_, err := bt.Transaction().Create(test.txInput)
			compareErrors(t, err, test.wantErr)
		})
	}

	t.Run("duplicate", func(t *testing.T) {
		t.Parallel()

		txInput := TransactionInput{
			Amount: decimal.NewFromFloat(3.8),
			Options: &TransactionOptions{
				StoreInVaultOnSuccess: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		}

		if _, err := bt.Transaction().Create(txInput); err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		_, err := bt.Transaction().Create(txInput)
		wantErr := &GatewayError{"duplicate"}
		compareErrors(t, err, wantErr)
	})
}

func TestFindTransaction(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		id      string
		wantErr error
	}{
		{name: "existing", id: "bx9a7av8", wantErr: nil},
		{name: "nonExisting", id: "nonExisting", wantErr: errors.New("404 Not Found")},
	}

	for _, test := range tests {
		test := test
		t.Run(test.name, func(t *testing.T) {
			t.Parallel()
			_, err := bt.Transaction().Find(test.id)
			compareErrors(t, err, test.wantErr)
		})
	}
}

func TestRefundTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.7),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Settle(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSettled {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusSettled, transaction.Status)
		}

		transaction2, err := bt.Transaction().Refund(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction2.Status != TransactionStatusSubmittedForSettlement {
			t.Errorf("transaction2.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}
		if transaction2.RefundedTransactionID != transaction.ID {
			t.Errorf("transaction2.RefundedTransactionID: expected %s, got %s", transaction.ID, transaction2.RefundedTransactionID)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Refund("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}

func TestSettleTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.6),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Settle(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSettled {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusSettled, transaction.Status)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Settle("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}

func TestVoidTransaction(t *testing.T) {
	t.Parallel()

	t.Run("shouldWork", func(t *testing.T) {
		t.Parallel()

		customer, err := bt.Customer().Create(CustomerInput{
			FirstName: "first",
			CreditCard: &CreditCardInput{
				PaymentMethodNonce: "fake-valid-visa-nonce",
			},
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}

		transaction, err := bt.Transaction().Create(TransactionInput{
			Amount: decimal.NewFromFloat(3.5),
			Options: &TransactionOptions{
				SubmitForSettlement: true,
			},
			PaymentMethodToken: customer.CreditCards[0].Token,
			Type:               TransactionTypeSale,
		})
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusSubmittedForSettlement {
			t.Fatalf("transaction.Status: expected %s, got %s", TransactionStatusSubmittedForSettlement, transaction.Status)
		}

		transaction, err = bt.Transaction().Void(transaction.ID)
		if err != nil {
			t.Fatalf("unexpected err: %s", err)
		}
		if transaction.Status != TransactionStatusVoided {
			t.Errorf("transaction.Status: expected %s, got %s", TransactionStatusVoided, transaction.Status)
		}
	})

	t.Run("nonExisting", func(t *testing.T) {
		t.Parallel()

		if _, err := bt.Transaction().Void("nonExisting"); err == nil || err.Error() != "404 Not Found" {
			t.Errorf("got: %v, want: 404 Not Found", err)
		}
	})
}
<|endoftext|>


<commit_before>package transport

import (
	"errors"
	"fmt"
	"logger"
	"net"
	"stash.cloudflare.com/go-stream/stream"
	"stash.cloudflare.com/go-stream/stream/sink"
	"stash.cloudflare.com/go-stream/stream/source"
	"stash.cloudflare.com/go-stream/util"
	"stash.cloudflare.com/go-stream/util/slog"
	"sync"
	"time"
)

const RETRIES = 3
const ACK_TIMEOUT_MS = 10000

type Client struct {
	*stream.HardStopChannelCloser
	*stream.BaseIn
	addr string
	//id string
	hwm      int
	buf      util.SequentialBuffer
	retries  int
	running  bool
	notifier stream.ProcessedNotifier
}

func DefaultClient(ip string) *Client {
	return NewClient(fmt.Sprintf("%s:4558", ip), DEFAULT_HWM)
}

func NewClient(addr string, hwm int) *Client {
	buf := util.NewSequentialBufferChanImpl(hwm + 1)
	return &Client{stream.NewHardStopChannelCloser(), stream.NewBaseIn(stream.CHAN_SLACK), addr, hwm, buf, 0, false, nil}
}

func (src *Client) SetNotifier(n stream.ProcessedNotifier) *Client {
	if n.Blocking() == true {
		slog.Fatalf("Can't use a blocking Notifier")
	}
	src.notifier = n
	return src
}

func (src *Client) processAck(seq int) (progress bool) {
	//log.Println("Processing ack", seq)
	cnt := src.buf.Ack(seq)
	if cnt > 0 {
		if src.notifier != nil {
			src.notifier.Notify(cnt)
		}
		src.retries = 0
		return true
	}
	return false
}

func (c *Client) ReConnect() error {
	if c.IsRunning() {
		return errors.New("Still Running")
	}
	c.retries = 0
	return c.Run()
}

func (src *Client) Run() error {
	src.running = true
	defer func() {
		src.running = false
	}()

	slog.Gm.Register(stream.Name(src))
	go func(op string, s *Client) { // Update the queue depth on input for each phase
		for {
			slog.Gm.Update(&op, s.GetInDepth())
			time.Sleep(1 * time.Second)
		}
	}(stream.Name(src), src)

	for src.retries < 3 {
		err := src.connect()
		if err == nil {
			slog.Logf(logger.Levels.Warn, "Connection failed without error")
			return err
		} else {
			slog.Logf(logger.Levels.Error, "Connection failed with error, retrying: %s", err)
		}
	}
	slog.Logf(logger.Levels.Error, "Connection failed retries exceeded. Leftover: %d", src.buf.Len())
	return nil //>>>>>>>>>>>>>>???????????????????????
}

func (src Client) IsRunning() bool {
	return src.running
}

func (src Client) Len() (int, error) {
	if src.IsRunning() {
		return -1, errors.New("Still Running")
	}
	return src.buf.Len(), nil
}

func (src *Client) resetAckTimer() (timer <-chan time.Time) {
	if src.buf.Len() > 0 {
		return time.After(ACK_TIMEOUT_MS * time.Millisecond)
	}
	return nil
}

func (src *Client) connect() error {
	defer func() {
		src.retries++
	}()

	conn, err := net.Dial("tcp", src.addr)
	if err != nil {
		slog.Logf(logger.Levels.Error, "Cannot establish a connection with %s %v", src.addr, err)
		return err
	}

	wg_sub := &sync.WaitGroup{}
	defer wg_sub.Wait()

	rcvChData := make(chan stream.Object, 10)
	receiver := source.NewIOReaderSourceLengthDelim(conn)
	receiver.SetOut(rcvChData)
	rcvChCloseNotifier := make(chan bool)
	wg_sub.Add(1)
	go func() {
		defer wg_sub.Done()
		defer close(rcvChCloseNotifier)
		err := receiver.Run()
		if err != nil {
			slog.Logf(logger.Levels.Error, "Error in client reciever: %v", err)
		}
	}()
	//receiver will be closed by the sender after it is done sending. receiver closed via a hard stop.

	writeNotifier := stream.NewNonBlockingProcessedNotifier(2)
	sndChData := make(chan stream.Object, src.hwm)
	sndChCloseNotifier := make(chan bool)
	defer close(sndChData)
	sender := sink.NewMultiPartWriterSink(conn)
	sender.CompletedNotifier = writeNotifier
	sender.SetIn(sndChData)
	wg_sub.Add(1)
	go func() {
		defer receiver.Stop() //close receiver
		defer wg_sub.Done()
		defer close(sndChCloseNotifier)
		err := sender.Run()
		if err != nil {
			slog.Logf(logger.Levels.Error, "Error in client sender: %v", err)
		}
	}()
	//sender closed by closing the sndChData channel or by a hard stop

	if src.buf.Len() > 0 {
		leftover := src.buf.Reset()
		for i, value := range leftover {
			sendData(sndChData, value, i+1)
		}
	}

	timer := src.resetAckTimer()

	closing := false

	//defer log.Println("Exiting client loop")
	opName := stream.Name(src)
	writesNotCompleted := uint(0)
	for {
		upstreamCh := src.In()
		if !src.buf.CanAdd() || closing {
			//disable upstream listening
			upstreamCh = nil
		}
		if closing && src.buf.Len() == 0 {
			sendClose(sndChData, 100)
			return nil
		}
		select {
		case msg, ok := <-upstreamCh:
			if !ok {
				//softClose
				//make sure everything was sent
				closing = true
			} else {
				bytes := msg.([]byte)
				seq, err := src.buf.Add(bytes)
				if err != nil {
					slog.Fatalf("Error adding item to buffer %v", err)
					return err
				}
				sendData(sndChData, bytes, seq)
				writesNotCompleted += 1
				slog.Gm.Event(&opName) // These are bactched
			}
		case cnt := <-writeNotifier.NotificationChannel():
			writesNotCompleted -= cnt
			if timer == nil {
				slog.Logf(logger.Levels.Debug, "Seting timer %v, %v", time.Now(), time.Now().UnixNano())
				timer = src.resetAckTimer()
			}
		case obj, ok := <-rcvChData:
			slog.Logf(logger.Levels.Debug, "in Rcv: %v", ok)
			if !ok {
				return errors.New("Connection to Server was Broken in Recieve Direction")
			}

			command, seq, _, err := parseMsg(obj.([]byte))
			if err != nil {
				slog.Fatalf("%v", err)
			}
			if command == ACK {
				if src.processAck(seq) {
					timer = src.resetAckTimer()
				}
			} else {
				slog.Fatalf("Unknown Command: %v", command)
			}
		case <-rcvChCloseNotifier:
			//connection threw an eof to the reader?
			return errors.New("In Select: Recieve Closed")
		case <-sndChCloseNotifier:
			return errors.New("Connection to Server was Broken in Send Direction")
		case <-timer:
			return errors.New(fmt.Sprintf("Time Out Waiting For Ack, %d %v %v", len(rcvChData), time.Now(), time.Now().UnixNano()))
		case <-src.StopNotifier:
			sender.Stop()
			return nil
		}
	}
}
<commit_msg>Changing transport to not drop but keep retrying, mostly indefinitly<commit_after>package transport

import (
	"errors"
	"fmt"
	"logger"
	"net"
	"stash.cloudflare.com/go-stream/stream"
	"stash.cloudflare.com/go-stream/stream/sink"
	"stash.cloudflare.com/go-stream/stream/source"
	"stash.cloudflare.com/go-stream/util"
	"stash.cloudflare.com/go-stream/util/slog"
	"sync"
	"time"
)

const ACK_TIMEOUT_MS = 10000
const RETRY_MAX = 100

type Client struct {
	*stream.HardStopChannelCloser
	*stream.BaseIn
	addr string
	//id string
	hwm      int
	buf      util.SequentialBuffer
	retries  int
	running  bool
	notifier stream.ProcessedNotifier
}

func DefaultClient(ip string) *Client {
	return NewClient(fmt.Sprintf("%s:4558", ip), DEFAULT_HWM)
}

func NewClient(addr string, hwm int) *Client {
	buf := util.NewSequentialBufferChanImpl(hwm + 1)
	return &Client{stream.NewHardStopChannelCloser(), stream.NewBaseIn(stream.CHAN_SLACK), addr, hwm, buf, 0, false, nil}
}

func (src *Client) SetNotifier(n stream.ProcessedNotifier) *Client {
	if n.Blocking() == true {
		slog.Fatalf("Can't use a blocking Notifier")
	}
	src.notifier = n
	return src
}

func (src *Client) processAck(seq int) (progress bool) {
	//log.Println("Processing ack", seq)
	cnt := src.buf.Ack(seq)
	if cnt > 0 {
		if src.notifier != nil {
			src.notifier.Notify(cnt)
		}
		src.retries = 0
		return true
	}
	return false
}

func (c *Client) ReConnect() error {
	if c.IsRunning() {
		return errors.New("Still Running")
	}
	c.retries = 0
	return c.Run()
}

func (src *Client) Run() error {
	src.running = true
	defer func() {
		src.running = false
	}()

	slog.Gm.Register(stream.Name(src))
	go func(op string, s *Client) { // Update the queue depth on input for each phase
		for {
			slog.Gm.Update(&op, s.GetInDepth())
			time.Sleep(1 * time.Second)
		}
	}(stream.Name(src), src)

	for src.retries < RETRY_MAX {
		err := src.connect()
		if err == nil {
			slog.Logf(logger.Levels.Warn, "Connection failed without error")
			return err
		} else {
			slog.Logf(logger.Levels.Error, "Connection failed with error, retrying: %s", err)
			time.Sleep(1 * time.Second)
		}
	}
	slog.Logf(logger.Levels.Error, "Connection failed retries exceeded. Leftover: %d", src.buf.Len())
	return nil //>>>>>>>>>>>>>>???????????????????????
}

func (src Client) IsRunning() bool {
	return src.running
}

func (src Client) Len() (int, error) {
	if src.IsRunning() {
		return -1, errors.New("Still Running")
	}
	return src.buf.Len(), nil
}

func (src *Client) resetAckTimer() (timer <-chan time.Time) {
	if src.buf.Len() > 0 {
		return time.After(ACK_TIMEOUT_MS * time.Millisecond)
	}
	return nil
}

func (src *Client) connect() error {
	defer func() {
		src.retries++
	}()

	conn, err := net.Dial("tcp", src.addr)
	if err != nil {
		slog.Logf(logger.Levels.Error, "Cannot establish a connection with %s %v", src.addr, err)
		return err
	}

	wg_sub := &sync.WaitGroup{}
	defer wg_sub.Wait()

	rcvChData := make(chan stream.Object, 10)
	receiver := source.NewIOReaderSourceLengthDelim(conn)
	receiver.SetOut(rcvChData)
	rcvChCloseNotifier := make(chan bool)
	wg_sub.Add(1)
	go func() {
		defer wg_sub.Done()
		defer close(rcvChCloseNotifier)
		err := receiver.Run()
		if err != nil {
			slog.Logf(logger.Levels.Error, "Error in client reciever: %v", err)
		}
	}()
	//receiver will be closed by the sender after it is done sending. receiver closed via a hard stop.

	writeNotifier := stream.NewNonBlockingProcessedNotifier(2)
	sndChData := make(chan stream.Object, src.hwm)
	sndChCloseNotifier := make(chan bool)
	defer close(sndChData)
	sender := sink.NewMultiPartWriterSink(conn)
	sender.CompletedNotifier = writeNotifier
	sender.SetIn(sndChData)
	wg_sub.Add(1)
	go func() {
		defer receiver.Stop() //close receiver
		defer wg_sub.Done()
		defer close(sndChCloseNotifier)
		err := sender.Run()
		if err != nil {
			slog.Logf(logger.Levels.Error, "Error in client sender: %v", err)
		}
	}()
	//sender closed by closing the sndChData channel or by a hard stop

	if src.buf.Len() > 0 {
		leftover := src.buf.Reset()
		for i, value := range leftover {
			sendData(sndChData, value, i+1)
		}
	}

	timer := src.resetAckTimer()

	closing := false

	//defer log.Println("Exiting client loop")
	opName := stream.Name(src)
	writesNotCompleted := uint(0)
	for {
		upstreamCh := src.In()
		if !src.buf.CanAdd() || closing {
			//disable upstream listening
			upstreamCh = nil
		}
		if closing && src.buf.Len() == 0 {
			sendClose(sndChData, 100)
			return nil
		}
		select {
		case msg, ok := <-upstreamCh:
			if !ok {
				//softClose
				//make sure everything was sent
				closing = true
			} else {
				bytes := msg.([]byte)
				seq, err := src.buf.Add(bytes)
				if err != nil {
					slog.Fatalf("Error adding item to buffer %v", err)
					return err
				}
				sendData(sndChData, bytes, seq)
				writesNotCompleted += 1
				slog.Gm.Event(&opName) // These are bactched
			}
		case cnt := <-writeNotifier.NotificationChannel():
			writesNotCompleted -= cnt
			if timer == nil {
				slog.Logf(logger.Levels.Debug, "Seting timer %v, %v", time.Now(), time.Now().UnixNano())
				timer = src.resetAckTimer()
			}
		case obj, ok := <-rcvChData:
			slog.Logf(logger.Levels.Debug, "in Rcv: %v", ok)
			if !ok {
				return errors.New("Connection to Server was Broken in Recieve Direction")
			}

			command, seq, _, err := parseMsg(obj.([]byte))
			if err != nil {
				slog.Fatalf("%v", err)
			}
			if command == ACK {
				if src.processAck(seq) {
					timer = src.resetAckTimer()
				}
			} else {
				slog.Fatalf("Unknown Command: %v", command)
			}
		case <-rcvChCloseNotifier:
			//connection threw an eof to the reader?
			return errors.New("In Select: Recieve Closed")
		case <-sndChCloseNotifier:
			return errors.New("Connection to Server was Broken in Send Direction")
		case <-timer:
			return errors.New(fmt.Sprintf("Time Out Waiting For Ack, %d %v %v", len(rcvChData), time.Now(), time.Now().UnixNano()))
		case <-src.StopNotifier:
			sender.Stop()
			return nil
		}
	}
}
<|endoftext|>