<commit_before>package symlink

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
)

// Making a symlink means:
// - making sure the destination exists
// - making sure the source don't exist and the parent folder is created
// - if the source does exist and
//   - is a symlink pointing to a different destination: rmdir
//   - is a symlink pointing to the same
//   - is a folder x, rename it to x.1 (if x.1 exists, x.2, ...)

type test struct {
	src string
	dst string
	err string
	sl  *SL
}

func TestDestination(t *testing.T) {
	t.Skip("Skip TestDestination")
	// only a nil bit will make filepath.Abs() fail:
	// https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L41
	// from UTF16FromString (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L71)
	// from FullPath (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/exec_windows.go#L134)
	// from abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path_windows.go#L109)
	// from Abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path.go#L235)

	osStat = testOsStat
	execRun = testExecRun
	tests := []*test{
		&test{dst: "unknown/dst", err: "The system cannot find the path specified"},
		&test{dst: string([]byte{0}), err: "invalid argument"},
		&test{dst: "err", err: "Test error on os.Stat with non-nil fi"},
		&test{dst: "badsymlink/dir", err: "unreadable dir on symlink"},
		&test{dst: "nojunction/dir", err: "Unable to find junction symlink in parent dir"},
		&test{dst: "cmdRun/dir", err: "The system cannot find the file specified"},
		&test{dst: "WarningOnDir/dir", err: "Warning on run"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(".", test.dst)
		if err == nil || strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if sl != nil {
			t.Errorf("SL '%v', expected <nil>", sl)
		}
	}
	// destination is a symlink
	_, err = New(`.`, `symlink`)
	// destination exists
	_, err = New(`x`, `.`)
	// fmt.Printf("%+v\n", err)
}

func TestSource(t *testing.T) {
	osStat = testOsStat
	execRun = testExecRun
	osMkdirAll = testOsMkdirAll
	osRename = testOsRename

	tests := []*test{
		&test{src: "parentNotYetCreated/newlink"},
		&test{src: "badSrcParent/newlink", err: "Test error badSrcParent on os.Stat with non-nil fi"},
		&test{src: "badSrcParentMdirAll/newlink", err: "Error on mkDirAll for"},
		&test{src: "symlinkdir/newlink", err: ""},
		&test{src: "badsrcparentdir/newlink", err: "Impossible to check/access link parent folder"},
		&test{src: string([]byte{0}), err: "invalid argument"},
		&test{src: "parentnomovesymlinkdir/newlink", err: "Unable to rename "},
		&test{src: "parent/newlinkBadStat", err: "newlinkBadStat cannot be stat"},
		&test{src: "existingparent/existingsymlink", err: ""},
		&test{src: "existingparent/existingsymlinkdiff", err: ""},
		&test{src: "existingparent/existingsymlinkdiffnomove", err: "Unable to rename"},
		&test{src: "parent/failedmklink", err: "Unable to run "},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(test.src, ".")
		if err != nil && strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if err == nil && test.err != "" {
			t.Errorf("Err nil, expected '%s'", test.err)
		}
		if sl == nil && err == nil {
			t.Errorf("SL '%v', expected NOT <nil>", sl)
		}
		fmt.Println("------------------")
	}
}

func testOsStat(name string) (os.FileInfo, error) {
	fmt.Printf("testOsStat name='%+v'\n", name)
	if strings.HasSuffix(name, `prj\symlink\err\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\badsymlink\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for bad symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\nojunction\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\cmdRun\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\WarningOnDir\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for warning on dir")
	}
	if strings.HasSuffix(name, `prj\symlink\badSrcParent\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error badSrcParent on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlinkdir")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnomovesymlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for src parent no move")
	}
	if strings.HasSuffix(name, `prj\symlink\badsrcparentdir\`) {
		return nil, fmt.Errorf("badsrcparentdir cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\newlinkBadStat\`) {
		return nil, fmt.Errorf("newlinkBadStat cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiff\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiff")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiffnomove\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiffnomove")
	}
	if strings.HasSuffix(name, `.1`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	return os.Stat(name)
}

func testOsMkdirAll(path string, perm os.FileMode) error {
	fmt.Printf("testOsMkdirAll path='%+v'\n", path)
	if strings.HasSuffix(path, `badSrcParentMdirAll\`) {
		return fmt.Errorf("Error on mkDirAll for '%s'", path)
	}
	return nil
}

var junctionOut = `
 Répertoire de C:\Users\VonC\prog\git\ggb\deps\src\github.com\VonC

22/06/2015  11:03    <REP>          .
22/06/2015  11:03    <REP>          ..
22/06/2015  11:03    <JONCTION>     symlink [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     symlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     parentnomovesymlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     existingsymlink [C:\Users\VonC\prog\git\ggb\prj\symlink\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiff [C:\Users\VonC\prog\git\ggb\prj\symlink\diff\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiffnomove [C:\Users\VonC\prog\git\ggb\prj\symlink\diff\]
`

func testExecRun(cmd *exec.Cmd) error {
	tmsg := fmt.Sprintf("testExecRun cmd='%v' in '%s'", cmd.Args, cmd.Dir)
	fmt.Println(tmsg)
	if strings.Contains(tmsg, `\failedmklink`) {
		return fmt.Errorf("mklink fails")
	}
	if strings.Contains(tmsg, "/J") {
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\WarningOnDir`) {
		io.WriteString(cmd.Stdout, "dummy content")
		io.WriteString(cmd.Stderr, "Some warning on dir")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\nojunction`) {
		io.WriteString(cmd.Stdout, "dummy content without any junction")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\badsymlink`) {
		return fmt.Errorf("unreadable dir on symlink")
	}

	path := ""
	if strings.Contains(cmd.Dir, `ggb\`) {
		i := strings.Index(cmd.Dir, `ggb\`)
		path = cmd.Dir[:i+len(`ggb\`)]
	}
	jjunctionOut := strings.Replace(junctionOut, `C:\Users\VonC\prog\git\ggb\`, path, -1)

	if strings.HasSuffix(cmd.Dir, `\symlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnomovesymlinkdir`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\existingparent`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	return cmdRun(cmd)
}

func testOsRename(oldpath, newpath string) error {
	fmt.Printf("testOsRename oldpath='%v', newpath '%s'\n", oldpath, newpath)
	if strings.HasSuffix(oldpath, `\parentnomovesymlinkdir`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	if strings.HasSuffix(oldpath, `\existingsymlinkdiffnomove`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	return nil
}
<commit_msg>symlink_test.go: test bad moveToX<commit_after>package symlink

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
)

// Making a symlink means:
// - making sure the destination exists
// - making sure the source don't exist and the parent folder is created
// - if the source does exist and
//   - is a symlink pointing to a different destination: rmdir
//   - is a symlink pointing to the same
//   - is a folder x, rename it to x.1 (if x.1 exists, x.2, ...)

type test struct {
	src string
	dst string
	err string
	sl  *SL
}

func TestDestination(t *testing.T) {
	t.Skip("Skip TestDestination")
	// only a nil bit will make filepath.Abs() fail:
	// https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L41
	// from UTF16FromString (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L71)
	// from FullPath (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/exec_windows.go#L134)
	// from abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path_windows.go#L109)
	// from Abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path.go#L235)

	osStat = testOsStat
	execRun = testExecRun
	tests := []*test{
		&test{dst: "unknown/dst", err: "The system cannot find the path specified"},
		&test{dst: string([]byte{0}), err: "invalid argument"},
		&test{dst: "err", err: "Test error on os.Stat with non-nil fi"},
		&test{dst: "badsymlink/dir", err: "unreadable dir on symlink"},
		&test{dst: "nojunction/dir", err: "Unable to find junction symlink in parent dir"},
		&test{dst: "cmdRun/dir", err: "The system cannot find the file specified"},
		&test{dst: "WarningOnDir/dir", err: "Warning on run"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(".", test.dst)
		if err == nil || strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if sl != nil {
			t.Errorf("SL '%v', expected <nil>", sl)
		}
	}
	// destination is a symlink
	_, err = New(`.`, `symlink`)
	// destination exists
	_, err = New(`x`, `.`)
	// fmt.Printf("%+v\n", err)
}

func TestSource(t *testing.T) {
	osStat = testOsStat
	execRun = testExecRun
	osMkdirAll = testOsMkdirAll
	osRename = testOsRename

	tests := []*test{
		&test{src: "parentNotYetCreated/newlink"},
		&test{src: "badSrcParent/newlink", err: "Test error badSrcParent on os.Stat with non-nil fi"},
		&test{src: "badSrcParentMdirAll/newlink", err: "Error on mkDirAll for"},
		&test{src: "symlinkdir/newlink", err: ""},
		&test{src: "badsrcparentdir/newlink", err: "Impossible to check/access link parent folder"},
		&test{src: string([]byte{0}), err: "invalid argument"},
		&test{src: "parentnomovesymlinkdir/newlink", err: "Unable to rename "},
		&test{src: "parent/newlinkBadStat", err: "newlinkBadStat cannot be stat"},
		&test{src: "existingparent/existingsymlink", err: ""},
		&test{src: "existingparent/existingsymlinkdiff", err: ""},
		&test{src: "existingparent/existingsymlinkdiffnomove", err: "Unable to rename"},
		&test{src: "parent/failedmklink", err: "Unable to run "},
		&test{src: "parent/existingsymlinkbadstat", err: "existingsymlinkbadstat.1 cannot be stat'd"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(test.src, ".")
		if err != nil && strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if err == nil && test.err != "" {
			t.Errorf("Err nil, expected '%s'", test.err)
		}
		if sl == nil && err == nil {
			t.Errorf("SL '%v', expected NOT <nil>", sl)
		}
		fmt.Println("------------------")
	}
}

func testOsStat(name string) (os.FileInfo, error) {
	fmt.Printf("testOsStat name='%+v'\n", name)
	if strings.HasSuffix(name, `prj\symlink\err\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\badsymlink\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for bad symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\nojunction\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\cmdRun\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\WarningOnDir\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for warning on dir")
	}
	if strings.HasSuffix(name, `prj\symlink\badSrcParent\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error badSrcParent on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlinkdir")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnomovesymlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for src parent no move")
	}
	if strings.HasSuffix(name, `prj\symlink\badsrcparentdir\`) {
		return nil, fmt.Errorf("badsrcparentdir cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\newlinkBadStat\`) {
		return nil, fmt.Errorf("newlinkBadStat cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat.1`) {
		return nil, fmt.Errorf("existingsymlinkbadstat.1 cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiff\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiff")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiffnomove\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiffnomove")
	}
	if strings.HasSuffix(name, `.1`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	return os.Stat(name)
}

func testOsMkdirAll(path string, perm os.FileMode) error {
	fmt.Printf("testOsMkdirAll path='%+v'\n", path)
	if strings.HasSuffix(path, `badSrcParentMdirAll\`) {
		return fmt.Errorf("Error on mkDirAll for '%s'", path)
	}
	return nil
}

var junctionOut = `
 Répertoire de C:\Users\VonC\prog\git\ggb\deps\src\github.com\VonC

22/06/2015  11:03    <REP>          .
22/06/2015  11:03    <REP>          ..
22/06/2015  11:03    <JONCTION>     symlink [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     symlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     parentnomovesymlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     existingsymlink [C:\Users\VonC\prog\git\ggb\prj\symlink\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiff [C:\Users\VonC\prog\git\ggb\prj\symlink\diff\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiffnomove [C:\Users\VonC\prog\git\ggb\prj\symlink\diff\]
`

func testExecRun(cmd *exec.Cmd) error {
	tmsg := fmt.Sprintf("testExecRun cmd='%v' in '%s'", cmd.Args, cmd.Dir)
	fmt.Println(tmsg)
	if strings.Contains(tmsg, `\failedmklink`) {
		return fmt.Errorf("mklink fails")
	}
	if strings.Contains(tmsg, "/J") {
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\WarningOnDir`) {
		io.WriteString(cmd.Stdout, "dummy content")
		io.WriteString(cmd.Stderr, "Some warning on dir")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\nojunction`) {
		io.WriteString(cmd.Stdout, "dummy content without any junction")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\badsymlink`) {
		return fmt.Errorf("unreadable dir on symlink")
	}

	path := ""
	if strings.Contains(cmd.Dir, `ggb\`) {
		i := strings.Index(cmd.Dir, `ggb\`)
		path = cmd.Dir[:i+len(`ggb\`)]
	}
	jjunctionOut := strings.Replace(junctionOut, `C:\Users\VonC\prog\git\ggb\`, path, -1)

	if strings.HasSuffix(cmd.Dir, `\symlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnomovesymlinkdir`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\existingparent`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	return cmdRun(cmd)
}

func testOsRename(oldpath, newpath string) error {
	fmt.Printf("testOsRename oldpath='%v', newpath '%s'\n", oldpath, newpath)
	if strings.HasSuffix(oldpath, `\parentnomovesymlinkdir`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	if strings.HasSuffix(oldpath, `\existingsymlinkdiffnomove`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/googlecloudplatform/gcsfuse/fs"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"golang.org/x/net/context"
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s [flags] <mount-point>\n", os.Args[0])
	flag.PrintDefaults()
}

var fBucketName = flag.String("bucket", "", "Name of GCS bucket to mount.")

var fTempDir = flag.String(
	"temp_dir", "",
	"The temporary directory in which to store local copies of GCS objects. "+
		"If empty, the system default (probably /tmp) will be used.")

var fTempDirLimit = flag.Int64(
	"temp_dir_bytes", 1<<31,
	"A desired limit on the number of bytes used in --temp_dir. May be exceeded "+
		"for dirty files that have not been flushed or closed.")

var fGCSChunkSize = flag.Uint64(
	"gcs_chunk_size", 1<<24,
	"If set to a non-zero value N, split up GCS objects into multiple chunks of "+
		"size at most N when reading, and do not read or cache unnecessary chunks.")

var fImplicitDirs = flag.Bool(
	"implicit_dirs",
	false,
	"Implicitly define directories based on their content. See docs/semantics.md.")

var fSupportNlink = flag.Bool(
	"support_nlink",
	false,
	"Return meaningful values for nlink from fstat(2). See docs/semantics.md.")

var fStatCacheTTL = flag.String(
	"stat_cache_ttl",
	"",
	"If non-empty, a duration specifying how long to cache StatObject results "+
		"from GCS, e.g. \"2s\" or \"15ms\". See docs/semantics.md for more.")

var fTypeCacheTTL = flag.String(
	"type_cache_ttl",
	"",
	"If non-empty, a duration specifying how long to cache name -> file/dir type "+
		"mappings in directory inodes, e.g. \"2s\" or \"15ms\". "+
		"See docs/semantics.md.")

func getBucketName() string {
	s := *fBucketName
	if s == "" {
		fmt.Println("You must set --bucket.")
		os.Exit(1)
	}

	return s
}

func registerSIGINTHandler(mountPoint string) {
	// Register for SIGINT.
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)

	// Start a goroutine that will unmount when the signal is received.
	go func() {
		for {
			<-signalChan
			log.Println("Received SIGINT, attempting to unmount...")

			err := fuse.Unmount(mountPoint)
			if err != nil {
				log.Printf("Failed to unmount in response to SIGINT: %v", err)
			} else {
				log.Printf("Successfully unmounted in response to SIGINT.")
				return
			}
		}
	}()
}

func getBucket() (b gcs.Bucket) {
	// Set up a GCS connection.
	log.Println("Initializing GCS connection.")
	conn, err := getConn()
	if err != nil {
		log.Fatal("Couldn't get GCS connection: ", err)
	}

	// Extract the appropriate bucket.
	b = conn.GetBucket(getBucketName())

	// Enable cached StatObject results, if appropriate.
	if *fStatCacheTTL != "" {
		ttl, err := time.ParseDuration(*fStatCacheTTL)
		if err != nil {
			log.Fatalf("Invalid --stat_cache_ttl: %v", err)
			return
		}

		const cacheCapacity = 4096
		b = gcscaching.NewFastStatBucket(
			ttl,
			gcscaching.NewStatCache(cacheCapacity),
			timeutil.RealClock(),
			b)
	}

	return
}

func main() {
	// Make logging output better.
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)

	// Set up flags.
	flag.Usage = usage
	flag.Parse()

	// Grab the mount point.
	if flag.NArg() != 1 {
		usage()
		os.Exit(1)
	}

	mountPoint := flag.Arg(0)

	// Parse --type_cache_ttl
	var typeCacheTTL time.Duration
	if *fTypeCacheTTL != "" {
		var err error
		typeCacheTTL, err = time.ParseDuration(*fTypeCacheTTL)
		if err != nil {
			log.Fatalf("Invalid --type_cache_ttl: %v", err)
			return
		}
	}

	// Create a file system server.
	serverCfg := &fs.ServerConfig{
		Clock:               timeutil.RealClock(),
		Bucket:              getBucket(),
		TempDir:             *fTempDir,
		TempDirLimit:        *fTempDirLimit,
		GCSChunkSize:        *fGCSChunkSize,
		ImplicitDirectories: *fImplicitDirs,
		SupportNlink:        *fSupportNlink,
		DirTypeCacheTTL:     typeCacheTTL,
	}

	server, err := fs.NewServer(serverCfg)
	if err != nil {
		log.Fatal("fs.NewServer:", err)
	}

	// Mount the file system.
	mountedFS, err := fuse.Mount(mountPoint, server, &fuse.MountConfig{})
	if err != nil {
		log.Fatal("Mount:", err)
	}

	log.Println("File system has been successfully mounted.")

	// Let the user unmount with Ctrl-C (SIGINT).
	registerSIGINTHandler(mountedFS.Dir())

	// Wait for it to be unmounted.
	if err := mountedFS.Join(context.Background()); err != nil {
		log.Fatal("MountedFileSystem.Join:", err)
	}

	log.Println("Successfully exiting.")
}
<commit_msg>Set flag defaults.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/signal"
	"time"

	"github.com/googlecloudplatform/gcsfuse/fs"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/fuse"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"golang.org/x/net/context"
)

func usage() {
	fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "  %s [flags] <mount-point>\n", os.Args[0])
	flag.PrintDefaults()
}

var fBucketName = flag.String("bucket", "", "Name of GCS bucket to mount.")

var fTempDir = flag.String(
	"temp_dir", "",
	"The temporary directory in which to store local copies of GCS objects. "+
		"If empty, the system default (probably /tmp) will be used.")

var fTempDirLimit = flag.Int64(
	"temp_dir_bytes", 1<<31,
	"A desired limit on the number of bytes used in --temp_dir. May be exceeded "+
		"for dirty files that have not been flushed or closed.")

var fGCSChunkSize = flag.Uint64(
	"gcs_chunk_size", 1<<24,
	"If set to a non-zero value N, split up GCS objects into multiple chunks of "+
		"size at most N when reading, and do not read or cache unnecessary chunks.")

var fImplicitDirs = flag.Bool(
	"implicit_dirs",
	false,
	"Implicitly define directories based on their content. See docs/semantics.md.")

var fSupportNlink = flag.Bool(
	"support_nlink",
	false,
	"Return meaningful values for nlink from fstat(2). See docs/semantics.md.")

var fStatCacheTTL = flag.String(
	"stat_cache_ttl",
	"1m",
	"If non-empty, a duration specifying how long to cache StatObject results "+
		"from GCS, e.g. \"2s\" or \"15ms\". See docs/semantics.md for more.")

var fTypeCacheTTL = flag.String(
	"type_cache_ttl",
	"1m",
	"If non-empty, a duration specifying how long to cache name -> file/dir type "+
		"mappings in directory inodes, e.g. \"2s\" or \"15ms\". "+
		"See docs/semantics.md.")

func getBucketName() string {
	s := *fBucketName
	if s == "" {
		fmt.Println("You must set --bucket.")
		os.Exit(1)
	}

	return s
}

func registerSIGINTHandler(mountPoint string) {
	// Register for SIGINT.
	signalChan := make(chan os.Signal, 1)
	signal.Notify(signalChan, os.Interrupt)

	// Start a goroutine that will unmount when the signal is received.
	go func() {
		for {
			<-signalChan
			log.Println("Received SIGINT, attempting to unmount...")

			err := fuse.Unmount(mountPoint)
			if err != nil {
				log.Printf("Failed to unmount in response to SIGINT: %v", err)
			} else {
				log.Printf("Successfully unmounted in response to SIGINT.")
				return
			}
		}
	}()
}

func getBucket() (b gcs.Bucket) {
	// Set up a GCS connection.
	log.Println("Initializing GCS connection.")
	conn, err := getConn()
	if err != nil {
		log.Fatal("Couldn't get GCS connection: ", err)
	}

	// Extract the appropriate bucket.
	b = conn.GetBucket(getBucketName())

	// Enable cached StatObject results, if appropriate.
	if *fStatCacheTTL != "" {
		ttl, err := time.ParseDuration(*fStatCacheTTL)
		if err != nil {
			log.Fatalf("Invalid --stat_cache_ttl: %v", err)
			return
		}

		const cacheCapacity = 4096
		b = gcscaching.NewFastStatBucket(
			ttl,
			gcscaching.NewStatCache(cacheCapacity),
			timeutil.RealClock(),
			b)
	}

	return
}

func main() {
	// Make logging output better.
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds)

	// Set up flags.
	flag.Usage = usage
	flag.Parse()

	// Grab the mount point.
	if flag.NArg() != 1 {
		usage()
		os.Exit(1)
	}

	mountPoint := flag.Arg(0)

	// Parse --type_cache_ttl
	var typeCacheTTL time.Duration
	if *fTypeCacheTTL != "" {
		var err error
		typeCacheTTL, err = time.ParseDuration(*fTypeCacheTTL)
		if err != nil {
			log.Fatalf("Invalid --type_cache_ttl: %v", err)
			return
		}
	}

	// Create a file system server.
	serverCfg := &fs.ServerConfig{
		Clock:               timeutil.RealClock(),
		Bucket:              getBucket(),
		TempDir:             *fTempDir,
		TempDirLimit:        *fTempDirLimit,
		GCSChunkSize:        *fGCSChunkSize,
		ImplicitDirectories: *fImplicitDirs,
		SupportNlink:        *fSupportNlink,
		DirTypeCacheTTL:     typeCacheTTL,
	}

	server, err := fs.NewServer(serverCfg)
	if err != nil {
		log.Fatal("fs.NewServer:", err)
	}

	// Mount the file system.
	mountedFS, err := fuse.Mount(mountPoint, server, &fuse.MountConfig{})
	if err != nil {
		log.Fatal("Mount:", err)
	}

	log.Println("File system has been successfully mounted.")

	// Let the user unmount with Ctrl-C (SIGINT).
	registerSIGINTHandler(mountedFS.Dir())

	// Wait for it to be unmounted.
	if err := mountedFS.Join(context.Background()); err != nil {
		log.Fatal("MountedFileSystem.Join:", err)
	}

	log.Println("Successfully exiting.")
}
<|endoftext|>


<commit_before>package gorm

import (
	"fmt"
	"reflect"
	"strings"
	"time"
)

type mysql struct{}

func (s *mysql) BinVar(i int) string {
	return "$$" // ?
}

func (s *mysql) SupportLastInsertId() bool {
	return true
}

func (s *mysql) HasTop() bool {
	return false
}

func (s *mysql) SqlTag(value reflect.Value, size int) string {
	switch value.Kind() {
	case reflect.Bool:
		return "boolean"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
		return "int"
	case reflect.Int64, reflect.Uint64:
		return "bigint"
	case reflect.Float32, reflect.Float64:
		return "double"
	case reflect.String:
		if size > 0 && size < 65532 {
			return fmt.Sprintf("varchar(%d)", size)
		}
		return "longtext"
	case reflect.Struct:
		if _, ok := value.Interface().(time.Time); ok {
			return "timestamp NULL"
		}
	case reflect.Array:
		if value.Type().Elem().Kind() == reflect.Uint8 {
			return fmt.Sprintf("binary(%d)", value.Len())
		}
	default:
		if _, ok := value.Interface().([]byte); ok {
			if size > 0 && size < 65532 {
				return fmt.Sprintf("varbinary(%d)", size)
			}
			return "longblob"
		}
	}
	panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", value.Type().Name(), value.Kind().String()))
}

func (s *mysql) PrimaryKeyTag(value reflect.Value, size int) string {
	suffix := " NOT NULL AUTO_INCREMENT PRIMARY KEY"
	switch value.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
		return "int" + suffix
	case reflect.Int64, reflect.Uint64:
		return "bigint" + suffix
	case reflect.Array:
		if value.Type().Elem().Kind() == reflect.Uint8 {
			return fmt.Sprintf("binary(%d) NOT NULL PRIMARY KEY", value.Len())
		}
		fallthrough
	default:
		panic("Invalid primary key type")
	}
}

func (s *mysql) ReturningStr(tableName, key string) string {
	return ""
}

func (s *mysql) SelectFromDummyTable() string {
	return "FROM DUAL"
}

func (s *mysql) Quote(key string) string {
	return fmt.Sprintf("`%s`", key)
}

func (s *mysql) databaseName(scope *Scope) string {
	from := strings.Index(scope.db.parent.source, "/") + 1
	to := strings.Index(scope.db.parent.source, "?")
	if to == -1 {
		to = len(scope.db.parent.source)
	}
	return scope.db.parent.source[from:to]
}

func (s *mysql) HasTable(scope *Scope, tableName string) bool {
	var count int
	newScope := scope.New(nil)
	newScope.Raw(fmt.Sprintf("SELECT count(*) FROM INFORMATION_SCHEMA.tables where table_name = %v AND table_schema = %v",
		newScope.AddToVars(tableName),
		newScope.AddToVars(s.databaseName(scope))))
	newScope.SqlDB().QueryRow(newScope.Sql, newScope.SqlVars...).Scan(&count)
	return count > 0
}

func (s *mysql) HasColumn(scope *Scope, tableName string, columnName string) bool {
	var count int
	newScope := scope.New(nil)
	newScope.Raw(fmt.Sprintf("SELECT count(*) FROM information_schema.columns WHERE table_schema = %v AND table_name = %v AND column_name = %v",
		newScope.AddToVars(s.databaseName(scope)),
		newScope.AddToVars(tableName),
		newScope.AddToVars(columnName),
	))
	newScope.SqlDB().QueryRow(newScope.Sql, newScope.SqlVars...).Scan(&count)
	return count > 0
}

func (s *mysql) RemoveIndex(scope *Scope, indexName string) {
	scope.Raw(fmt.Sprintf("DROP INDEX %v ON %v", indexName, scope.QuotedTableName())).Exec()
}
<commit_msg>use Datetime(3) as default database type for time.Time<commit_after>package gorm

import (
	"fmt"
	"reflect"
	"strings"
	"time"
)

type mysql struct{}

func (s *mysql) BinVar(i int) string {
	return "$$" // ?
}

func (s *mysql) SupportLastInsertId() bool {
	return true
}

func (s *mysql) HasTop() bool {
	return false
}

func (s *mysql) SqlTag(value reflect.Value, size int) string {
	switch value.Kind() {
	case reflect.Bool:
		return "boolean"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
		return "int"
	case reflect.Int64, reflect.Uint64:
		return "bigint"
	case reflect.Float32, reflect.Float64:
		return "double"
	case reflect.String:
		if size > 0 && size < 65532 {
			return fmt.Sprintf("varchar(%d)", size)
		}
		return "longtext"
	case reflect.Struct:
		if _, ok := value.Interface().(time.Time); ok {
			return "datetime(3) NULL"
		}
	case reflect.Array:
		if value.Type().Elem().Kind() == reflect.Uint8 {
			return fmt.Sprintf("binary(%d)", value.Len())
		}
	default:
		if _, ok := value.Interface().([]byte); ok {
			if size > 0 && size < 65532 {
				return fmt.Sprintf("varbinary(%d)", size)
			}
			return "longblob"
		}
	}
	panic(fmt.Sprintf("invalid sql type %s (%s) for mysql", value.Type().Name(), value.Kind().String()))
}

func (s *mysql) PrimaryKeyTag(value reflect.Value, size int) string {
	suffix := " NOT NULL AUTO_INCREMENT PRIMARY KEY"
	switch value.Kind() {
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uintptr:
		return "int" + suffix
	case reflect.Int64, reflect.Uint64:
		return "bigint" + suffix
	case reflect.Array:
		if value.Type().Elem().Kind() == reflect.Uint8 {
			return fmt.Sprintf("binary(%d) NOT NULL PRIMARY KEY", value.Len())
		}
		fallthrough
	default:
		panic("Invalid primary key type")
	}
}

func (s *mysql) ReturningStr(tableName, key string) string {
	return ""
}

func (s *mysql) SelectFromDummyTable() string {
	return "FROM DUAL"
}

func (s *mysql) Quote(key string) string {
	return fmt.Sprintf("`%s`", key)
}

func (s *mysql) databaseName(scope *Scope) string {
	from := strings.Index(scope.db.parent.source, "/") + 1
	to := strings.Index(scope.db.parent.source, "?")
	if to == -1 {
		to = len(scope.db.parent.source)
	}
	return scope.db.parent.source[from:to]
}

func (s *mysql) HasTable(scope *Scope, tableName string) bool {
	var count int
	newScope := scope.New(nil)
	newScope.Raw(fmt.Sprintf("SELECT count(*) FROM INFORMATION_SCHEMA.tables where table_name = %v AND table_schema = %v",
		newScope.AddToVars(tableName),
		newScope.AddToVars(s.databaseName(scope))))
	newScope.SqlDB().QueryRow(newScope.Sql, newScope.SqlVars...).Scan(&count)
	return count > 0
}

func (s *mysql) HasColumn(scope *Scope, tableName string, columnName string) bool {
	var count int
	newScope := scope.New(nil)
	newScope.Raw(fmt.Sprintf("SELECT count(*) FROM information_schema.columns WHERE table_schema = %v AND table_name = %v AND column_name = %v",
		newScope.AddToVars(s.databaseName(scope)),
		newScope.AddToVars(tableName),
		newScope.AddToVars(columnName),
	))
	newScope.SqlDB().QueryRow(newScope.Sql, newScope.SqlVars...).Scan(&count)
	return count > 0
}

func (s *mysql) RemoveIndex(scope *Scope, indexName string) {
	scope.Raw(fmt.Sprintf("DROP INDEX %v ON %v", indexName, scope.QuotedTableName())).Exec()
}
<|endoftext|>


<commit_before>//go:generate bitfanDoc
package inputsql

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/ShowMax/go-fqdn"
	_ "github.com/go-sql-driver/mysql"
	"github.com/vjeantet/bitfan/core/location"
	"github.com/vjeantet/bitfan/processors"
)

func New() processors.Processor {
	return &processor{opt: &options{}}
}

type options struct {
	// If this filter is successful, add any arbitrary fields to this event.
	Add_field map[string]interface{}

	// If this filter is successful, add arbitrary tags to the event. Tags can be dynamic
	// and include parts of the event using the %{field} syntax.
	Tags []string

	// Add a type field to all events handled by this input
	Type string

	// The codec used for input data. Input codecs are a convenient method for decoding
	// your data before it enters the input, without needing a separate filter in your bitfan pipeline
	Codec string

	// GOLANG driver class to load, for example, "mysql".
	// @ExampleLS driver => "mysql"
	Driver string `mapstructure:"driver" validate:"required"`

	// Send an event row by row or one event with all results
	// possible values "row", "result"
	// @Default "row"
	EventBy string `mapstructure:"event_by"`

	// SQL Statement
	// When there is more than 1 statement, only data from the last one will generate events.
	// @ExampleLS statement => "SELECT * FROM mytable"
	Statement string `mapstructure:"statement" validate:"required"`

	// Set an interval when this processor is used as a input
	// @ExampleLS interval => "10"
	Interval string `mapstructure:"interval"  validate:"required"`

	// @ExampleLS connection_string => "username:password@tcp(192.168.1.2:3306)/mydatabase?charset=utf8"
	ConnectionString string `mapstructure:"connection_string" validate:"required"`

	// You can set variable to be used in Statements by using ${var}.
	// each reference will be replaced by the value of the variable found in Statement's content
	// The replacement is case-sensitive.
	// @ExampleLS var => {"hostname"=>"myhost","varname"=>"varvalue"}
	Var map[string]string `mapstructure:"var"`

	// Define the target field for placing the retrieved data. If this setting is omitted,
	// the data will be stored in the "data" field
	// Set the value to "." to store value to the root (top level) of the event
	// @ExampleLS target => "data"
	// @Default "data"
	Target string `mapstructure:"target"`
}

type processor struct {
	processors.Base
	db   *sql.DB
	opt  *options
	q    chan bool
	host string
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	defaults := options{
		EventBy: "row",
		Target:  "data",
	}

	p.opt = &defaults
	p.host = fqdn.Get()

	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	if p.opt.Interval == "" {
		p.Logger.Warningln("No interval set")
	}

	loc, err := location.NewLocation(p.opt.Statement, p.ConfigWorkingLocation)
	if err != nil {
		return err
	}

	content, _, err := loc.ContentWithOptions(p.opt.Var)
	if err != nil {
		return err
	}
	p.opt.Statement = string(content)

	p.db, err = sql.Open(p.opt.Driver, p.opt.ConnectionString)
	if err != nil {
		return err
	}

	err = p.db.Ping()
	if err != nil {
		return err
	}

	return nil
}

func (p *processor) Tick(e processors.IPacket) error {
	return p.Receive(e)
}

func (p *processor) Receive(e processors.IPacket) error {
	p.opt.Statement = strings.Trim(p.opt.Statement, ";")
	reqs := strings.Split(p.opt.Statement, ";")
	for _, r := range reqs[:len(reqs)-1] {
		p.Logger.Debugf("db.Exec - %s", r)
		p.db.Exec(r)
	}

	p.Logger.Debugf("db.Query - %s", reqs[len(reqs)-1])
	rows, err := p.db.Query(reqs[len(reqs)-1])
	if err != nil {
		return err
	}

	columns, err := rows.Columns()
	if err != nil {
		return err
	}

	scanArgs := make([]interface{}, len(columns))
	values := make([]interface{}, len(columns))
	for i := range values {
		scanArgs[i] = &values[i]
	}
	var records []map[string]interface{}
	for rows.Next() {
		record := make(map[string]interface{})
		err = rows.Scan(scanArgs...)
		if err != nil {
			return err
		}

		for i, col := range values {
			if col != nil {
				// fmt.Printf("\n%s: type= %s\n", columns[i], reflect.TypeOf(col))
				switch t := col.(type) {
				default:
					fmt.Printf("Unexpected type %T\n", t)
				case bool:
					record[columns[i]] = col.(bool)
				case int:
					record[columns[i]] = col.(int)
				case int64:
					record[columns[i]] = col.(int64)
				case float64:
					record[columns[i]] = col.(float64)
				case string:
					record[columns[i]] = col.(string)
				case []byte: // -- all cases go HERE!
					record[columns[i]] = string(col.([]byte))
					//case time.Time:
					// record[columns[i]] = col.(string)
				}
			}
		}

		if p.opt.EventBy == "row" {
			var e processors.IPacket
			e = p.NewPacket("", map[string]interface{}{})
			e.Fields().SetValueForPath(p.host, "host")
			if len(p.opt.Var) > 0 {
				e.Fields().SetValueForPath(p.opt.Var, "var")
			}

			if p.opt.Target == "." {
				for k, v := range record {
					e.Fields().SetValueForPath(v, k)
				}
			} else {
				e.Fields().SetValueForPath(record, p.opt.Target)
			}

			processors.ProcessCommonFields(e.Fields(), p.opt.Add_field, p.opt.Tags, p.opt.Type)
			p.Send(e)
		} else {
			records = append(records, record)
		}
	}

	rows.Close()

	if p.opt.EventBy != "row" {
		e.Fields().SetValueForPath(p.host, "host")
		if len(p.opt.Var) > 0 {
			e.Fields().SetValueForPath(p.opt.Var, "var")
		}
		e.Fields().SetValueForPath(records, p.opt.Target)

		processors.ProcessCommonFields(e.Fields(), p.opt.Add_field, p.opt.Tags, p.opt.Type)
		p.Send(e)
	}

	return nil
}

func (p *processor) Stop(e processors.IPacket) error {
	p.db.Close()
	return nil
}
<commit_msg>processor sql : prepare processor to be used as input, filter and output !<commit_after>//go:generate bitfanDoc
package inputsql

import (
	"database/sql"
	"fmt"
	"strings"

	"github.com/ShowMax/go-fqdn"
	_ "github.com/go-sql-driver/mysql"
	"github.com/vjeantet/bitfan/core/location"
	"github.com/vjeantet/bitfan/processors"
)

func New() processors.Processor {
	return &processor{opt: &options{}}
}

type options struct {
	// If this filter is successful, add any arbitrary fields to this event.
	Add_field map[string]interface{}

	// If this filter is successful, add arbitrary tags to the event. Tags can be dynamic
	// and include parts of the event using the %{field} syntax.
	Tags []string

	// Add a type field to all events handled by this input
	Type string

	// The codec used for input data. Input codecs are a convenient method for decoding
	// your data before it enters the input, without needing a separate filter in your bitfan pipeline
	Codec string

	// GOLANG driver class to load, for example, "mysql".
	// @ExampleLS driver => "mysql"
	Driver string `mapstructure:"driver" validate:"required"`

	// Send an event row by row or one event with all results
	// possible values "row", "result"
	// @Default "row"
	EventBy string `mapstructure:"event_by"`

	// SQL Statement
	// When there is more than 1 statement, only data from the last one will generate events.
	// @ExampleLS statement => "SELECT * FROM mytable"
	Statement string `mapstructure:"statement" validate:"required"`

	// Set an interval when this processor is used as a input
	// @ExampleLS interval => "10"
	Interval string `mapstructure:"interval"  validate:"required"`

	// @ExampleLS connection_string => "username:password@tcp(192.168.1.2:3306)/mydatabase?charset=utf8"
	ConnectionString string `mapstructure:"connection_string" validate:"required"`

	// You can set variable to be used in Statements by using ${var}.
	// each reference will be replaced by the value of the variable found in Statement's content
	// The replacement is case-sensitive.
	// @ExampleLS var => {"hostname"=>"myhost","varname"=>"varvalue"}
	Var map[string]string `mapstructure:"var"`

	// Define the target field for placing the retrieved data. If this setting is omitted,
	// the data will be stored in the "data" field
	// Set the value to "." to store value to the root (top level) of the event
	// @ExampleLS target => "data"
	// @Default "data"
	Target string `mapstructure:"target"`
}

type processor struct {
	processors.Base
	db   *sql.DB
	opt  *options
	q    chan bool
	host string
}

func (p *processor) Configure(ctx processors.ProcessorContext, conf map[string]interface{}) error {
	defaults := options{
		EventBy: "row",
		Target:  "data",
	}

	p.opt = &defaults
	p.host = fqdn.Get()

	err := p.ConfigureAndValidate(ctx, conf, p.opt)
	if err != nil {
		return err
	}

	if p.opt.Interval == "" {
		p.Logger.Warningln("No interval set")
	}

	loc, err := location.NewLocation(p.opt.Statement, p.ConfigWorkingLocation)
	if err != nil {
		return err
	}

	content, _, err := loc.ContentWithOptions(p.opt.Var)
	if err != nil {
		return err
	}
	p.opt.Statement = string(content)

	p.db, err = sql.Open(p.opt.Driver, p.opt.ConnectionString)
	if err != nil {
		return err
	}

	err = p.db.Ping()
	if err != nil {
		return err
	}

	return nil
}

func (p *processor) Tick(e processors.IPacket) error {
	return p.Receive(e)
}

func (p *processor) Receive(e processors.IPacket) error {
	p.opt.Statement = strings.Trim(p.opt.Statement, ";")
	reqs := strings.Split(p.opt.Statement, ";")
	for _, r := range reqs[:len(reqs)-1] {
		p.Logger.Debugf("db.Exec - %s", r)
		p.db.Exec(r)
	}

	p.Logger.Debugf("db.Query - %s", reqs[len(reqs)-1])
	rows, err := p.db.Query(reqs[len(reqs)-1])
	if err != nil {
		return err
	}

	columns, err := rows.Columns()
	if err != nil {
		return err
	}

	scanArgs := make([]interface{}, len(columns))
	values := make([]interface{}, len(columns))
	for i := range values {
		scanArgs[i] = &values[i]
	}
	var records []map[string]interface{}
	for rows.Next() {
		record := make(map[string]interface{})
		err = rows.Scan(scanArgs...)
		if err != nil {
			return err
		}

		for i, col := range values {
			if col != nil {
				// fmt.Printf("\n%s: type= %s\n", columns[i], reflect.TypeOf(col))
				switch t := col.(type) {
				default:
					fmt.Printf("Unexpected type %T\n", t)
				case bool:
					record[columns[i]] = col.(bool)
				case int:
					record[columns[i]] = col.(int)
				case int64:
					record[columns[i]] = col.(int64)
				case float64:
					record[columns[i]] = col.(float64)
				case string:
					record[columns[i]] = col.(string)
				case []byte: // -- all cases go HERE!
					record[columns[i]] = string(col.([]byte))
					//case time.Time:
					// record[columns[i]] = col.(string)
				}
			}
		}

		if p.opt.EventBy == "row" {
			var e2 processors.IPacket
			e2 = e.Clone()
			e2.Fields().SetValueForPath(p.host, "host")
			if len(p.opt.Var) > 0 {
				e2.Fields().SetValueForPath(p.opt.Var, "var")
			}

			if p.opt.Target == "." {
				for k, v := range record {
					e2.Fields().SetValueForPath(v, k)
				}
			} else {
				e2.Fields().SetValueForPath(record, p.opt.Target)
			}

			processors.ProcessCommonFields(e2.Fields(), p.opt.Add_field, p.opt.Tags, p.opt.Type)
			p.Send(e2)
		} else {
			records = append(records, record)
		}
	}

	rows.Close()

	if p.opt.EventBy != "row" {
		e.Fields().SetValueForPath(p.host, "host")
		if len(p.opt.Var) > 0 {
			e.Fields().SetValueForPath(p.opt.Var, "var")
		}
		e.Fields().SetValueForPath(records, p.opt.Target)

		processors.ProcessCommonFields(e.Fields(), p.opt.Add_field, p.opt.Tags, p.opt.Type)
		p.Send(e)
	}

	return nil
}

func (p *processor) Stop(e processors.IPacket) error {
	p.db.Close()
	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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 mungers

import (
	"path"
	"strings"

	"k8s.io/contrib/mungegithub/features"
	"k8s.io/contrib/mungegithub/github"
	"k8s.io/contrib/mungegithub/mungers/mungerutil"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
)

const maxDepth = 3

// ApprovalHandler will try to add "approved" label once
// all files of change has been approved by approvers.
type ApprovalHandler struct {
	features *features.Features
}

func init() {
	h := &ApprovalHandler{}
	RegisterMungerOrDie(h)
}

// Name is the name usable in --pr-mungers
func (*ApprovalHandler) Name() string { return "approval-handler" }

// RequiredFeatures is a slice of 'features' that must be provided
func (*ApprovalHandler) RequiredFeatures() []string {
	return []string{features.RepoFeatureName, features.AliasesFeature}
}

// Initialize will initialize the munger
func (h *ApprovalHandler) Initialize(config *github.Config, features *features.Features) error {
	h.features = features
	return nil
}

// EachLoop is called at the start of every munge loop
func (*ApprovalHandler) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (*ApprovalHandler) AddFlags(cmd *cobra.Command, config *github.Config) {}

// Munge is the workhorse the will actually make updates to the PR
// The algorithm goes as:
// - Initially, we set up approverSet
//   - Go through all comments after latest commit. If any approver said "/approve", add him to approverSet.
// - For each file, we see if any approver of this file is in approverSet.
//   - An approver of a file is defined as:
//     - It's known that each dir has a list of approvers. (This might not hold true. For usability, current situation is enough.)
//     - Approver of a dir is also the approver of child dirs.
//   - We look at top N (default 3) level dir approvers. For example, for file "/a/b/c/d/e", we might search for approver from
//     "/", "/a/", "/a/b/"
// - Iff all files has been approved, the bot will add "approved" label.
func (h *ApprovalHandler) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}
	files, err := obj.ListFiles()
	if err != nil {
		glog.Errorf("failed to list files in this PR: %v", err)
		return
	}

	comments, err := getCommentsAfterLastModified(obj)
	if err != nil {
		glog.Errorf("failed to get comments in this PR: %v", err)
		return
	}

	approverSet := sets.String{}

	// from oldest to latest
	for i := len(comments) - 1; i >= 0; i-- {
		c := comments[i]

		if !mungerutil.IsValidUser(c.User) {
			continue
		}

		fields := strings.Fields(strings.TrimSpace(*c.Body))

		if len(fields) == 1 && strings.ToLower(fields[0]) == "/approve" {
			approverSet.Insert(*c.User.Login)
			continue
		}

		if len(fields) == 2 && strings.ToLower(fields[0]) == "/approve" && strings.ToLower(fields[1]) == "cancel" {
			approverSet.Delete(*c.User.Login)
		}
	}

	for _, file := range files {
		if !h.hasApproval(*file.Filename, approverSet, maxDepth) {
			return
		}
	}
	obj.AddLabel(approvedLabel)
}

func (h *ApprovalHandler) hasApproval(filename string, approverSet sets.String, depth int) bool {
	paths := strings.Split(filename, "/")
	p := ""
	for i := 0; i < len(paths) && i < depth; i++ {
		fileOwners := h.features.Repos.LeafAssignees(p)
		if fileOwners.Len() == 0 {
			glog.Warningf("Couldn't find an owner for path (%s)", p)
			continue
		}

		if h.features.Aliases != nil && h.features.Aliases.IsEnabled {
			fileOwners = h.features.Aliases.Expand(fileOwners)
		}

		for _, owner := range fileOwners.List() {
			if approverSet.Has(owner) {
				return true
			}
		}
		p = path.Join(p, paths[i])
	}
	return false
}
<commit_msg>Un-delete getCommentsAfterLastModified.<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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 mungers

import (
	"path"
	"strings"

	"k8s.io/contrib/mungegithub/features"
	"k8s.io/contrib/mungegithub/github"
	"k8s.io/contrib/mungegithub/mungers/mungerutil"
	"k8s.io/kubernetes/pkg/util/sets"

	"github.com/golang/glog"
	"github.com/spf13/cobra"
)

const maxDepth = 3

// ApprovalHandler will try to add "approved" label once
// all files of change has been approved by approvers.
type ApprovalHandler struct {
	features *features.Features
}

func init() {
	h := &ApprovalHandler{}
	RegisterMungerOrDie(h)
}

// Name is the name usable in --pr-mungers
func (*ApprovalHandler) Name() string { return "approval-handler" }

// RequiredFeatures is a slice of 'features' that must be provided
func (*ApprovalHandler) RequiredFeatures() []string {
	return []string{features.RepoFeatureName, features.AliasesFeature}
}

// Initialize will initialize the munger
func (h *ApprovalHandler) Initialize(config *github.Config, features *features.Features) error {
	h.features = features
	return nil
}

// EachLoop is called at the start of every munge loop
func (*ApprovalHandler) EachLoop() error { return nil }

// AddFlags will add any request flags to the cobra `cmd`
func (*ApprovalHandler) AddFlags(cmd *cobra.Command, config *github.Config) {}

// Munge is the workhorse the will actually make updates to the PR
// The algorithm goes as:
// - Initially, we set up approverSet
//   - Go through all comments after latest commit. If any approver said "/approve", add him to approverSet.
// - For each file, we see if any approver of this file is in approverSet.
//   - An approver of a file is defined as:
//     - It's known that each dir has a list of approvers. (This might not hold true. For usability, current situation is enough.)
//     - Approver of a dir is also the approver of child dirs.
//   - We look at top N (default 3) level dir approvers. For example, for file "/a/b/c/d/e", we might search for approver from
//     "/", "/a/", "/a/b/"
// - Iff all files has been approved, the bot will add "approved" label.
func (h *ApprovalHandler) Munge(obj *github.MungeObject) {
	if !obj.IsPR() {
		return
	}
	files, err := obj.ListFiles()
	if err != nil {
		glog.Errorf("failed to list files in this PR: %v", err)
		return
	}

	comments, err := getCommentsAfterLastModified(obj)
	if err != nil {
		glog.Errorf("failed to get comments in this PR: %v", err)
		return
	}

	approverSet := sets.String{}

	// from oldest to latest
	for i := len(comments) - 1; i >= 0; i-- {
		c := comments[i]

		if !mungerutil.IsValidUser(c.User) {
			continue
		}

		fields := strings.Fields(strings.TrimSpace(*c.Body))

		if len(fields) == 1 && strings.ToLower(fields[0]) == "/approve" {
			approverSet.Insert(*c.User.Login)
			continue
		}

		if len(fields) == 2 && strings.ToLower(fields[0]) == "/approve" && strings.ToLower(fields[1]) == "cancel" {
			approverSet.Delete(*c.User.Login)
		}
	}

	for _, file := range files {
		if !h.hasApproval(*file.Filename, approverSet, maxDepth) {
			return
		}
	}
	obj.AddLabel(approvedLabel)
}

func (h *ApprovalHandler) hasApproval(filename string, approverSet sets.String, depth int) bool {
	paths := strings.Split(filename, "/")
	p := ""
	for i := 0; i < len(paths) && i < depth; i++ {
		fileOwners := h.features.Repos.LeafAssignees(p)
		if fileOwners.Len() == 0 {
			glog.Warningf("Couldn't find an owner for path (%s)", p)
			continue
		}

		if h.features.Aliases != nil && h.features.Aliases.IsEnabled {
			fileOwners = h.features.Aliases.Expand(fileOwners)
		}

		for _, owner := range fileOwners.List() {
			if approverSet.Has(owner) {
				return true
			}
		}
		p = path.Join(p, paths[i])
	}
	return false
}

func getCommentsAfterLastModified(obj *github.MungeObject) ([]*githubapi.IssueComment, error) {
	afterLastModified := func(opt *githubapi.IssueListCommentsOptions) *githubapi.IssueListCommentsOptions {
		// Only comments updated at or after this time are returned.
		// One possible case is that reviewer might "/lgtm" first, contributor updated PR, and reviewer updated "/lgtm".
		// This is still valid. We don't recommend user to update it.
		lastModified := *obj.LastModifiedTime()
		opt.Since = lastModified
		return opt
	}
	return obj.ListComments(afterLastModified)
}
<|endoftext|>


<commit_before>// +build linux darwin freebsd

package commands

import (
	"fmt"
	"time"

	cmds "github.com/jbenet/go-ipfs/commands"
	"github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
	ipns "github.com/jbenet/go-ipfs/fuse/ipns"
	rofs "github.com/jbenet/go-ipfs/fuse/readonly"
)

// amount of time to wait for mount errors
// TODO is this non-deterministic?
const mountTimeout = time.Second

var mountCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only)",
		ShortDescription: `
Mount ipfs at a read-only mountpoint on the OS. All ipfs objects
will be accessible under that directory. Note that the root will
not be listable, as it is virtual. Accessing known paths directly.
`,
	},

	Options: []cmds.Option{
		// TODO longform
		cmds.StringOption("f", "The path where IPFS should be mounted"),

		// TODO longform
		cmds.StringOption("n", "The path where IPNS should be mounted"),
	},
	Run: func(req cmds.Request) (interface{}, error) {
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

		node, err := req.Context().GetNode()
		if err != nil {
			return nil, err
		}

		// error if we aren't running node in online mode
		if node.Network == nil {
			return nil, errNotOnline
		}

		if err := platformFuseChecks(); err != nil {
			return nil, err
		}

		fsdir, found, err := req.Option("f").String()
		if err != nil {
			return nil, err
		}
		if !found {
			fsdir = cfg.Mounts.IPFS // use default value
		}
		fsdone := mountIpfs(node, fsdir)

		// get default mount points
		nsdir, found, err := req.Option("n").String()
		if err != nil {
			return nil, err
		}
		if !found {
			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
		}

		nsdone := mountIpns(node, nsdir, fsdir)

		// wait until mounts return an error (or timeout if successful)
		select {
		case err := <-fsdone:
			return nil, err
		case err := <-nsdone:
			return nil, err

		// mounted successfully, we timed out with no errors
		case <-time.After(mountTimeout):
			output := cfg.Mounts
			return &output, nil
		}
	},
	Type: &config.Mounts{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			v := res.Output().(*config.Mounts)
			s := fmt.Sprintf("IPFS mounted at: %s\n", v.IPFS)
			s += fmt.Sprintf("IPNS mounted at: %s\n", v.IPNS)
			return []byte(s), nil
		},
	},
}

func mountIpfs(node *core.IpfsNode, fsdir string) <-chan error {
	done := make(chan error)
	log.Info("Mounting IPFS at ", fsdir)

	go func() {
		err := rofs.Mount(node, fsdir)
		done <- err
		close(done)
	}()

	return done
}

func mountIpns(node *core.IpfsNode, nsdir, fsdir string) <-chan error {
	if nsdir == "" {
		return nil
	}
	done := make(chan error)
	log.Info("Mounting IPNS at ", nsdir)

	go func() {
		err := ipns.Mount(node, nsdir, fsdir)
		done <- err
		close(done)
	}()

	return done
}

var platformFuseChecks = func() error {
	return nil
}
<commit_msg>cmds2: mount exmplae<commit_after>// +build linux darwin freebsd

package commands

import (
	"fmt"
	"time"

	cmds "github.com/jbenet/go-ipfs/commands"
	"github.com/jbenet/go-ipfs/config"
	core "github.com/jbenet/go-ipfs/core"
	ipns "github.com/jbenet/go-ipfs/fuse/ipns"
	rofs "github.com/jbenet/go-ipfs/fuse/readonly"
)

// amount of time to wait for mount errors
// TODO is this non-deterministic?
const mountTimeout = time.Second

var mountCmd = &cmds.Command{
	Helptext: cmds.HelpText{
		Tagline: "Mounts IPFS to the filesystem (read-only)",
		ShortDescription: `
Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All ipfs objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

You may kave to create /ipfs and /ipfs before using 'ipfs mount':

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs mount
`,
		LongDescription: `
Mount ipfs at a read-only mountpoint on the OS (default: /ipfs and /ipns).
All ipfs objects will be accessible under that directory. Note that the
root will not be listable, as it is virtual. Access known paths directly.

> sudo mkdir /ipfs /ipns
> sudo chown ` + "`" + `whoami` + "`" + ` /ipfs /ipns
> ipfs mount

EXAMPLE:

# setup
> mkdir foo
> echo "baz" > foo/bar
> ipfs add -r foo
added QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR foo/bar
added QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC foo
> ipfs ls QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR 12 bar
> ipfs cat QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz

# mount
> ipfs daemon &
> ipfs mount
IPFS mounted at: /ipfs
IPNS mounted at: /ipns
> cd /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC
> ls
bar
> cat bar
baz
> cat /ipfs/QmSh5e7S6fdcu75LAbXNZAFY2nGyZUJXyLCJDvn2zRkWyC/bar
baz
> cat /ipfs/QmWLdkp93sNxGRjnFHPaYg8tCQ35NBY3XPn6KiETd3Z4WR
baz
`,
	},

	Options: []cmds.Option{
		// TODO longform
		cmds.StringOption("f", "The path where IPFS should be mounted"),

		// TODO longform
		cmds.StringOption("n", "The path where IPNS should be mounted"),
	},
	Run: func(req cmds.Request) (interface{}, error) {
		cfg, err := req.Context().GetConfig()
		if err != nil {
			return nil, err
		}

		node, err := req.Context().GetNode()
		if err != nil {
			return nil, err
		}

		// error if we aren't running node in online mode
		if node.Network == nil {
			return nil, errNotOnline
		}

		if err := platformFuseChecks(); err != nil {
			return nil, err
		}

		fsdir, found, err := req.Option("f").String()
		if err != nil {
			return nil, err
		}
		if !found {
			fsdir = cfg.Mounts.IPFS // use default value
		}
		fsdone := mountIpfs(node, fsdir)

		// get default mount points
		nsdir, found, err := req.Option("n").String()
		if err != nil {
			return nil, err
		}
		if !found {
			nsdir = cfg.Mounts.IPNS // NB: be sure to not redeclare!
		}

		nsdone := mountIpns(node, nsdir, fsdir)

		// wait until mounts return an error (or timeout if successful)
		select {
		case err := <-fsdone:
			return nil, err
		case err := <-nsdone:
			return nil, err

		// mounted successfully, we timed out with no errors
		case <-time.After(mountTimeout):
			output := cfg.Mounts
			return &output, nil
		}
	},
	Type: &config.Mounts{},
	Marshalers: cmds.MarshalerMap{
		cmds.Text: func(res cmds.Response) ([]byte, error) {
			v := res.Output().(*config.Mounts)
			s := fmt.Sprintf("IPFS mounted at: %s\n", v.IPFS)
			s += fmt.Sprintf("IPNS mounted at: %s\n", v.IPNS)
			return []byte(s), nil
		},
	},
}

func mountIpfs(node *core.IpfsNode, fsdir string) <-chan error {
	done := make(chan error)
	log.Info("Mounting IPFS at ", fsdir)

	go func() {
		err := rofs.Mount(node, fsdir)
		done <- err
		close(done)
	}()

	return done
}

func mountIpns(node *core.IpfsNode, nsdir, fsdir string) <-chan error {
	if nsdir == "" {
		return nil
	}
	done := make(chan error)
	log.Info("Mounting IPNS at ", nsdir)

	go func() {
		err := ipns.Mount(node, nsdir, fsdir)
		done <- err
		close(done)
	}()

	return done
}

var platformFuseChecks = func() error {
	return nil
}
<|endoftext|>


<commit_before>package core

import (
	. "github.com/smartystreets/goconvey/convey"
	"pfi/sensorbee/sensorbee/core/tuple"
	"strings"
	"testing"
)

func TestDefaultTopology(t *testing.T) {
	Convey("When creating a default topology builder", t, func() {
		var tb StaticTopologyBuilder = NewDefaultStaticTopologyBuilder()
		So(tb, ShouldNotBeNil)
	})

	Convey("Given a default topology builder", t, func() {
		tb := NewDefaultStaticTopologyBuilder()
		s := &DefaultSource{}
		b := &DefaultBox{}
		si := &DefaultSink{}
		var err DeclarerError

		Convey("when using a source name twice", func() {
			err = tb.AddSource("mySource", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("mySource", s)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a source name with a box name", func() {
			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a source name with a sink name", func() {
			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name twice", func() {
			err = tb.AddBox("myBox", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("myBox", b)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name with a source name", func() {
			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name with a sink name", func() {
			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name twice", func() {
			err = tb.AddSink("mySink", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("mySink", si)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name with a source name", func() {
			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name with a box name", func() {
			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})
	})

	Convey("Given a default topology builder with a source", t, func() {
		tb := NewDefaultStaticTopologyBuilder()
		s := &DefaultSource{}
		tb.AddSource("aSource", s)
		b := &DefaultBox{}
		tb.AddBox("aBox", b)
		si := &DefaultSink{}
		var err DeclarerError

		Convey("when a new box references a non-existing item", func() {
			err = tb.AddBox("otherBox", b).
				Input("something", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new box references an existing source", func() {
			err = tb.AddBox("otherBox", b).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references an existing box", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references multiple items", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references an existing source twice", func() {
			err = tb.AddBox("otherBox", b).
				Input("aSource", nil).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new box references an existing box twice", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil).
				Input("aBox", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references a non-existing item", func() {
			err = tb.AddSink("aSink", si).
				Input("something")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references an existing source", func() {
			err = tb.AddSink("aSink", si).
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references an existing box", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references multiple items", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox").
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references an existing source twice", func() {
			err = tb.AddSink("aSink", si).
				Input("aSource").
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references an existing box twice", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox").
				Input("aBox")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})
	})

	/* TODO Add tests that show the correct data transport in DefaultTopology.
	 * We should remove the Default* structs from defaulttopology.go and
	 * add some replacements in this file that allow us to check data
	 * flow. For example, a TestSource could just emit two messages with
	 * different IDs and a TestSink could store all received messages
	 * in a local array. Then after Run() is finished, check whether all
	 * items arrived at the sink(s) in the correct order and where processed
	 * correctly by all intermediate boxes.
	 */
	Convey("Given a default topology", t, func() {

		tb := NewDefaultStaticTopologyBuilder()
		s1 := &DummyDefaultSource{"value"}
		tb.AddSource("Source1", s1)
		s2 := &DummyDefaultSource{"test"}
		tb.AddSource("Source2", s2)
		s3 := &DummyDefaultSource{"hoge"}
		tb.AddSource("Source3", s3)
		s4 := &DummyDefaultSource{"fuga"}
		tb.AddSource("Source4", s4)
		s5 := &DummyDefaultSource{"foo"}
		tb.AddSource("Source5", s5)
		b1 := BoxFunc(dummyToUpperBoxFunc)
		tb.AddBox("aBox", &b1).Input("Source1", nil).
			Input("Source2", nil).
			Input("Source3", nil).
			Input("Source4", nil).
			Input("Source5", nil)
		b2 := BoxFunc(dummyFilterBoxFunc)
		tb.AddBox("bBox", &b2).Input("aBox", nil)
		si := &DummyDefaultSink{}
		tb.AddSink("si", si).Input("aBox")
		t := tb.Build()
		Convey("Run topology", func() {
			t.Run()
			So(si.result, ShouldEqual, "HOGE")
		})
	})

}

type DummyDefaultSource struct{ initial string }

func (this *DummyDefaultSource) GenerateStream(w Writer) error {
	t := &tuple.Tuple{}
	t.Data = tuple.Map{
		"source": tuple.String(this.initial),
	}
	w.Write(t)
	return nil
}
func (this *DummyDefaultSource) Schema() *Schema {
	var s Schema = Schema("test")
	return &s
}

func dummyToUpperBoxFunc(t *tuple.Tuple, w Writer) error {
	x, _ := t.Data.Get("source")
	s, _ := x.String()
	t.Data = tuple.Map{
		"source": tuple.String(strings.ToUpper(string(s))),
	}
	w.Write(t)
	return nil
}

func dummyFilterBoxFunc(t *tuple.Tuple, w Writer) error {
	x, _ := t.Data.Get("source")
	s, _ := x.String()
	if s == "HOGE" {
		t.Data["filtered"] = s
	}
	return nil
}

type DummyDefaultSink struct{ result string }

func (this *DummyDefaultSink) Write(t *tuple.Tuple) error {
	x, err := t.Data.Get("filtered")
	if err != nil {
		return nil
	}
	s, _ := x.String()
	this.result = string(s)
	return nil
}
<commit_msg>add basic topology test<commit_after>package core

import (
	. "github.com/smartystreets/goconvey/convey"
	"pfi/sensorbee/sensorbee/core/tuple"
	"strings"
	"testing"
	"time"
)

func TestDefaultTopology(t *testing.T) {
	Convey("When creating a default topology builder", t, func() {
		var tb StaticTopologyBuilder = NewDefaultStaticTopologyBuilder()
		So(tb, ShouldNotBeNil)
	})

	Convey("Given a default topology builder", t, func() {
		tb := NewDefaultStaticTopologyBuilder()
		s := &DefaultSource{}
		b := &DefaultBox{}
		si := &DefaultSink{}
		var err DeclarerError

		Convey("when using a source name twice", func() {
			err = tb.AddSource("mySource", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("mySource", s)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a source name with a box name", func() {
			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a source name with a sink name", func() {
			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name twice", func() {
			err = tb.AddBox("myBox", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("myBox", b)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name with a source name", func() {
			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a box name with a sink name", func() {
			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name twice", func() {
			err = tb.AddSink("mySink", si)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("mySink", si)
			So(err, ShouldNotBeNil)
			Convey("the second time should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name with a source name", func() {
			err = tb.AddSource("someName", s)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when using a sink name with a box name", func() {
			err = tb.AddBox("someName", b)
			So(err, ShouldNotBeNil)
			So(err.Err(), ShouldBeNil)

			err = tb.AddSink("someName", si)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})
	})

	Convey("Given a default topology builder with a source", t, func() {
		tb := NewDefaultStaticTopologyBuilder()
		s := &DefaultSource{}
		tb.AddSource("aSource", s)
		b := &DefaultBox{}
		tb.AddBox("aBox", b)
		si := &DefaultSink{}
		var err DeclarerError

		Convey("when a new box references a non-existing item", func() {
			err = tb.AddBox("otherBox", b).
				Input("something", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new box references an existing source", func() {
			err = tb.AddBox("otherBox", b).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references an existing box", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references multiple items", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new box references an existing source twice", func() {
			err = tb.AddBox("otherBox", b).
				Input("aSource", nil).
				Input("aSource", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new box references an existing box twice", func() {
			err = tb.AddBox("otherBox", b).
				Input("aBox", nil).
				Input("aBox", nil)
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references a non-existing item", func() {
			err = tb.AddSink("aSink", si).
				Input("something")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references an existing source", func() {
			err = tb.AddSink("aSink", si).
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references an existing box", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references multiple items", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox").
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should work", func() {
				So(err.Err(), ShouldBeNil)
			})
		})

		Convey("when a new sink references an existing source twice", func() {
			err = tb.AddSink("aSink", si).
				Input("aSource").
				Input("aSource")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})

		Convey("when a new sink references an existing box twice", func() {
			err = tb.AddSink("aSink", si).
				Input("aBox").
				Input("aBox")
			So(err, ShouldNotBeNil)
			Convey("adding should fail", func() {
				So(err.Err(), ShouldNotBeNil)
			})
		})
	})

	/* TODO Add tests that show the correct data transport in DefaultTopology.
	 * We should remove the Default* structs from defaulttopology.go and
	 * add some replacements in this file that allow us to check data
	 * flow. For example, a TestSource could just emit two messages with
	 * different IDs and a TestSink could store all received messages
	 * in a local array. Then after Run() is finished, check whether all
	 * items arrived at the sink(s) in the correct order and where processed
	 * correctly by all intermediate boxes.
	 */
	Convey("Given basic topology", t, func() {

		tb := NewDefaultStaticTopologyBuilder()
		s1 := &DummyDefaultSource{"value"}
		tb.AddSource("Source1", s1)
		b1 := BoxFunc(dummyToUpperBoxFunc)
		tb.AddBox("aBox", &b1).Input("Source1", nil)
		si := &DummyDefaultSink{}
		tb.AddSink("si", si).Input("aBox")
		t := tb.Build()
		Convey("Run topology with ToUpperBox", func() {
			t.Run()
			So(si.filtered, ShouldEqual, "VALUE")
		})
	})

}

type DummyDefaultSource struct{ initial string }

func (this *DummyDefaultSource) GenerateStream(w Writer) error {
	time.Sleep(0.5 * 1e9) // to confirm .Run() goroutine
	t := &tuple.Tuple{}
	t.Data = tuple.Map{
		"source": tuple.String(this.initial),
	}
	w.Write(t)
	t2 := &tuple.Tuple{}
	t2.Data = tuple.Map{
		"source": tuple.String(this.initial),
	}
	w.Write(t2)
	return nil
}
func (this *DummyDefaultSource) Schema() *Schema {
	var s Schema = Schema("test")
	return &s
}

func dummyToUpperBoxFunc(t *tuple.Tuple, w Writer) error {
	x, _ := t.Data.Get("source")
	s, _ := x.String()
	t.Data["to-upper-box"] = tuple.String(strings.ToUpper(string(s)))
	w.Write(t)
	return nil
}

type DummyDefaultSink struct {
	filtered string
}

func (this *DummyDefaultSink) Write(t *tuple.Tuple) error {
	x, err := t.Data.Get("to-upper-box")
	if err != nil {
		return nil
	}
	s, _ := x.String()
	this.filtered = string(s)
	return nil
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
	"encoding/json"
	"github.com/ghawk1ns/golf/database"
	"github.com/gorilla/mux"
	"github.com/ghawk1ns/golf/model"
	"errors"
	"github.com/ghawk1ns/golf/logger"
)

func GolferProfile(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)
	golferId := vars["id"]

	if golferId == "" {
		onGolferError(w, errors.New("Invalid Golfer Id"))
	}

	var result model.Profile
	golfer, err := database.GetGolferById(golferId)
	if err != nil {
		onGolferError(w, err)
		return
	} else {
		result.Golfer = golfer
	}

	scores, err := database.GetScoresForGolfer(golfer.GolferId)
	if err != nil {
		onGolferError(w, err)
		return
	} else {
		result.Scores = scores
	}

	result.Stats, err = getStats(golferId)

	b, err := json.Marshal(result)
	if err != nil {
		onGolferProfileError(w, err)
		return
	} else {
		logger.Info.Println("golferProfile: ", string(b))
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, string(b))
	}
}

// This is not a good way to do this
func onGolferProfileError(w http.ResponseWriter, err error) {
	w.WriteHeader(http.StatusBadRequest)
	logger.Error.Println("an error occured:", err.Error())
	fmt.Fprintln(w, nil)
}

func getStats(golferId string) (model.Stats, error) {
	roundAvg := make(chan float64)
	numRounds := make(chan int)
	winCounts := make(chan []model.WinCount)

	// retrieve the golfer's average
	go func() {
		result, err := database.GetGolferAverage(golferId)
		if err != nil {
			logger.Error.Println(err)
			roundAvg <- -1
		} else {
			roundAvg <- result
		}
	}()

	// retrieve the golfer's total rounds played
	go func() {
		result, err := database.GetGolferNumRounds(golferId)
		if err != nil {
			logger.Error.Println(err)
			numRounds <- -1
		} else {
			numRounds <- result
		}
	}()

	// retrieve the golfer's victory over other golfers
	go func() {
		logger.Info.Println("Getting win stats for", golferId)
		wins, err := database.GetGolferWins(golferId)
		if err != nil {
			logger.Error.Println(err)
			winCounts <- nil
		} else {
			var localWinCounts []model.WinCount
			for opponentId,count := range wins {
				golfer, err := database.GetGolferById(opponentId)
				if err != nil {
					logger.Error.Println(err.Error())
				} else {
					logger.Info.Printf("%s has beaten %s, %d times\n", golferId, golfer.Name, count)
					localWinCounts = append(localWinCounts, model.WinCount{golfer, count})
				}

			}
			winCounts <- localWinCounts
		}
	}()

	stats := model.Stats{ <- numRounds, <- roundAvg, <- winCounts}
	if stats.Rounds == -1 || stats.Average == -1 {
		return stats, errors.New("something went wrong with stat gathering")
	} else {
		return stats, nil
	}
}<commit_msg>Don't use goroutines to fetch stats async just yet<commit_after>package handlers

import (
	"fmt"
	"net/http"
	"encoding/json"
	"github.com/ghawk1ns/golf/database"
	"github.com/gorilla/mux"
	"github.com/ghawk1ns/golf/model"
	"errors"
	"github.com/ghawk1ns/golf/logger"
)

func GolferProfile(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json; charset=UTF-8")

	vars := mux.Vars(r)
	golferId := vars["id"]

	if golferId == "" {
		onGolferError(w, errors.New("Invalid Golfer Id"))
	}

	var result model.Profile
	golfer, err := database.GetGolferById(golferId)
	if err != nil {
		onGolferError(w, err)
		return
	} else {
		result.Golfer = golfer
	}

	scores, err := database.GetScoresForGolfer(golfer.GolferId)
	if err != nil {
		onGolferError(w, err)
		return
	} else {
		result.Scores = scores
	}

	result.Stats, err = getStats(golferId)

	b, err := json.Marshal(result)
	if err != nil {
		onGolferProfileError(w, err)
		return
	} else {
		logger.Info.Println("golferProfile: ", string(b))
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, string(b))
	}
}

// This is not a good way to do this
func onGolferProfileError(w http.ResponseWriter, err error) {
	w.WriteHeader(http.StatusBadRequest)
	logger.Error.Println("an error occured:", err.Error())
	fmt.Fprintln(w, nil)
}

// TODO: debug me
func getStatsAsync(golferId string) (model.Stats, error) {
	roundAvg := make(chan float64)
	numRounds := make(chan int)
	winCounts := make(chan []model.WinCount)

	// retrieve the golfer's average
	go func() {
		result, err := database.GetGolferAverage(golferId)
		if err != nil {
			logger.Error.Println(err)
			roundAvg <- -1
		} else {
			roundAvg <- result
		}
	}()

	// retrieve the golfer's total rounds played
	go func() {
		result, err := database.GetGolferNumRounds(golferId)
		if err != nil {
			logger.Error.Println(err)
			numRounds <- -1
		} else {
			numRounds <- result
		}
	}()

	// retrieve the golfer's victory over other golfers
	go func() {
		logger.Info.Println("Getting win stats for", golferId)
		wins, err := database.GetGolferWins(golferId)
		if err != nil {
			logger.Error.Println(err)
			winCounts <- nil
		} else {
			var localWinCounts []model.WinCount
			for opponentId,count := range wins {
				golfer, err := database.GetGolferById(opponentId)
				if err != nil {
					logger.Error.Println(err.Error())
				} else {
					logger.Info.Printf("%s has beaten %s, %d times\n", golferId, golfer.Name, count)
					localWinCounts = append(localWinCounts, model.WinCount{golfer, count})
				}

			}
			winCounts <- localWinCounts
		}
	}()

	stats := model.Stats{ <- numRounds, <- roundAvg, <- winCounts}
	if stats.Rounds == -1 || stats.Average == -1 {
		return stats, errors.New("something went wrong with stat gathering")
	} else {
		return stats, nil
	}
}

func getStats(golferId string) (model.Stats, error) {

	roundAvg, err := database.GetGolferAverage(golferId)
	if err != nil {
		logger.Error.Println(err)
		roundAvg = -1
	}

	// retrieve the golfer's total rounds played
	numRounds, err := database.GetGolferNumRounds(golferId)
	if err != nil {
		logger.Error.Println(err)
		numRounds = -1
	}

	// retrieve the golfer's victory over other golfers
	logger.Info.Println("Getting win stats for", golferId)
	wins, err := database.GetGolferWins(golferId)
	var winCounts []model.WinCount
	if err != nil {
		logger.Error.Println(err)
		winCounts = nil
	} else {
		var localWinCounts []model.WinCount
		for opponentId,count := range wins {
			golfer, err := database.GetGolferById(opponentId)
			if err != nil {
				logger.Error.Println(err.Error())
			} else {
				logger.Info.Printf("%s has beaten %s, %d times\n", golferId, golfer.Name, count)
				localWinCounts = append(localWinCounts, model.WinCount{golfer, count})
			}

		}
		winCounts = localWinCounts
	}

	stats := model.Stats{ numRounds, roundAvg, winCounts}
	if stats.Rounds == -1 || stats.Average == -1 {
		return stats, errors.New("something went wrong with stat gathering")
	} else {
		return stats, nil
	}
}<|endoftext|>