<commit_before>package daemon

import (
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/subgraph/oz/ipc"
)

func clientConnect() (*ipc.MsgConn, error) {
	return ipc.Connect(SocketName, messageFactory, nil)
}

func clientSend(msg interface{}) (*ipc.Message, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	defer c.Close()
	rr, err := c.ExchangeMsg(msg)
	if err != nil {
		return nil, err
	}

	resp := <-rr.Chan()
	rr.Done()
	return resp, nil
}

func ListProfiles() ([]Profile, error) {
	resp, err := clientSend(new(ListProfilesMsg))
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListProfilesResp)
	if !ok {
		return nil, errors.New("ListProfiles response was not expected type")
	}
	return body.Profiles, nil
}

func ListSandboxes() ([]SandboxInfo, error) {
	resp, err := clientSend(&ListSandboxesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListSandboxesResp)
	if !ok {
		return nil, errors.New("ListSandboxes response was not expected type")
	}
	return body.Sandboxes, nil
}

func Launch(arg, cpath string, args []string, noexec bool) error {
	idx, name, err := parseProfileArg(arg)
	if err != nil {
		return err
	}
	pwd, _ := os.Getwd()
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&LaunchMsg{
		Index:  idx,
		Name:   name,
		Path:   cpath,
		Pwd:    pwd,
		Gids:   gg,
		Args:   args,
		Env:    os.Environ(),
		Noexec: noexec,
	})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		fmt.Printf("error was %s\n", body.Msg)
	case *OkMsg:
		fmt.Println("ok received")
	default:
		fmt.Printf("Unexpected message received %+v", body)
	}
	return nil
}

func KillAllSandboxes() error {
	return KillSandbox(-1)
}

func KillSandbox(id int) error {
	resp, err := clientSend(&KillSandboxMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchXpraClient(id int) error {
	resp, err := clientSend(&RelaunchXpraClientMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchAllXpraClient() error {
	return RelaunchXpraClient(-1)
}

func MountFiles(id int, files []string, readOnly bool) error {
	mountFilesMsg := MountFilesMsg{
		Id:       id,
		Files:    files,
		ReadOnly: readOnly,
	}
	resp, err := clientSend(&mountFilesMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func UnmountFile(id int, file string) error {
	unmountFileMsg := UnmountFileMsg{
		Id:   id,
		File: file,
	}
	resp, err := clientSend(&unmountFileMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func parseProfileArg(arg string) (int, string, error) {
	if len(arg) == 0 {
		return 0, "", errors.New("profile argument needed")
	}
	if n, err := strconv.Atoi(arg); err == nil {
		return n, "", nil
	}
	return 0, arg, nil
}

func Logs(count int, follow bool) (chan string, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	rr, err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
	if err != nil {
		return nil, err
	}
	out := make(chan string)
	go dumpLogs(out, rr)
	return out, nil
}

func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
	for resp := range rr.Chan() {
		switch body := resp.Body.(type) {
		case *OkMsg:
			rr.Done()
			close(out)
			return
		case *LogData:
			for _, ll := range body.Lines {
				out <- ll
			}
		default:
			out <- fmt.Sprintf("Unexpected response type (%T)", body)
		}
	}
}
<commit_msg>Backport dynamic forwarder oz client implementation<commit_after>package daemon

import (
	"errors"
	"fmt"
	"os"
	"strconv"

	"github.com/subgraph/oz/ipc"
)

func clientConnect() (*ipc.MsgConn, error) {
	return ipc.Connect(SocketName, messageFactory, nil)
}

func clientSend(msg interface{}) (*ipc.Message, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	defer c.Close()
	rr, err := c.ExchangeMsg(msg)
	if err != nil {
		return nil, err
	}

	resp := <-rr.Chan()
	rr.Done()
	return resp, nil
}

func ListProfiles() ([]Profile, error) {
	resp, err := clientSend(new(ListProfilesMsg))
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListProfilesResp)
	if !ok {
		return nil, errors.New("ListProfiles response was not expected type")
	}
	return body.Profiles, nil
}

func ListForwarders(id int) ([]Forwarder, error) {
	resp, err := clientSend(&ListForwardersMsg{Id: id})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListForwardersResp)
	if !ok {
		return nil, errors.New("ListForwarders response was not expected type")
	}
	return body.Forwarders, nil
}

func ListSandboxes() ([]SandboxInfo, error) {
	resp, err := clientSend(&ListSandboxesMsg{})
	if err != nil {
		return nil, err
	}
	body, ok := resp.Body.(*ListSandboxesResp)
	if !ok {
		return nil, errors.New("ListSandboxes response was not expected type")
	}
	return body.Sandboxes, nil
}

func Launch(arg, cpath string, args []string, noexec bool) error {
	idx, name, err := parseProfileArg(arg)
	if err != nil {
		return err
	}
	pwd, _ := os.Getwd()
	groups, _ := os.Getgroups()
	gg := []uint32{}
	if len(groups) > 0 {
		gg = make([]uint32, len(groups))
		for i, v := range groups {
			gg[i] = uint32(v)
		}
	}
	resp, err := clientSend(&LaunchMsg{
		Index:  idx,
		Name:   name,
		Path:   cpath,
		Pwd:    pwd,
		Gids:   gg,
		Args:   args,
		Env:    os.Environ(),
		Noexec: noexec,
	})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		fmt.Printf("error was %s\n", body.Msg)
	case *OkMsg:
		fmt.Println("ok received")
	default:
		fmt.Printf("Unexpected message received %+v", body)
	}
	return nil
}

func KillAllSandboxes() error {
	return KillSandbox(-1)
}

func KillSandbox(id int) error {
	resp, err := clientSend(&KillSandboxMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchXpraClient(id int) error {
	resp, err := clientSend(&RelaunchXpraClientMsg{Id: id})
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func RelaunchAllXpraClient() error {
	return RelaunchXpraClient(-1)
}

func MountFiles(id int, files []string, readOnly bool) error {
	mountFilesMsg := MountFilesMsg{
		Id:       id,
		Files:    files,
		ReadOnly: readOnly,
	}
	resp, err := clientSend(&mountFilesMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func UnmountFile(id int, file string) error {
	unmountFileMsg := UnmountFileMsg{
		Id:   id,
		File: file,
	}
	resp, err := clientSend(&unmountFileMsg)
	if err != nil {
		return err
	}
	switch body := resp.Body.(type) {
	case *ErrorMsg:
		return errors.New(body.Msg)
	case *OkMsg:
		return nil
	default:
		return fmt.Errorf("Unexpected message received %+v", body)
	}
}

func AskForwarder(id int, name, port string) (string, error) {
	askForwarderMsg := AskForwarderMsg{
		Id: id,
		Name: name,
		Port: port,
	}
	resp, err := clientSend(&askForwarderMsg)
	if err != nil {
		return "", err
	}
        body, ok := resp.Body.(*ForwarderSuccessMsg)
	if !ok {
                return "", fmt.Errorf("Unexpected message received %+v", body)
	} else {
		return body.Addr, nil
	}
}

func parseProfileArg(arg string) (int, string, error) {
	if len(arg) == 0 {
		return 0, "", errors.New("profile argument needed")
	}
	if n, err := strconv.Atoi(arg); err == nil {
		return n, "", nil
	}
	return 0, arg, nil
}

func Logs(count int, follow bool) (chan string, error) {
	c, err := clientConnect()
	if err != nil {
		return nil, err
	}
	rr, err := c.ExchangeMsg(&LogsMsg{Count: count, Follow: follow})
	if err != nil {
		return nil, err
	}
	out := make(chan string)
	go dumpLogs(out, rr)
	return out, nil
}

func dumpLogs(out chan<- string, rr ipc.ResponseReader) {
	for resp := range rr.Chan() {
		switch body := resp.Body.(type) {
		case *OkMsg:
			rr.Done()
			close(out)
			return
		case *LogData:
			for _, ll := range body.Lines {
				out <- ll
			}
		default:
			out <- fmt.Sprintf("Unexpected response type (%T)", body)
		}
	}
}
<|endoftext|>


<commit_before>package crc

func Validate(bs []byte) bool {
	if len(bs) <= 2 {
		return false
	}
	length := len(bs)
	var crc crc
	crc.Reset()
	crc.Write(bs[0 : length-2])
	return bs[length-2] == crc.low && bs[length-1] == crc.high
}

func Append(bs []byte) []byte {
	return New().Sum(bs)
}
<commit_msg>add a little doc<commit_after>package crc

//Validate return true if byte slice ends with valid crc
func Validate(bs []byte) bool {
	if len(bs) <= 2 {
		return false
	}
	length := len(bs)
	var crc crc
	crc.Reset()
	crc.Write(bs[0 : length-2])
	return bs[length-2] == crc.low && bs[length-1] == crc.high
}

func Append(bs []byte) []byte {
	return New().Sum(bs)
}
<|endoftext|>


<commit_before>package agent

import (
	"log"
	"strings"
	"time"
)

// DiscoverInterface is an interface for the Discover type in the go-discover
// library. Using an interface allows for ease of testing.
type DiscoverInterface interface {
	// Addrs discovers ip addresses of nodes that match the given filter
	// criteria.
	// The config string must have the format 'provider=xxx key=val key=val ...'
	// where the keys and values are provider specific. The values are URL
	// encoded.
	Addrs(string, *log.Logger) ([]string, error)

	// Help describes the format of the configuration string for address
	// discovery and the various provider specific options.
	Help() string

	// Names returns the names of the configured providers.
	Names() []string
}

// retryJoiner is used to handle retrying a join until it succeeds or all of
// its tries are exhausted.
type retryJoiner struct {
	// join adds the specified servers to the serf cluster
	join func([]string) (int, error)

	// discover is of type Discover, where this is either the go-discover
	// implementation or a mock used for testing
	discover DiscoverInterface

	// errCh is used to communicate with the agent when the max retry attempt
	// limit has been reached
	errCh chan struct{}

	// logger is the agent logger.
	logger *log.Logger
}

// retryJoin is used to handle retrying a join until it succeeds or all retries
// are exhausted.
func (r *retryJoiner) RetryJoin(config *Config) {
	if len(config.Server.RetryJoin) == 0 || !config.Server.Enabled {
		return
	}

	attempt := 0

	addrsToJoin := strings.Join(config.Server.RetryJoin, " ")
	r.logger.Printf("[INFO] agent: Joining cluster... %s", addrsToJoin)

	for {
		var addrs []string

		for _, addr := range config.Server.RetryJoin {
			switch {
			case strings.HasPrefix(addr, "provider="):
				servers, err := r.discover.Addrs(addr, r.logger)
				if err != nil {
					r.logger.Printf("[ERR] agent: Join error %s", err)
				} else {
					addrs = append(addrs, servers...)
				}
			default:
				addrs = append(addrs, addr)
			}
		}

		if len(addrs) == 0 {
			r.logger.Printf("[INFO] agent: Join completed. no addresses specified to sync with")
			return
		}

		n, err := r.join(addrs)

		if err == nil {
			r.logger.Printf("[INFO] agent: Join completed. Synced with %d initial agents", n)
			return
		}

		attempt++
		if config.Server.RetryMaxAttempts > 0 && attempt > config.Server.RetryMaxAttempts {
			r.logger.Printf("[ERR] agent: max join retry exhausted, exiting")
			close(r.errCh)
			return
		}

		r.logger.Printf("[WARN] agent: Join failed: %v, retrying in %v", err,
			config.Server.RetryInterval)
		time.Sleep(config.Server.retryInterval)
	}
}
<commit_msg>retry until all options are exhausted<commit_after>package agent

import (
	"log"
	"strings"
	"time"
)

// DiscoverInterface is an interface for the Discover type in the go-discover
// library. Using an interface allows for ease of testing.
type DiscoverInterface interface {
	// Addrs discovers ip addresses of nodes that match the given filter
	// criteria.
	// The config string must have the format 'provider=xxx key=val key=val ...'
	// where the keys and values are provider specific. The values are URL
	// encoded.
	Addrs(string, *log.Logger) ([]string, error)

	// Help describes the format of the configuration string for address
	// discovery and the various provider specific options.
	Help() string

	// Names returns the names of the configured providers.
	Names() []string
}

// retryJoiner is used to handle retrying a join until it succeeds or all of
// its tries are exhausted.
type retryJoiner struct {
	// join adds the specified servers to the serf cluster
	join func([]string) (int, error)

	// discover is of type Discover, where this is either the go-discover
	// implementation or a mock used for testing
	discover DiscoverInterface

	// errCh is used to communicate with the agent when the max retry attempt
	// limit has been reached
	errCh chan struct{}

	// logger is the agent logger.
	logger *log.Logger
}

// retryJoin is used to handle retrying a join until it succeeds or all retries
// are exhausted.
func (r *retryJoiner) RetryJoin(config *Config) {
	if len(config.Server.RetryJoin) == 0 || !config.Server.Enabled {
		return
	}

	attempt := 0

	addrsToJoin := strings.Join(config.Server.RetryJoin, " ")
	r.logger.Printf("[INFO] agent: Joining cluster... %s", addrsToJoin)

	for {
		var addrs []string
		var err error

		for _, addr := range config.Server.RetryJoin {
			switch {
			case strings.HasPrefix(addr, "provider="):
				servers, err := r.discover.Addrs(addr, r.logger)
				if err != nil {
					r.logger.Printf("[ERR] agent: Join error %s", err)
				} else {
					addrs = append(addrs, servers...)
				}
			default:
				addrs = append(addrs, addr)
			}
		}

		if len(addrs) > 0 {
			n, err := r.join(addrs)
			if err == nil {
				r.logger.Printf("[INFO] agent: Join completed. Synced with %d initial agents", n)
			}
		}

		if len(addrs) == 0 {
			r.logger.Printf("[INFO] agent: Join completed. no addresses specified to sync with")
		}

		attempt++
		if config.Server.RetryMaxAttempts > 0 && attempt > config.Server.RetryMaxAttempts {
			r.logger.Printf("[ERR] agent: max join retry exhausted, exiting")
			close(r.errCh)
			return
		}

		r.logger.Printf("[WARN] agent: Join failed: %v, retrying in %v", err,
			config.Server.RetryInterval)
		time.Sleep(config.Server.retryInterval)
	}
}
<|endoftext|>


<commit_before>package files

import (
	"io"
	"io/ioutil"
	"mime/multipart"
	"strings"
	"testing"
)

func TestSliceFiles(t *testing.T) {
	name := "testname"
	files := []File{
		NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader("Some text!\n")), nil),
		NewReaderFile("beep.txt", "beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil),
		NewReaderFile("boop.txt", "boop.txt", ioutil.NopCloser(strings.NewReader("boop")), nil),
	}
	buf := make([]byte, 20)

	sf := NewSliceFile(name, name, files)

	if !sf.IsDirectory() {
		t.Fatal("SliceFile should always be a directory")
	}

	if n, err := sf.Read(buf); n > 0 || err != io.EOF {
		t.Fatal("Shouldn't be able to read data from a SliceFile")
	}

	if err := sf.Close(); err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Close` on a SliceFile")
	}

	file, err := sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}
	read, err := file.Read(buf)
	if read != 11 || err != nil {
		t.Fatal("NextFile got a file in the wrong order")
	}

	file, err = sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}
	file, err = sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}

	file, err = sf.NextFile()
	if file != nil || err != io.EOF {
		t.Fatal("Expected a nil file and io.EOF")
	}
}

func TestReaderFiles(t *testing.T) {
	message := "beep boop"
	rf := NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader(message)), nil)
	buf := make([]byte, len(message))

	if rf.IsDirectory() {
		t.Fatal("ReaderFile should never be a directory")
	}
	file, err := rf.NextFile()
	if file != nil || err != ErrNotDirectory {
		t.Fatal("Expected a nil file and ErrNotDirectory")
	}

	if n, err := rf.Read(buf); n == 0 || err != nil {
		t.Fatal("Expected to be able to read")
	}
	if err := rf.Close(); err != nil {
		t.Fatal("Should be able to close")
	}
	if n, err := rf.Read(buf); n != 0 || err != io.EOF {
		t.Fatal("Expected EOF when reading after close")
	}
}

func TestMultipartFiles(t *testing.T) {
	data := `
--Boundary!
Content-Type: text/plain
Content-Disposition: file; filename="name"
Some-Header: beep

beep
--Boundary!
Content-Type: application/x-directory
Content-Disposition: file; filename="dir"

--Boundary!--

`

	reader := strings.NewReader(data)
	mpReader := multipart.NewReader(reader, "Boundary!")
	buf := make([]byte, 20)

	// test properties of a file created from the first part
	part, err := mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err := NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if mpf.IsDirectory() {
		t.Fatal("Expected file to not be a directory")
	}
	if mpf.FileName() != "name" {
		t.Fatal("Expected filename to be \"name\"")
	}
	if file, err := mpf.NextFile(); file != nil || err != ErrNotDirectory {
		t.Fatal("Expected a nil file and ErrNotDirectory")
	}
	if n, err := mpf.Read(buf); n != 4 || err != nil {
		t.Fatal("Expected to be able to read 4 bytes")
	}
	if err := mpf.Close(); err != nil {
		t.Fatal("Expected to be able to close file")
	}

	// test properties of file created from second part (directory)
	part, err = mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err = NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if !mpf.IsDirectory() {
		t.Fatal("Expected file to be a directory")
	}
	if mpf.FileName() != "dir" {
		t.Fatal("Expected filename to be \"dir\"")
	}
	if n, err := mpf.Read(buf); n > 0 || err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Read` on a directory")
	}
	if err := mpf.Close(); err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Close` on a directory")
	}

}
<commit_msg>add more tests for multipart parsing<commit_after>package files

import (
	"io"
	"io/ioutil"
	"mime/multipart"
	"strings"
	"testing"
)

func TestSliceFiles(t *testing.T) {
	name := "testname"
	files := []File{
		NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader("Some text!\n")), nil),
		NewReaderFile("beep.txt", "beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil),
		NewReaderFile("boop.txt", "boop.txt", ioutil.NopCloser(strings.NewReader("boop")), nil),
	}
	buf := make([]byte, 20)

	sf := NewSliceFile(name, name, files)

	if !sf.IsDirectory() {
		t.Fatal("SliceFile should always be a directory")
	}

	if n, err := sf.Read(buf); n > 0 || err != io.EOF {
		t.Fatal("Shouldn't be able to read data from a SliceFile")
	}

	if err := sf.Close(); err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Close` on a SliceFile")
	}

	file, err := sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}
	read, err := file.Read(buf)
	if read != 11 || err != nil {
		t.Fatal("NextFile got a file in the wrong order")
	}

	file, err = sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}
	file, err = sf.NextFile()
	if file == nil || err != nil {
		t.Fatal("Expected a file and nil error")
	}

	file, err = sf.NextFile()
	if file != nil || err != io.EOF {
		t.Fatal("Expected a nil file and io.EOF")
	}
}

func TestReaderFiles(t *testing.T) {
	message := "beep boop"
	rf := NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader(message)), nil)
	buf := make([]byte, len(message))

	if rf.IsDirectory() {
		t.Fatal("ReaderFile should never be a directory")
	}
	file, err := rf.NextFile()
	if file != nil || err != ErrNotDirectory {
		t.Fatal("Expected a nil file and ErrNotDirectory")
	}

	if n, err := rf.Read(buf); n == 0 || err != nil {
		t.Fatal("Expected to be able to read")
	}
	if err := rf.Close(); err != nil {
		t.Fatal("Should be able to close")
	}
	if n, err := rf.Read(buf); n != 0 || err != io.EOF {
		t.Fatal("Expected EOF when reading after close")
	}
}

func TestMultipartFiles(t *testing.T) {
	data := `
--Boundary!
Content-Type: text/plain
Content-Disposition: file; filename="name"
Some-Header: beep

beep
--Boundary!
Content-Type: application/x-directory
Content-Disposition: file; filename="dir"

--Boundary!
Content-Type: text/plain
Content-Disposition: file; filename="dir/nested"

some content
--Boundary!
Content-Type: application/symlink
Content-Disposition: file; filename="dir/simlynk"

anotherfile
--Boundary!--

`

	reader := strings.NewReader(data)
	mpReader := multipart.NewReader(reader, "Boundary!")
	buf := make([]byte, 20)

	// test properties of a file created from the first part
	part, err := mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err := NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if mpf.IsDirectory() {
		t.Fatal("Expected file to not be a directory")
	}
	if mpf.FileName() != "name" {
		t.Fatal("Expected filename to be \"name\"")
	}
	if file, err := mpf.NextFile(); file != nil || err != ErrNotDirectory {
		t.Fatal("Expected a nil file and ErrNotDirectory")
	}
	if n, err := mpf.Read(buf); n != 4 || err != nil {
		t.Fatal("Expected to be able to read 4 bytes")
	}
	if err := mpf.Close(); err != nil {
		t.Fatal("Expected to be able to close file")
	}

	// test properties of file created from second part (directory)
	part, err = mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err = NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if !mpf.IsDirectory() {
		t.Fatal("Expected file to be a directory")
	}
	if mpf.FileName() != "dir" {
		t.Fatal("Expected filename to be \"dir\"")
	}
	if n, err := mpf.Read(buf); n > 0 || err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Read` on a directory")
	}
	if err := mpf.Close(); err != ErrNotReader {
		t.Fatal("Shouldn't be able to call `Close` on a directory")
	}

	// test properties of file created from third part (nested file)
	part, err = mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err = NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if mpf.IsDirectory() {
		t.Fatal("Expected file, got directory")
	}
	if mpf.FileName() != "dir/nested" {
		t.Fatalf("Expected filename to be \"nested\", got %s", mpf.FileName())
	}
	if n, err := mpf.Read(buf); n != 12 || err != nil {
		t.Fatalf("expected to be able to read 12 bytes from file: %s (got %d)", err, n)
	}
	if err := mpf.Close(); err != nil {
		t.Fatal("should be able to close file: %s", err)
	}

	// test properties of symlink created from fourth part (symlink)
	part, err = mpReader.NextPart()
	if part == nil || err != nil {
		t.Fatal("Expected non-nil part, nil error")
	}
	mpf, err = NewFileFromPart(part)
	if mpf == nil || err != nil {
		t.Fatal("Expected non-nil MultipartFile, nil error")
	}
	if mpf.IsDirectory() {
		t.Fatal("Expected file to be a symlink")
	}
	if mpf.FileName() != "dir/simlynk" {
		t.Fatal("Expected filename to be \"dir/simlynk\"")
	}
	slink, ok := mpf.(*Symlink)
	if !ok {
		t.Fatalf("expected file to be a symlink")
	}
	if slink.Target != "anotherfile" {
		t.Fatal("expected link to point to anotherfile")
	}
}
<|endoftext|>


<commit_before>/*
Copyright IBM Corp. 2016 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 cauthdsl

import (
	"fmt"
	"time"

	"github.com/hyperledger/fabric/common/flogging"
	"github.com/hyperledger/fabric/msp"
	cb "github.com/hyperledger/fabric/protos/common"
	mb "github.com/hyperledger/fabric/protos/msp"

	"github.com/op/go-logging"
)

var cauthdslLogger = flogging.MustGetLogger("cauthdsl")
var deserializedIdentites = make(map[string]msp.Identity)

// compile recursively builds a go evaluatable function corresponding to the policy specified
func compile(policy *cb.SignaturePolicy, identities []*mb.MSPPrincipal, deserializer msp.IdentityDeserializer) (func([]*cb.SignedData, []bool) bool, error) {
	if policy == nil {
		return nil, fmt.Errorf("Empty policy element")
	}

	switch t := policy.Type.(type) {
	case *cb.SignaturePolicy_NOutOf_:
		policies := make([]func([]*cb.SignedData, []bool) bool, len(t.NOutOf.Rules))
		for i, policy := range t.NOutOf.Rules {
			compiledPolicy, err := compile(policy, identities, deserializer)
			if err != nil {
				return nil, err
			}
			policies[i] = compiledPolicy

		}
		return func(signedData []*cb.SignedData, used []bool) bool {
			grepKey := time.Now().UnixNano()
			cauthdslLogger.Debugf("%p gate %d evaluation starts", signedData, grepKey)
			verified := int32(0)
			_used := make([]bool, len(used))
			for _, policy := range policies {
				copy(_used, used)
				if policy(signedData, _used) {
					verified++
					if verified >= t.NOutOf.N {
						break
					}
					copy(used, _used)
				}
			}

			if verified >= t.NOutOf.N {
				cauthdslLogger.Debugf("%p gate %d evaluation succeeds", signedData, grepKey)
			} else {
				cauthdslLogger.Debugf("%p gate %d evaluation fails", signedData, grepKey)
			}

			return verified >= t.NOutOf.N
		}, nil
	case *cb.SignaturePolicy_SignedBy:
		if t.SignedBy < 0 || t.SignedBy >= int32(len(identities)) {
			return nil, fmt.Errorf("identity index out of range, requested %v, but identies length is %d", t.SignedBy, len(identities))
		}
		signedByID := identities[t.SignedBy]
		return func(signedData []*cb.SignedData, used []bool) bool {
			cauthdslLogger.Debugf("%p signed by %d principal evaluation starts (used %v)", signedData, t.SignedBy, used)
			for i, sd := range signedData {
				if used[i] {
					cauthdslLogger.Debugf("%p skipping identity %d because it has already been used", signedData, i)
					continue
				}
				strID := string(sd.Identity)
				var identity msp.Identity
				var err error
				identity, ok := deserializedIdentites[strID]
				if !ok {
					identity, err = deserializer.DeserializeIdentity(sd.Identity)
					if err != nil {
						cauthdslLogger.Errorf("Principal deserialization failed: (%s) for identity %v", err, sd.Identity)
						continue
					}
					deserializedIdentites[strID] = identity
				}

				err = identity.SatisfiesPrincipal(signedByID)
				if err != nil {
					cauthdslLogger.Debugf("%p identity %d does not satisfy principal: %s", signedData, i, err)
					continue
				}
				cauthdslLogger.Debugf("%p principal matched by identity %d", signedData, i)
				err = identity.Verify(sd.Data, sd.Signature)
				if err != nil {
					cauthdslLogger.Debugf("%p signature for identity %d is invalid: %s", signedData, i, err)
					continue
				}
				cauthdslLogger.Debugf("%p principal evaluation succeeds for identity %d", signedData, i)
				used[i] = true
				return true
			}
			cauthdslLogger.Debugf("%p principal evaluation fails", signedData)
			return false
		}, nil
	default:
		return nil, fmt.Errorf("Unknown type: %T:%v", t, t)
	}
}
<commit_msg>Revert "Optimize cauthdsl - cache identities" Switching off this optimization temporarily<commit_after>/*
Copyright IBM Corp. 2016 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 cauthdsl

import (
	"fmt"
	"time"

	"github.com/hyperledger/fabric/common/flogging"
	"github.com/hyperledger/fabric/msp"
	cb "github.com/hyperledger/fabric/protos/common"
	mb "github.com/hyperledger/fabric/protos/msp"

	"github.com/op/go-logging"
)

var cauthdslLogger = flogging.MustGetLogger("cauthdsl")

// compile recursively builds a go evaluatable function corresponding to the policy specified
func compile(policy *cb.SignaturePolicy, identities []*mb.MSPPrincipal, deserializer msp.IdentityDeserializer) (func([]*cb.SignedData, []bool) bool, error) {
	if policy == nil {
		return nil, fmt.Errorf("Empty policy element")
	}

	switch t := policy.Type.(type) {
	case *cb.SignaturePolicy_NOutOf_:
		policies := make([]func([]*cb.SignedData, []bool) bool, len(t.NOutOf.Rules))
		for i, policy := range t.NOutOf.Rules {
			compiledPolicy, err := compile(policy, identities, deserializer)
			if err != nil {
				return nil, err
			}
			policies[i] = compiledPolicy

		}
		return func(signedData []*cb.SignedData, used []bool) bool {
			grepKey := time.Now().UnixNano()
			cauthdslLogger.Debugf("%p gate %d evaluation starts", signedData, grepKey)
			verified := int32(0)
			_used := make([]bool, len(used))
			for _, policy := range policies {
				copy(_used, used)
				if policy(signedData, _used) {
					verified++
					copy(used, _used)
				}
			}

			if verified >= t.NOutOf.N {
				cauthdslLogger.Debugf("%p gate %d evaluation succeeds", signedData, grepKey)
			} else {
				cauthdslLogger.Debugf("%p gate %d evaluation fails", signedData, grepKey)
			}

			return verified >= t.NOutOf.N
		}, nil
	case *cb.SignaturePolicy_SignedBy:
		if t.SignedBy < 0 || t.SignedBy >= int32(len(identities)) {
			return nil, fmt.Errorf("identity index out of range, requested %v, but identies length is %d", t.SignedBy, len(identities))
		}
		signedByID := identities[t.SignedBy]
		return func(signedData []*cb.SignedData, used []bool) bool {
			cauthdslLogger.Debugf("%p signed by %d principal evaluation starts (used %v)", signedData, t.SignedBy, used)
			for i, sd := range signedData {
				if used[i] {
					cauthdslLogger.Debugf("%p skipping identity %d because it has already been used", signedData, i)
					continue
				}
				identity, err := deserializer.DeserializeIdentity(sd.Identity)
				if err != nil {
					cauthdslLogger.Errorf("Principal deserialization failed: (%s) for identity %v", err, sd.Identity)
					continue
				}
				err = identity.SatisfiesPrincipal(signedByID)
				if err != nil {
					cauthdslLogger.Debugf("%p identity %d does not satisfy principal: %s", signedData, i, err)
					continue
				}
				cauthdslLogger.Debugf("%p principal matched by identity %d", signedData, i)
				err = identity.Verify(sd.Data, sd.Signature)
				if err != nil {
					cauthdslLogger.Debugf("%p signature for identity %d is invalid: %s", signedData, i, err)
					continue
				}
				cauthdslLogger.Debugf("%p principal evaluation succeeds for identity %d", signedData, i)
				used[i] = true
				return true
			}
			cauthdslLogger.Debugf("%p principal evaluation fails", signedData)
			return false
		}, nil
	default:
		return nil, fmt.Errorf("Unknown type: %T:%v", t, t)
	}
}
<|endoftext|>


<commit_before>// Copyright 2015 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package clock

import (
	"time"

	"golang.org/x/net/context"
)

type systemTimer struct {
	// ctx is the underlying timer. It starts as nil, and is initialized on Reset.
	ctx context.Context

	// timerC is the timer channel.
	timerC chan TimerResult

	// timerStoppedC is a signal channel used by Stop to alert our monitor that
	// this timer has been manually canceled.
	timerStoppedC chan struct{}
	// timerMonitorResultC returns true if the timer monitor was prematurely
	// terminated, false if not. It is used by our timer monitor to indicate its
	// status when stopped.
	timerMonitorResultC chan bool
}

var _ Timer = (*systemTimer)(nil)

func newSystemTimer(ctx context.Context) Timer {
	return &systemTimer{
		ctx:    ctx,
		timerC: make(chan TimerResult, 1),
	}
}

func (t *systemTimer) GetC() <-chan TimerResult {
	return t.timerC
}

func (t *systemTimer) Reset(d time.Duration) (running bool) {
	running = t.Stop()

	// If our Context is already done, finish immediately.
	if err := t.ctx.Err(); err != nil {
		t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
		return
	}

	// Start a monitor goroutine and our actual timer. Copy our channels, since
	// future stop/reset will change the systemTimer's values and our goroutine
	// should only operate on this round's values.
	t.timerStoppedC = make(chan struct{})
	t.timerMonitorResultC = make(chan bool, 1)

	timerStoppedC, timerMonitorResultC := t.timerStoppedC, t.timerMonitorResultC

	realTimer := time.NewTimer(d)
	go func() {
		defer realTimer.Stop()

		interrupted := false
		defer func() {
			timerMonitorResultC <- interrupted
			close(timerMonitorResultC)
		}()

		select {
		case <-timerStoppedC:
			interrupted = true

		case <-t.ctx.Done():
			t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
		case now := <-realTimer.C:
			t.timerC <- TimerResult{Time: now, Err: t.ctx.Err()}
		}
	}()

	return
}

func (t *systemTimer) Stop() bool {
	if t.timerStoppedC == nil {
		return false
	}
	close(t.timerStoppedC)
	t.timerStoppedC = nil
	return <-t.timerMonitorResultC
}
<commit_msg>Fix system timer context logic.<commit_after>// Copyright 2015 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package clock

import (
	"time"

	"golang.org/x/net/context"
)

type systemTimer struct {
	// ctx is the underlying timer. It starts as nil, and is initialized on Reset.
	ctx context.Context

	// timerC is the timer channel.
	timerC chan TimerResult

	// timerStoppedC is a signal channel used by Stop to alert our monitor that
	// this timer has been manually canceled.
	timerStoppedC chan struct{}
	// timerMonitorResultC returns true if the timer monitor was prematurely
	// terminated, false if not. It is used by our timer monitor to indicate its
	// status when stopped.
	timerMonitorResultC chan bool
}

var _ Timer = (*systemTimer)(nil)

func newSystemTimer(ctx context.Context) Timer {
	return &systemTimer{
		ctx:    ctx,
		timerC: make(chan TimerResult, 1),
	}
}

func (t *systemTimer) GetC() <-chan TimerResult {
	return t.timerC
}

func (t *systemTimer) Reset(d time.Duration) (running bool) {
	running = t.Stop()

	// If our Context is already done, finish immediately.
	if done := t.ctx.Done(); done != nil {
		select {
		case <-done:
			t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
			return
		default:
		}
	}

	// Start a monitor goroutine and our actual timer. Copy our channels, since
	// future stop/reset will change the systemTimer's values and our goroutine
	// should only operate on this round's values.
	t.timerStoppedC = make(chan struct{})
	t.timerMonitorResultC = make(chan bool, 1)

	timerStoppedC, timerMonitorResultC := t.timerStoppedC, t.timerMonitorResultC

	realTimer := time.NewTimer(d)
	go func() {
		defer realTimer.Stop()

		interrupted := false
		defer func() {
			timerMonitorResultC <- interrupted
			close(timerMonitorResultC)
		}()

		select {
		case <-timerStoppedC:
			interrupted = true

		case <-t.ctx.Done():
			t.timerC <- TimerResult{Time: time.Now(), Err: t.ctx.Err()}
		case now := <-realTimer.C:
			t.timerC <- TimerResult{Time: now, Err: nil}
		}
	}()

	return
}

func (t *systemTimer) Stop() bool {
	if t.timerStoppedC == nil {
		return false
	}
	close(t.timerStoppedC)
	t.timerStoppedC = nil
	return <-t.timerMonitorResultC
}
<|endoftext|>


<commit_before>package dao

import (
	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
	"auto-ng/config"
)

type  Template struct {
	Id          int
	ProjectName string `orm:"size(50)"`
	Content     string `orm:"size(200)"`
}

var db *config.DBconfig

func init() {

	db = config.GetDBconfig()

	orm.RegisterDriver("mysql", orm.DRMySQL)

	orm.RegisterDataBase(db.Alias, db.DriverName, db.DataSource, db.Conns)

	orm.RegisterModel(new(Template))

	orm.RunSyncdb(db.Alias, false, true)
}

func QueryById() {

}

func Insert() {

}

func DeleteById() {

}

func Update() {

}<commit_msg>dao OK<commit_after>package dao

import (
	"github.com/astaxie/beego/orm"
	_ "github.com/go-sql-driver/mysql"
	"auto-ng/config"
)

type Template struct {
	Id          int
	ProjectName string `orm:"size(50)"`
	Content     string `orm:"size(200)"`
}

var db *config.DBconfig

var ormer orm.Ormer

func init() {

	db = config.GetDBconfig()

	orm.RegisterDriver("mysql", orm.DRMySQL)

	orm.RegisterDataBase(db.Alias, db.DriverName, db.DataSource, db.Conns)

	orm.RegisterModel(new(Template))

	orm.RunSyncdb(db.Alias, false, true)

	ormer = orm.NewOrm()
}

func QueryById(id int) Template {
	t := Template{Id: id}
	err := ormer.Read(&t)
	if err != nil {
		panic(err)
	}
	return t
}

func Insert(tmpl *Template) int {
	id, err := ormer.Insert(&tmpl)
	if err != nil {
		panic(err)
	}
	return id
}

func DeleteById(id int) bool {
	t := Template{Id: id}
	num, err := ormer.Delete(&t)
	if err != nil {
		panic(err)
	}
	return num == 1
}

func Update(tmpl *Template) bool {
	num, err := ormer.Update(&tmpl)
	if err != nil {
		panic(err)
	}
	return num == 1
}<|endoftext|>


<commit_before>package db

import (
	"bufio"
	"errors"
	"io"
	"net"
	"strings"

	"github.com/PuerkitoBio/gred/resp"
)

var (
	// ok is the standard OK serialized return value, to avoid allocation for
	// this common case.
	ok = []byte("+OK\r\n")

	// emptyArray is the unique empty array value used to return empty lists
	// for this common case.
	emptyArray = resp.Array{}

	// defaultDb is the default database used by all new connections (db 0).
	defaultDb = NewDB(0)

	// errInvalidCommand is returned when a malformed command is received.
	errInvalidCommand = errors.New("db: invalid command")

	// errNilSuccess is a sentinel value to indicate the success of a command,
	// and that the nil value should be returned.
	errNilSuccess = errors.New("db: (nil)")

	// errInvalidKeyType is the error returned when the key is not the right
	// type for the command to execute.
	errInvalidKeyType = errors.New("db: invalid key type")
)

// Conn represents a network connection to the server.
type Conn struct {
	c    net.Conn
	db   *Database
	ctx  *Ctx
	quit bool
}

// NewConn creates a new Conn for the underlying net.Conn network
// connection.
func NewConn(c net.Conn) *Conn {
	conn := &Conn{
		c:   c,
		db:  defaultDb,
		ctx: &Ctx{},
	}
	conn.ctx.conn = conn
	return conn
}

// Handle handles a connection to the server, and processes its requests.
func (c *Conn) Handle() error {
	defer c.c.Close()

	br := bufio.NewReader(c.c)

	for {
		// Get the request
		ar, err := resp.DecodeRequest(br)
		if err != nil {
			// Network error, return
			if err == io.EOF || err == io.ErrClosedPipe {
				return err
			}
			// Write the error to the client
			err = resp.Encode(c.c, resp.Error(err.Error()))
			if err != nil {
				// If write failed, return
				return errors.New("db.Conn.Handle: encode failed: " + err.Error())
			}
			continue
		}

		// Run the command
		err = c.do(ar[0], ar[1:]...)
		if err != nil {
			return err
		}
		if c.quit {
			// Quit command, asked to close connection.
			return nil
		}
	}
}

// Do executes a given command on the connection.
func (c *Conn) do(cmd string, args ...string) error {
	var res interface{}
	var err error

	// Prepare the command and context
	cmd = strings.ToLower(cmd)
	c.ctx.db = c.db
	c.ctx.raw = args

	// Excecute the command
	switch cmd {
	// Connection commands
	case "echo":
		res, err = cmdEcho(c.ctx)
	case "ping":
		res, err = cmdPing(c.ctx)
	case "quit":
		res, err = cmdQuit(c.ctx)
		if err == nil {
			c.quit = true
		}

		// Keys commands
	case "del":
		res, err = cmdDel(c.ctx)
	case "exists":
		res, err = cmdExists(c.ctx)
	case "expire":
		res, err = cmdExpire(c.ctx)
	case "persist":
		res, err = cmdPersist(c.ctx)
	case "ttl":
		res, err = cmdTTL(c.ctx)

		// Strings commands
	case "append":
		res, err = cmdAppend(c.ctx)
	case "get":
		res, err = cmdGet(c.ctx)
	case "getrange":
		res, err = cmdGetRange(c.ctx)
	case "getset":
		res, err = cmdGetSet(c.ctx)
	case "set":
		res, err = cmdSet(c.ctx)
	case "strlen":
		res, err = cmdStrLen(c.ctx)

		// Hashes commands
	case "hdel":
		res, err = cmdHdel(c.ctx)
	case "hexists":
		res, err = cmdHexists(c.ctx)
	case "hget":
		res, err = cmdHget(c.ctx)
	case "hgetall":
		res, err = cmdHgetAll(c.ctx)
	case "hkeys":
		res, err = cmdHkeys(c.ctx)
	case "hlen":
		res, err = cmdHlen(c.ctx)
	case "hmget":
		res, err = cmdHmget(c.ctx)
	case "hset":
		res, err = cmdHset(c.ctx)
	case "hvals":
		res, err = cmdHvals(c.ctx)

	default:
		err = errInvalidCommand
	}
	return c.writeResponse(res, err)
}

// writeResponse writes the response to the network connection.
func (c *Conn) writeResponse(res interface{}, err error) error {
	switch err {
	case errNilSuccess:
		// Special-case for success but nil return value
		return resp.Encode(c.c, nil)

	case errPong:
		// Special-case for pong response
		_, err = c.c.Write(pong)
		return err

	case nil:
		if res == nil {
			// If the result is nil, send the OK response
			_, err = c.c.Write(ok)
			return err
		}
		// Otherwise encode the response
		return resp.Encode(c.c, res)

	default:
		// Return the non-nil error
		return resp.Encode(c.c, resp.Error(err.Error()))
	}
}
<commit_msg>buffered writer shows no real advantage<commit_after>package db

import (
	"bufio"
	"errors"
	"io"
	"net"
	"strings"

	"github.com/PuerkitoBio/gred/resp"
)

var (
	// ok is the standard OK serialized return value, to avoid allocation for
	// this common case.
	ok = []byte("+OK\r\n")

	// emptyArray is the unique empty array value used to return empty lists
	// for this common case.
	emptyArray = resp.Array{}

	// defaultDb is the default database used by all new connections (db 0).
	defaultDb = NewDB(0)

	// errInvalidCommand is returned when a malformed command is received.
	errInvalidCommand = errors.New("db: invalid command")

	// errNilSuccess is a sentinel value to indicate the success of a command,
	// and that the nil value should be returned.
	errNilSuccess = errors.New("db: (nil)")

	// errInvalidKeyType is the error returned when the key is not the right
	// type for the command to execute.
	errInvalidKeyType = errors.New("db: invalid key type")
)

// Conn represents a network connection to the server.
type Conn struct {
	c net.Conn

	db   *Database
	ctx  *Ctx
	quit bool
}

// NewConn creates a new Conn for the underlying net.Conn network
// connection.
func NewConn(c net.Conn) *Conn {
	conn := &Conn{
		c:   c,
		db:  defaultDb,
		ctx: &Ctx{},
	}
	conn.ctx.conn = conn
	return conn
}

// Handle handles a connection to the server, and processes its requests.
func (c *Conn) Handle() error {
	defer c.c.Close()

	br := bufio.NewReader(c.c)

	for {
		// Get the request
		ar, err := resp.DecodeRequest(br)
		if err != nil {
			// Network error, return
			if err == io.EOF || err == io.ErrClosedPipe {
				return err
			}
			// Write the error to the client
			err = resp.Encode(c.c, resp.Error(err.Error()))
			if err != nil {
				// If write failed, return
				return errors.New("db.Conn.Handle: encode failed: " + err.Error())
			}
			continue
		}

		// Run the command
		err = c.do(ar[0], ar[1:]...)
		if err != nil {
			return err
		}
		if c.quit {
			// Quit command, asked to close connection.
			return nil
		}
	}
}

// Do executes a given command on the connection.
func (c *Conn) do(cmd string, args ...string) error {
	var res interface{}
	var err error

	// Prepare the command and context
	cmd = strings.ToLower(cmd)
	c.ctx.db = c.db
	c.ctx.raw = args

	// Excecute the command
	switch cmd {
	// Connection commands
	case "echo":
		res, err = cmdEcho(c.ctx)
	case "ping":
		res, err = cmdPing(c.ctx)
	case "quit":
		res, err = cmdQuit(c.ctx)
		if err == nil {
			c.quit = true
		}

		// Keys commands
	case "del":
		res, err = cmdDel(c.ctx)
	case "exists":
		res, err = cmdExists(c.ctx)
	case "expire":
		res, err = cmdExpire(c.ctx)
	case "persist":
		res, err = cmdPersist(c.ctx)
	case "ttl":
		res, err = cmdTTL(c.ctx)

		// Strings commands
	case "append":
		res, err = cmdAppend(c.ctx)
	case "get":
		res, err = cmdGet(c.ctx)
	case "getrange":
		res, err = cmdGetRange(c.ctx)
	case "getset":
		res, err = cmdGetSet(c.ctx)
	case "set":
		res, err = cmdSet(c.ctx)
	case "strlen":
		res, err = cmdStrLen(c.ctx)

		// Hashes commands
	case "hdel":
		res, err = cmdHdel(c.ctx)
	case "hexists":
		res, err = cmdHexists(c.ctx)
	case "hget":
		res, err = cmdHget(c.ctx)
	case "hgetall":
		res, err = cmdHgetAll(c.ctx)
	case "hkeys":
		res, err = cmdHkeys(c.ctx)
	case "hlen":
		res, err = cmdHlen(c.ctx)
	case "hmget":
		res, err = cmdHmget(c.ctx)
	case "hset":
		res, err = cmdHset(c.ctx)
	case "hvals":
		res, err = cmdHvals(c.ctx)

	default:
		err = errInvalidCommand
	}
	return c.writeResponse(res, err)
}

// writeResponse writes the response to the network connection.
func (c *Conn) writeResponse(res interface{}, err error) error {
	switch err {
	case errNilSuccess:
		// Special-case for success but nil return value
		return resp.Encode(c.c, nil)

	case errPong:
		// Special-case for pong response
		_, err = c.c.Write(pong)
		return err

	case nil:
		if res == nil {
			// If the result is nil, send the OK response
			_, err = c.c.Write(ok)
			return err
		}
		// Otherwise encode the response
		return resp.Encode(c.c, res)

	default:
		// Return the non-nil error
		return resp.Encode(c.c, resp.Error(err.Error()))
	}
}
<|endoftext|>


<commit_before>package xorm_ext

import (
	"github.com/go-xorm/xorm"

	. "github.com/gogap/xorm_ext/errorcode"
)

const (
	REPO_DEFAULT_ENGINE               = "default"
	REPO_ERR_DEFAULT_ENGINE_NOT_FOUND = "`default` xorm engine not found"
)

type TXFunc func(repos []interface{}) (err error)

type DBRepo struct {
	isTransaction bool
	engines       map[string]*xorm.Engine
	defaultEngine *xorm.Engine
	txSession     *xorm.Session
}

func (p *DBRepo) SetEngines(ormEngines map[string]*xorm.Engine) {
	if defaultEngine, exist := ormEngines[REPO_DEFAULT_ENGINE]; exist {
		p.engines = ormEngines
		p.defaultEngine = defaultEngine
	} else {
		panic(REPO_ERR_DEFAULT_ENGINE_NOT_FOUND)
	}
}

func (p *DBRepo) Engines() map[string]*xorm.Engine {
	return p.engines
}

func (p *DBRepo) DefaultEngine() *xorm.Engine {
	return p.defaultEngine
}

func (p *DBRepo) IsTransaction() bool {
	return p.isTransaction
}

func (p *DBRepo) beginTransaction(engineName string) error {
	if p.isTransaction == false {
		p.isTransaction = true
		p.txSession = p.SessionUsing(engineName)
		if p.txSession == nil {
			p.txSession = p.defaultEngine.NewSession()
		}
	} else {
		ERR_DB_TX_ALREADY_BEGINED.New()
	}
	return nil
}

func (p *DBRepo) commitNoTransaction(txFunc TXFunc, engineName string, repos ...interface{}) (err error) {
	if p.isTransaction {
		return ERR_DB_IS_A_TX.New()
	}

	p.txSession = p.SessionUsing(engineName)

	if p.txSession == nil {
		return ERR_DB_SESSION_IS_NIL.New()
	}

	if txFunc == nil {
		return ERR_DB_TX_NOFUNC.New()
	}

	if e := txFunc(repos); e != nil {
		return e
	}

	return
}

func (p *DBRepo) commitTransaction(txFunc TXFunc, repos ...interface{}) (err error) {
	if !p.isTransaction {
		return ERR_DB_NOT_A_TX.New()
	}

	if p.txSession == nil {
		return ERR_DB_SESSION_IS_NIL.New()
	}

	if txFunc == nil {
		return ERR_DB_TX_NOFUNC.New()
	}

	isNeedRollBack := true
	session := p.txSession

	if session.Begin() != nil {
		return ERR_DB_TX_CANNOT_BEGIN.New()
	}

	defer func() {
		if isNeedRollBack == true {
			session.Rollback()
		}
		return
	}()

	if e := txFunc(repos); e != nil {
		return e
	}

	isNeedRollBack = false
	if e := session.Commit(); e != nil {
		return ERR_DB_TX_COMMIT_ERROR.New()
	}
	return
}

func (p *DBRepo) Session() *xorm.Session {
	if p.isTransaction &&
		p.txSession != nil {
		return p.txSession
	}
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) NewSession() *xorm.Session {
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) SessionUsing(engineName string) *xorm.Session {
	if engine, exist := p.engines[engineName]; exist {
		return engine.NewSession()
	}
	return nil
}
<commit_msg>fix stmt close() issue<commit_after>package xorm_ext

import (
	"github.com/go-xorm/xorm"

	. "github.com/gogap/xorm_ext/errorcode"
)

const (
	REPO_DEFAULT_ENGINE               = "default"
	REPO_ERR_DEFAULT_ENGINE_NOT_FOUND = "`default` xorm engine not found"
)

type TXFunc func(repos []interface{}) (err error)

type DBRepo struct {
	isTransaction bool
	engines       map[string]*xorm.Engine
	defaultEngine *xorm.Engine
	txSession     *xorm.Session
}

func (p *DBRepo) SetEngines(ormEngines map[string]*xorm.Engine) {
	if defaultEngine, exist := ormEngines[REPO_DEFAULT_ENGINE]; exist {
		p.engines = ormEngines
		p.defaultEngine = defaultEngine
	} else {
		panic(REPO_ERR_DEFAULT_ENGINE_NOT_FOUND)
	}
}

func (p *DBRepo) Engines() map[string]*xorm.Engine {
	return p.engines
}

func (p *DBRepo) DefaultEngine() *xorm.Engine {
	return p.defaultEngine
}

func (p *DBRepo) IsTransaction() bool {
	return p.isTransaction
}

func (p *DBRepo) beginTransaction(engineName string) error {
	if p.isTransaction == false {
		p.isTransaction = true
		p.txSession = p.SessionUsing(engineName)
		if p.txSession == nil {
			p.txSession = p.defaultEngine.NewSession()
		}
	} else {
		ERR_DB_TX_ALREADY_BEGINED.New()
	}
	return nil
}

func (p *DBRepo) commitNoTransaction(txFunc TXFunc, engineName string, repos ...interface{}) (err error) {
	if p.isTransaction {
		return ERR_DB_IS_A_TX.New()
	}

	p.txSession = p.SessionUsing(engineName)

	if p.txSession == nil {
		return ERR_DB_SESSION_IS_NIL.New()
	}

	defer p.txSession.Close()

	if txFunc == nil {
		return ERR_DB_TX_NOFUNC.New()
	}

	if e := txFunc(repos); e != nil {
		return e
	}

	return
}

func (p *DBRepo) commitTransaction(txFunc TXFunc, repos ...interface{}) (err error) {
	if !p.isTransaction {
		return ERR_DB_NOT_A_TX.New()
	}

	if p.txSession == nil {
		return ERR_DB_SESSION_IS_NIL.New()
	}

	if txFunc == nil {
		return ERR_DB_TX_NOFUNC.New()
	}

	isNeedRollBack := true
	session := p.txSession

	defer session.Close()

	if session.Begin() != nil {
		return ERR_DB_TX_CANNOT_BEGIN.New()
	}

	defer func() {
		if isNeedRollBack == true {
			session.Rollback()
		}
		return
	}()

	if e := txFunc(repos); e != nil {
		return e
	}

	isNeedRollBack = false
	if e := session.Commit(); e != nil {
		return ERR_DB_TX_COMMIT_ERROR.New()
	}
	return
}

func (p *DBRepo) Session() *xorm.Session {
	if p.isTransaction &&
		p.txSession != nil {
		return p.txSession
	}
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) NewSession() *xorm.Session {
	return p.defaultEngine.NewSession()
}

func (p *DBRepo) SessionUsing(engineName string) *xorm.Session {
	if engine, exist := p.engines[engineName]; exist {
		return engine.NewSession()
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases.  It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp

package gorp_test

import (
	"testing"
)

type customType1 []string

func (c customType1) ToStringSlice() []string {
	return []string(c)
}

type customType2 []int64

func (c customType2) ToInt64Slice() []int64 {
	return []int64(c)
}

func TestDbMap_Select_expandSliceArgs(t *testing.T) {
	tests := []struct {
		description string
		query       string
		args        []interface{}
		wantLen     int
	}{
		{
			description: "it should handle slice placeholders correctly",
			query: `
SELECT 1 FROM crazy_table
WHERE field1 = :Field1
AND field2 IN (:FieldStringList)
AND field3 IN (:FieldUIntList)
AND field4 IN (:FieldUInt8List)
AND field5 IN (:FieldUInt16List)
AND field6 IN (:FieldUInt32List)
AND field7 IN (:FieldUInt64List)
AND field8 IN (:FieldIntList)
AND field9 IN (:FieldInt8List)
AND field10 IN (:FieldInt16List)
AND field11 IN (:FieldInt32List)
AND field12 IN (:FieldInt64List)
AND field13 IN (:FieldFloat32List)
AND field14 IN (:FieldFloat64List)
`,
			args: []interface{}{
				map[string]interface{}{
					"Field1":           123,
					"FieldStringList":  []string{"h", "e", "y"},
					"FieldUIntList":    []uint{1, 2, 3, 4},
					"FieldUInt8List":   []uint8{1, 2, 3, 4},
					"FieldUInt16List":  []uint16{1, 2, 3, 4},
					"FieldUInt32List":  []uint32{1, 2, 3, 4},
					"FieldUInt64List":  []uint64{1, 2, 3, 4},
					"FieldIntList":     []int{1, 2, 3, 4},
					"FieldInt8List":    []int8{1, 2, 3, 4},
					"FieldInt16List":   []int16{1, 2, 3, 4},
					"FieldInt32List":   []int32{1, 2, 3, 4},
					"FieldInt64List":   []int64{1, 2, 3, 4},
					"FieldFloat32List": []float32{1, 2, 3, 4},
					"FieldFloat64List": []float64{1, 2, 3, 4},
				},
			},
			wantLen: 1,
		},
		{
			description: "it should handle slice placeholders correctly with custom types",
			query: `
SELECT 1 FROM crazy_table
WHERE field2 IN (:FieldStringList)
AND field12 IN (:FieldIntList)
`,
			args: []interface{}{
				map[string]interface{}{
					"FieldStringList": customType1{"h", "e", "y"},
					"FieldIntList":    customType2{1, 2, 3, 4},
				},
			},
			wantLen: 3,
		},
	}

	type dataFormat struct {
		Field1  int
		Field2  string
		Field3  uint
		Field4  uint8
		Field5  uint16
		Field6  uint32
		Field7  uint64
		Field8  int
		Field9  int8
		Field10 int16
		Field11 int32
		Field12 int64
		Field13 float32
		Field14 float64
	}

	dbmap := newDbMap()
	dbmap.ExpandSliceArgs = true
	dbmap.AddTableWithName(dataFormat{}, "crazy_table")

	err := dbmap.CreateTables()
	if err != nil {
		panic(err)
	}
	defer dropAndClose(dbmap)

	err = dbmap.Insert(
		&dataFormat{
			Field1:  123,
			Field2:  "h",
			Field3:  1,
			Field4:  1,
			Field5:  1,
			Field6:  1,
			Field7:  1,
			Field8:  1,
			Field9:  1,
			Field10: 1,
			Field11: 1,
			Field12: 1,
			Field13: 1,
			Field14: 1,
		},
		&dataFormat{
			Field1:  124,
			Field2:  "e",
			Field3:  2,
			Field4:  2,
			Field5:  2,
			Field6:  2,
			Field7:  2,
			Field8:  2,
			Field9:  2,
			Field10: 2,
			Field11: 2,
			Field12: 2,
			Field13: 2,
			Field14: 2,
		},
		&dataFormat{
			Field1:  125,
			Field2:  "y",
			Field3:  3,
			Field4:  3,
			Field5:  3,
			Field6:  3,
			Field7:  3,
			Field8:  3,
			Field9:  3,
			Field10: 3,
			Field11: 3,
			Field12: 3,
			Field13: 3,
			Field14: 3,
		},
	)

	if err != nil {
		t.Fatal(err)
	}

	for _, tt := range tests {
		t.Run(tt.description, func(t *testing.T) {
			var dummy []int
			_, err := dbmap.Select(&dummy, tt.query, tt.args...)
			if err != nil {
				t.Fatal(err)
			}

			if len(dummy) != tt.wantLen {
				t.Errorf("wrong result count\ngot:  %d\nwant: %d", len(dummy), tt.wantLen)
			}
		})
	}
}
<commit_msg>Set column names in test<commit_after>// Copyright 2012 James Cooper. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

// Package gorp provides a simple way to marshal Go structs to and from
// SQL databases.  It uses the database/sql package, and should work with any
// compliant database/sql driver.
//
// Source code and project home:
// https://github.com/go-gorp/gorp

package gorp_test

import (
	"testing"
)

type customType1 []string

func (c customType1) ToStringSlice() []string {
	return []string(c)
}

type customType2 []int64

func (c customType2) ToInt64Slice() []int64 {
	return []int64(c)
}

func TestDbMap_Select_expandSliceArgs(t *testing.T) {
	tests := []struct {
		description string
		query       string
		args        []interface{}
		wantLen     int
	}{
		{
			description: "it should handle slice placeholders correctly",
			query: `
SELECT 1 FROM crazy_table
WHERE field1 = :Field1
AND field2 IN (:FieldStringList)
AND field3 IN (:FieldUIntList)
AND field4 IN (:FieldUInt8List)
AND field5 IN (:FieldUInt16List)
AND field6 IN (:FieldUInt32List)
AND field7 IN (:FieldUInt64List)
AND field8 IN (:FieldIntList)
AND field9 IN (:FieldInt8List)
AND field10 IN (:FieldInt16List)
AND field11 IN (:FieldInt32List)
AND field12 IN (:FieldInt64List)
AND field13 IN (:FieldFloat32List)
AND field14 IN (:FieldFloat64List)
`,
			args: []interface{}{
				map[string]interface{}{
					"Field1":           123,
					"FieldStringList":  []string{"h", "e", "y"},
					"FieldUIntList":    []uint{1, 2, 3, 4},
					"FieldUInt8List":   []uint8{1, 2, 3, 4},
					"FieldUInt16List":  []uint16{1, 2, 3, 4},
					"FieldUInt32List":  []uint32{1, 2, 3, 4},
					"FieldUInt64List":  []uint64{1, 2, 3, 4},
					"FieldIntList":     []int{1, 2, 3, 4},
					"FieldInt8List":    []int8{1, 2, 3, 4},
					"FieldInt16List":   []int16{1, 2, 3, 4},
					"FieldInt32List":   []int32{1, 2, 3, 4},
					"FieldInt64List":   []int64{1, 2, 3, 4},
					"FieldFloat32List": []float32{1, 2, 3, 4},
					"FieldFloat64List": []float64{1, 2, 3, 4},
				},
			},
			wantLen: 1,
		},
		{
			description: "it should handle slice placeholders correctly with custom types",
			query: `
SELECT 1 FROM crazy_table
WHERE field2 IN (:FieldStringList)
AND field12 IN (:FieldIntList)
`,
			args: []interface{}{
				map[string]interface{}{
					"FieldStringList": customType1{"h", "e", "y"},
					"FieldIntList":    customType2{1, 2, 3, 4},
				},
			},
			wantLen: 3,
		},
	}

	type dataFormat struct {
		Field1  int     `db:"field1"`
		Field2  string  `db:"field2"`
		Field3  uint    `db:"field3"`
		Field4  uint8   `db:"field4"`
		Field5  uint16  `db:"field5"`
		Field6  uint32  `db:"field6"`
		Field7  uint64  `db:"field7"`
		Field8  int     `db:"field8"`
		Field9  int8    `db:"field9"`
		Field10 int16   `db:"field10"`
		Field11 int32   `db:"field11"`
		Field12 int64   `db:"field12"`
		Field13 float32 `db:"field13"`
		Field14 float64 `db:"field14"`
	}

	dbmap := newDbMap()
	dbmap.ExpandSliceArgs = true
	dbmap.AddTableWithName(dataFormat{}, "crazy_table")

	err := dbmap.CreateTables()
	if err != nil {
		panic(err)
	}
	defer dropAndClose(dbmap)

	err = dbmap.Insert(
		&dataFormat{
			Field1:  123,
			Field2:  "h",
			Field3:  1,
			Field4:  1,
			Field5:  1,
			Field6:  1,
			Field7:  1,
			Field8:  1,
			Field9:  1,
			Field10: 1,
			Field11: 1,
			Field12: 1,
			Field13: 1,
			Field14: 1,
		},
		&dataFormat{
			Field1:  124,
			Field2:  "e",
			Field3:  2,
			Field4:  2,
			Field5:  2,
			Field6:  2,
			Field7:  2,
			Field8:  2,
			Field9:  2,
			Field10: 2,
			Field11: 2,
			Field12: 2,
			Field13: 2,
			Field14: 2,
		},
		&dataFormat{
			Field1:  125,
			Field2:  "y",
			Field3:  3,
			Field4:  3,
			Field5:  3,
			Field6:  3,
			Field7:  3,
			Field8:  3,
			Field9:  3,
			Field10: 3,
			Field11: 3,
			Field12: 3,
			Field13: 3,
			Field14: 3,
		},
	)

	if err != nil {
		t.Fatal(err)
	}

	for _, tt := range tests {
		t.Run(tt.description, func(t *testing.T) {
			var dummy []int
			_, err := dbmap.Select(&dummy, tt.query, tt.args...)
			if err != nil {
				t.Fatal(err)
			}

			if len(dummy) != tt.wantLen {
				t.Errorf("wrong result count\ngot:  %d\nwant: %d", len(dummy), tt.wantLen)
			}
		})
	}
}
<|endoftext|>


<commit_before>package game

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/game/model"
)

const defaultTickInterval time.Duration = time.Second / 3

// The gameLoop is what updates the game model instance that it is
// associated with. The updates are performed in ticks. Each tick
// applies all actions that has been added since the last tick
// sequentially on the model. For each such actions, zero or more
// events are created. Those events are handled before the next action
// is processed, by delegating to the registered eventHandler.
//
// Notice that any reads or writes (outside of actions) on the model
// once the gameLoop is started is not safe.
type gameLoop struct {
	logEntry *logrus.Entry
	// Duration between each tick
	tickInterval time.Duration
	// The game instance on which actions are to be applied
	game *model.Game
	// Flag set to 1 once the game has completed.
	gameOver int32

	eventHandlerMu sync.Mutex
	// A handler to handle events produced when applying actions
	// to the game instance.
	eventHandler eventHandler

	// A WaitGroup for events being handled by the registered event
	// handler. We wait for this WaitGroup before returning from Run,
	// to make sure that each event has finished being handled.
	handleEventWG sync.WaitGroup

	actionsMu sync.Mutex
	// A slice of actions to be applied on the next tick
	actions []model.Action
}

// eventHandler is a handler for game events produced when applying actions to
// the game.
type eventHandler interface {
	// handleEvent handles an event produced. This method will be called on a
	// separate go routine and must block until the even has been handled, or
	// the context is cancelled.
	handleEvent(ctx context.Context, event model.Event)
}

func newGameLoop(log *logrus.Logger, game *model.Game) (*gameLoop, error) {
	logEntry := common.ModuleLogEntryWithID(log, "gameLoop")
	return &gameLoop{
		logEntry:     logEntry,
		tickInterval: defaultTickInterval,
		game:         game,
		actions:      make([]model.Action, 0),
	}, nil
}

// Sets the handler for game events.
func (gl *gameLoop) SetEventHandler(eventHandler eventHandler) {
	gl.eventHandlerMu.Lock()
	gl.eventHandler = eventHandler
	gl.eventHandlerMu.Unlock()
}

// Adds an action to be processed in the next tick.
func (gl *gameLoop) AddAction(action model.Action) {
	if gl.isGameOver() {
		// No more actions will be processed if game is over.
		return
	}
	gl.actionsMu.Lock()
	gl.actions = append(gl.actions, action)
	gl.actionsMu.Unlock()
}

// Runs the game loop. Run blocks until the context is cancelled, an
// error occurs, or the game is over.
func (gl *gameLoop) Run(ctx context.Context) error {
	gl.logEntry.Debug("Starting")
	defer gl.logEntry.Debug("Stopped")
	err := gl.tickLoop(ctx)
	// Before returning control, wait for calls to eventHandler to finish
	gl.handleEventWG.Wait()
	return errors.Wrap(err, "error while running tickLoop")
}

// setGameOver sets the game over flag to true
func (gl *gameLoop) setGameOver() {
	gl.logEntry.Debug("gameOver set")
	atomic.StoreInt32(&gl.gameOver, 1)
}

// isGameOver checks if the game over flag has been set
func (gl *gameLoop) isGameOver() bool {
	return atomic.LoadInt32(&gl.gameOver) == 1
}

// Performs a "tick" each tickInterval. The tick is what updates the game, by applying
// actions that has been added since the last tick. This method blocks until the game
// is over, on an error occurs.
func (gl *gameLoop) tickLoop(ctx context.Context) error {
	ticker := time.NewTicker(gl.tickInterval)
	defer ticker.Stop()
	lastTick := time.Now()
	for {
		if gl.isGameOver() {
			return nil
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			elapsed := time.Since(lastTick)
			actions := gl.getActions()
			// Append tick and "check game over" events each tick.
			actions = append(actions, &model.ActionTick{Delta: elapsed})
			actions = append(actions, &model.ActionCheckGameOver{})
			if err := gl.applyActions(ctx, actions); err != nil {
				return errors.Wrap(err, "Error when performing tick")
			}
			lastTick = time.Now()
		}
	}
}

// getActions swaps the current slice of actions with a new empty slice,
// returning the previous actions.
func (gl *gameLoop) getActions() []model.Action {
	gl.actionsMu.Lock()
	defer gl.actionsMu.Unlock()
	// We slowly shrink the initial capacity of the actions here, so
	// that one tick with an abnormal number of actions doesn't result
	// in every new actions slice being allocated that same large size.
	newLen := len(gl.actions) / 2
	actions := gl.actions
	gl.actions = make([]model.Action, 0, newLen)
	return actions
}

// applyActions applies actions on the game sequentially, making it safe
// for the applied actions to modify the game-state.
func (gl *gameLoop) applyActions(ctx context.Context, actions []model.Action) error {
	for _, action := range actions {
		if err := gl.applyAction(ctx, action); err != nil {
			return errors.Wrap(err, "Error applying action")
		}
		if gl.isGameOver() {
			break
		}
	}
	return nil
}

// applyAction applies a single action to the game, and handles each
// event this action produced sequentially.
func (gl *gameLoop) applyAction(ctx context.Context, action model.Action) error {
	events, err := action.Apply(gl.game)
	if err != nil {
		if _, ok := err.(*model.IllegalActionError); ok {
			gl.logEntry.Warnf("Ignoring illegal action error: %v", err)
			return nil
		}
		return errors.Wrapf(err, "Error applying action: %+v (%T)", action, action)
	}
	if err := gl.handleEvents(ctx, events); err != nil {
		return errors.Wrap(err, "Error handling events")
	}
	return nil
}

// handleEvents handles each event by delegating to the event handler. If an
// event representing the game being over is encountered, then all further
// events are discarded and the gameLoop is set to game over state.
func (gl *gameLoop) handleEvents(ctx context.Context, events []model.Event) error {
	for _, event := range events {
		if err := gl.handleEvent(ctx, event); err != nil {
			return errors.Wrapf(err, "Error handling event: %+v (%T)", event, event)
		}
		// Stop processing events if we just sent a game over event
		if model.IsGameOverEvent(event) {
			gl.setGameOver()
			break
		}
	}
	return nil
}

// handleEvent handles a single event by forwarding it to the registered
// eventHandler on a new go-routine.
func (gl *gameLoop) handleEvent(ctx context.Context, event model.Event) error {
	gl.eventHandlerMu.Lock()
	handler := gl.eventHandler
	gl.eventHandlerMu.Unlock()
	if handler == nil {
		return errors.New("Unable to handle event, no eventHandler was registered")
	}
	gl.handleEventWG.Add(1)
	go func() {
		defer gl.handleEventWG.Done()
		handler.handleEvent(ctx, event)
	}()
	return nil
}
<commit_msg>Illegal action errors only in debug<commit_after>package game

import (
	"context"
	"sync"
	"sync/atomic"
	"time"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/verath/archipelago/lib/common"
	"github.com/verath/archipelago/lib/game/model"
)

const defaultTickInterval time.Duration = time.Second / 3

// The gameLoop is what updates the game model instance that it is
// associated with. The updates are performed in ticks. Each tick
// applies all actions that has been added since the last tick
// sequentially on the model. For each such actions, zero or more
// events are created. Those events are handled before the next action
// is processed, by delegating to the registered eventHandler.
//
// Notice that any reads or writes (outside of actions) on the model
// once the gameLoop is started is not safe.
type gameLoop struct {
	logEntry *logrus.Entry
	// Duration between each tick
	tickInterval time.Duration
	// The game instance on which actions are to be applied
	game *model.Game
	// Flag set to 1 once the game has completed.
	gameOver int32

	eventHandlerMu sync.Mutex
	// A handler to handle events produced when applying actions
	// to the game instance.
	eventHandler eventHandler

	// A WaitGroup for events being handled by the registered event
	// handler. We wait for this WaitGroup before returning from Run,
	// to make sure that each event has finished being handled.
	handleEventWG sync.WaitGroup

	actionsMu sync.Mutex
	// A slice of actions to be applied on the next tick
	actions []model.Action
}

// eventHandler is a handler for game events produced when applying actions to
// the game.
type eventHandler interface {
	// handleEvent handles an event produced. This method will be called on a
	// separate go routine and must block until the even has been handled, or
	// the context is cancelled.
	handleEvent(ctx context.Context, event model.Event)
}

func newGameLoop(log *logrus.Logger, game *model.Game) (*gameLoop, error) {
	logEntry := common.ModuleLogEntryWithID(log, "gameLoop")
	return &gameLoop{
		logEntry:     logEntry,
		tickInterval: defaultTickInterval,
		game:         game,
		actions:      make([]model.Action, 0),
	}, nil
}

// Sets the handler for game events.
func (gl *gameLoop) SetEventHandler(eventHandler eventHandler) {
	gl.eventHandlerMu.Lock()
	gl.eventHandler = eventHandler
	gl.eventHandlerMu.Unlock()
}

// Adds an action to be processed in the next tick.
func (gl *gameLoop) AddAction(action model.Action) {
	if gl.isGameOver() {
		// No more actions will be processed if game is over.
		return
	}
	gl.actionsMu.Lock()
	gl.actions = append(gl.actions, action)
	gl.actionsMu.Unlock()
}

// Runs the game loop. Run blocks until the context is cancelled, an
// error occurs, or the game is over.
func (gl *gameLoop) Run(ctx context.Context) error {
	gl.logEntry.Debug("Starting")
	defer gl.logEntry.Debug("Stopped")
	err := gl.tickLoop(ctx)
	// Before returning control, wait for calls to eventHandler to finish
	gl.handleEventWG.Wait()
	return errors.Wrap(err, "error while running tickLoop")
}

// setGameOver sets the game over flag to true
func (gl *gameLoop) setGameOver() {
	gl.logEntry.Debug("gameOver set")
	atomic.StoreInt32(&gl.gameOver, 1)
}

// isGameOver checks if the game over flag has been set
func (gl *gameLoop) isGameOver() bool {
	return atomic.LoadInt32(&gl.gameOver) == 1
}

// Performs a "tick" each tickInterval. The tick is what updates the game, by applying
// actions that has been added since the last tick. This method blocks until the game
// is over, on an error occurs.
func (gl *gameLoop) tickLoop(ctx context.Context) error {
	ticker := time.NewTicker(gl.tickInterval)
	defer ticker.Stop()
	lastTick := time.Now()
	for {
		if gl.isGameOver() {
			return nil
		}
		select {
		case <-ctx.Done():
			return ctx.Err()
		case <-ticker.C:
			elapsed := time.Since(lastTick)
			actions := gl.getActions()
			// Append tick and "check game over" events each tick.
			actions = append(actions, &model.ActionTick{Delta: elapsed})
			actions = append(actions, &model.ActionCheckGameOver{})
			if err := gl.applyActions(ctx, actions); err != nil {
				return errors.Wrap(err, "Error when performing tick")
			}
			lastTick = time.Now()
		}
	}
}

// getActions swaps the current slice of actions with a new empty slice,
// returning the previous actions.
func (gl *gameLoop) getActions() []model.Action {
	gl.actionsMu.Lock()
	defer gl.actionsMu.Unlock()
	// We slowly shrink the initial capacity of the actions here, so
	// that one tick with an abnormal number of actions doesn't result
	// in every new actions slice being allocated that same large size.
	newLen := len(gl.actions) / 2
	actions := gl.actions
	gl.actions = make([]model.Action, 0, newLen)
	return actions
}

// applyActions applies actions on the game sequentially, making it safe
// for the applied actions to modify the game-state.
func (gl *gameLoop) applyActions(ctx context.Context, actions []model.Action) error {
	for _, action := range actions {
		if err := gl.applyAction(ctx, action); err != nil {
			return errors.Wrap(err, "Error applying action")
		}
		if gl.isGameOver() {
			break
		}
	}
	return nil
}

// applyAction applies a single action to the game, and handles each
// event this action produced sequentially.
func (gl *gameLoop) applyAction(ctx context.Context, action model.Action) error {
	events, err := action.Apply(gl.game)
	if err != nil {
		if _, ok := err.(*model.IllegalActionError); ok {
			gl.logEntry.Debugf("Ignoring illegal action error: %v", err)
			return nil
		}
		return errors.Wrapf(err, "Error applying action: %+v (%T)", action, action)
	}
	if err := gl.handleEvents(ctx, events); err != nil {
		return errors.Wrap(err, "Error handling events")
	}
	return nil
}

// handleEvents handles each event by delegating to the event handler. If an
// event representing the game being over is encountered, then all further
// events are discarded and the gameLoop is set to game over state.
func (gl *gameLoop) handleEvents(ctx context.Context, events []model.Event) error {
	for _, event := range events {
		if err := gl.handleEvent(ctx, event); err != nil {
			return errors.Wrapf(err, "Error handling event: %+v (%T)", event, event)
		}
		// Stop processing events if we just sent a game over event
		if model.IsGameOverEvent(event) {
			gl.setGameOver()
			break
		}
	}
	return nil
}

// handleEvent handles a single event by forwarding it to the registered
// eventHandler on a new go-routine.
func (gl *gameLoop) handleEvent(ctx context.Context, event model.Event) error {
	gl.eventHandlerMu.Lock()
	handler := gl.eventHandler
	gl.eventHandlerMu.Unlock()
	if handler == nil {
		return errors.New("Unable to handle event, no eventHandler was registered")
	}
	gl.handleEventWG.Add(1)
	go func() {
		defer gl.handleEventWG.Done()
		handler.handleEvent(ctx, event)
	}()
	return nil
}
<|endoftext|>


<commit_before>package german

import (
	"errors"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Reflexive string

const (
	Without   Reflexive = ""
	Acusative           = "a"
	Dative              = "d"
)

type Article string

const (
	Der Article = "r"
	Die         = "e"
	Das         = "s"
)

const learnedForm = "2006-01-02"

const (
	alternativeSeparator = "/"
	conjugationSeparator = ","
	argumentSeparator    = "+"
	meaningSeparator     = ";"
	synonimSeparator     = ","
	tagSeparator         = ","
)

var (
	// Article:
	// ^                      -- match beginning of string
	//  ([res])               -- match first article notion <-- r: der, e: die, s: das
	//         (/([res]))?    -- match optional second article notion, following a / sign
	//                    $   -- match end of string
	ArticleRegexp = regexp.MustCompile("^([res])(/([res]))?$")

	// Auxiliary:
	// ^                      -- match beginning of string
	//  ([sh])                -- match first auxiliary notion <-- s: sein, h: haben
	//        (/([hs]))?      -- match optional second auxiliary notion, followin a / sign
	//                  $     -- match end of string
	AuxiliaryRegexp = regexp.MustCompile("^([sh])(/([hs]))?$")

	// Noun:
	// ^                                                                               -- match beginning of string
	//  ([A-ZÄÖÜ][A-ZÄÖÜßa-zäöü ]+)                                                    -- match noun in singular, must start with a capital
	//                             ,                                                   -- match a comma
	//                              ([A-ZÄÖÜa-zäöü~⍨() -]*)                            -- match plural part, can be an extension only starting with a ⍨, ~
	//                                                     (,[A-ZÄÖÜßa-zäöü~⍨ ]*)?     -- match optional genitive, can be an extension
	//                                                                            $    -- match end of string
	NounRegexp = regexp.MustCompile("^([A-ZÄÖÜ][A-ZÄÖÜßa-zäöü ]+),([A-ZÄÖÜa-zäöü~⍨() -]*)(,[A-ZÄÖÜßa-zäöü~⍨ ]*)?$")

	// Adjective:
	// ^                                                       -- match beginning of string
	//  ([a-zäöüß]+)                                           -- match adjective
	//              (,([a-zäöüß~⍨-]*))?                        -- match optional comparative, can be an extension only starting with a ⍨, ~
	//                                 (,([a-zäöüß~⍨-]*))?     -- match optional superlative, can be an extension
	//                                                    $    -- match end of string
	AdjectiveRegexp = regexp.MustCompile("^([a-zäöüß]+)(,([a-zäöüß~⍨-]*))?(,([a-zäöüß~⍨-]*))?$")

	// Verb:
	// ^                                                 -- match beginning of string
	//  ([A-ZÄÖÜßa-zäöü, ]+)                             -- match verb
	//                     ([A-ZÄÖÜßa-zäöü+() ]*)?       -- match extension(s), separated by plus signs
	//                                            $      -- match end of string
	VerbRegexp = regexp.MustCompile("^([A-ZÄÖÜßa-zäöü, ]+)([A-ZÄÖÜßa-zäöü+() ]*)?$")

	// English Word:
	// ^                       -- match beginning of string
	//  [a-zA-Z,.() ]*         -- English words can only contain letters, dots, parantheses and spaces
	//                $        -- match end of string
	EnglishRegexp = regexp.MustCompile("^[a-zA-Z,.() ]*$")

	// German Word:
	// ^                           -- match beginning of string
	//  [a-zA-ZäÄöÖüÜß,.() ]*      -- German words can only contain German letters, dots, parantheses and spaces
	//                       $     -- match end of string
	GermanRegexp = regexp.MustCompile("^[a-zA-ZäÄöÖüÜß,.() ]*$")
)

type Word interface {
	GetGerman() string
	GetEnglish() []Meaning
	GetThird() []Meaning
	GetCategory() string
	GetUser() string
	GetScore() int
	GetLearned() time.Time
	IsOk() bool
}

type Meaning struct {
	Main        string `bson:"main" json:"main"`
	Paranthases string `bson:"paranthases" json:"paranthases"`
}

func NewMeanings(allMeanings string) []Meaning {
	meanings := []Meaning{}

	for _, word := range TrimSplit(allMeanings, meaningSeparator) {
		meanings = append(meanings, Meaning{word, ""})
	}

	return meanings
}

type DefaultWord struct {
	German   string    `bson:"german" json:"german"`
	English  []Meaning `bson:"english" json:"english"`
	Third    []Meaning `bson:"third" json:"third"`
	Category string    `bson:"category" json:"category"`
	User     string    `bson:"user" json:"user"`
	Learned  time.Time `bson:"learned" json:"learned"`
	Score    int       `bson:"score" json:"score"`
	Tags     []string  `bson:"tags" json:"tags"`
	Ok       bool      `bson:"ok", json:"ok"`
}

func NewDefaultWord(german, english, third, category, user, learned, score, tags string) DefaultWord {
	englishMeanings, thirdMeanings := NewMeanings(english), NewMeanings(third)

	scoreParsed, err := strconv.ParseInt(score, 0, 0)
	if err != nil || scoreParsed < 1 || scoreParsed > 10 {
		scoreParsed = 5
	}

	learnedParsed, err := time.Parse(learnedForm, learned)
	if err != nil {
		learnedParsed = time.Now()
	}

	return DefaultWord{
		german,
		englishMeanings,
		thirdMeanings,
		category,
		user,
		learnedParsed,
		int(scoreParsed),
		TrimSplit(tags, tagSeparator),
		true,
	}
}

func (w *DefaultWord) GetGerman() string {
	return w.German
}

func (w *DefaultWord) GetEnglish() []Meaning {
	return w.English
}

func (w *DefaultWord) GetThird() []Meaning {
	return w.Third
}

func (w *DefaultWord) GetCategory() string {
	return w.Category
}

func (w *DefaultWord) GetScore() int {
	return w.Score
}

func (w *DefaultWord) GetUser() string {
	return w.User
}

func (w *DefaultWord) GetLearned() time.Time {
	return w.Learned
}

func (w *DefaultWord) IsOk() bool {
	return w.Ok
}

type Verb struct {
	DefaultWord    `bson:"word" json:"word"`
	Auxiliary      []string  `bson:"auxiliary" json:"auxiliary"`
	Noun           string    `bson:"noun" json:"noun"`
	Adjective      string    `bson:"adjective" json:"adjective"`
	PastParticiple []string  `bson:"pastParticiple" json:"pastParticiple"`
	Preterite      []string  `bson:"preterite" json:"preterite"`
	S1             []string  `bson:"s1" json:"s1"`
	S2             []string  `bson:"s2" json:"s2"`
	S3             []string  `bson:"s3" json:"s3"`
	P1             []string  `bson:"p1" json:"p1"`
	P2             []string  `bson:"p2" json:"p2"`
	P3             []string  `bson:"p3" json:"p3"`
	Reflexive      Reflexive `bson:"reflexive" json:"reflexive"`
	Arguments      []string  `bson:"arguments" json:"arguments"`
}

func NewVerb(auxiliary, german, english, third, user, learned, score, tags string) *Verb {
	pastParticiple, preterite, ich, du, er, wir, ihr, sie := "", "", "", "", "", "", "", ""

	matches := VerbRegexp.FindStringSubmatch(german)
	if len(matches) < 3 {
		return nil
	}

	main := TrimSplit(matches[1], conjugationSeparator)
	switch len(main) {
	case 1:
		german = main[0]
		break
	case 3:
		german, pastParticiple, preterite = main[0], main[1], main[2]
		break
	case 5:
		german, pastParticiple, preterite, du, er = main[0], main[1], main[2], main[3], main[4]
		break
	case 9:
		german, ich, du, er, wir, ihr, sie, pastParticiple, preterite = main[0], main[1], main[2], main[3], main[4], main[5], main[6], main[7], main[8]
		break
	default:
		return nil
	}

	sich, arguments, err := parseArguments(matches[2])
	if err != nil {
		return nil
	}

	return &Verb{
		NewDefaultWord(german, english, third, "verb", user, learned, score, tags),
		TrimSplit(auxiliary, alternativeSeparator),
		"",
		"",
		TrimSplit(pastParticiple, alternativeSeparator),
		TrimSplit(preterite, alternativeSeparator),
		TrimSplit(ich, alternativeSeparator),
		TrimSplit(du, alternativeSeparator),
		TrimSplit(er, alternativeSeparator),
		TrimSplit(wir, alternativeSeparator),
		TrimSplit(ihr, alternativeSeparator),
		TrimSplit(sie, alternativeSeparator),
		sich,
		arguments,
	}
}

func parseArguments(rawArguments string) (Reflexive, []string, error) {
	if rawArguments == "" {
		return Without, []string{}, nil
	}
	arguments := TrimSplit(rawArguments, argumentSeparator)

	if strings.Contains(arguments[0], "sich (A)") {
		return Acusative, arguments[1:], nil
	}

	if strings.Contains(arguments[0], "sich (D)") {
		return Dative, arguments[1:], nil
	}

	if strings.Contains(arguments[0], "sich") {
		return Without, []string{}, errors.New("Reflexive definition is invalid")
	}

	return Without, []string{}, nil
}

type Noun struct {
	DefaultWord `bson:"word" json:"word"`
	Articles    []Article `bson:"article" json:"article"`
	Plural      []string  `bson:"plural" json:"plural"`
}

func NewNoun(articles, german, english, third, user, learned, score, tags string) *Noun {
	matches := NounRegexp.FindStringSubmatch(german)

	articleList := []Article{}
	for _, article := range TrimSplit(articles, alternativeSeparator) {
		switch article {
		case "r":
			articleList = append(articleList, Der)
			break
		case "e":
			articleList = append(articleList, Die)
			break
		case "s":
			articleList = append(articleList, Das)
			break
		default:
			return nil
		}
	}

	return &Noun{
		NewDefaultWord(german, english, third, "noun", user, learned, score, tags),
		articleList,
		TrimSplit(matches[2], alternativeSeparator),
	}
}

type Adjective struct {
	DefaultWord `bson:"word" json:"word"`
	Comparative []string `bson:"comparative" json:"comparative"`
	Superlative []string `bson:"superlative" json:"superlative"`
}

func NewAdjective(german, english, third, user, learned, score, tags string) *Adjective {
	adjectiveParts := TrimSplit(german, conjugationSeparator)

	comparative := []string{}
	superlative := []string{}

	if len(adjectiveParts) > 1 {
		comparative = TrimSplit(adjectiveParts[1], alternativeSeparator)
	}
	if len(adjectiveParts) > 2 {
		superlative = TrimSplit(adjectiveParts[2], alternativeSeparator)
	}

	return &Adjective{
		NewDefaultWord(german, english, third, "adjective", user, learned, score, tags),
		comparative,
		superlative,
	}
}

type Any struct {
	DefaultWord `bson:"word" json:"word"`
}

func NewAny(german, english, third, category, user, learned, score, tags string, ok bool) *Any {
	d := NewDefaultWord(german, english, third, category, user, learned, score, tags)

	d.Ok = ok

	return &Any{d}
}
<commit_msg>Enhance Argument and Meaning parsing<commit_after>package german

import (
	"errors"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Reflexive string

const (
	ReflexiveWithout   Reflexive = ""
	ReflexiveAcusative           = "A"
	ReflexiveDative              = "D"
)

type Case string

const (
	CaseNominative Case = "N"
	CaseAcusative       = "A"
	CaseDative          = "D"
	CaseGenitive        = "G"
)

type Article string

const (
	Der Article = "r"
	Die         = "e"
	Das         = "s"
)

const learnedForm = "2006-01-02"

const (
	alternativeSeparator = "/"
	conjugationSeparator = ","
	argumentSeparator    = "+"
	meaningSeparator     = ";"
	synonimSeparator     = ","
	tagSeparator         = ","
	defaultWhitespace    = "\t\n\f\r "
)

var (
	// Article:
	// ^                      -- match beginning of string
	//  ([res])               -- match first article notion <-- r: der, e: die, s: das
	//         (/([res]))?    -- match optional second article notion, following a / sign
	//                    $   -- match end of string
	ArticleRegexp = regexp.MustCompile("^([res])(/([res]))?$")

	// Auxiliary:
	// ^                      -- match beginning of string
	//  ([sh])                -- match first auxiliary notion <-- s: sein, h: haben
	//        (/([hs]))?      -- match optional second auxiliary notion, followin a / sign
	//                  $     -- match end of string
	AuxiliaryRegexp = regexp.MustCompile("^([sh])(/([hs]))?$")

	// Argument:
	// ^                                  -- match beginning of string
	//  ([^(]*)                           -- match any string that is not a open paranthases character
	//          ?                         -- match optional space character
	//           (                        -- start of paranthases matching
	//            [(]                       -- match open paranthases character
	//               ([NADG])               -- match a case notion character  <-- N: nominative, A: acusative, D: dative, G: genitive
	//                       [)]            -- match close paranthases character
	//                          )?          -- end of paranthases matching
	//                             *      -- match optional spaces
	//                              $     -- match end of string
	ArgumentRegexp = regexp.MustCompile("^([^(]*) ?([(]([NADG])[)])? *$")

	// Meaning:
	// ^                                -- match beginning of string
	//  ([^(]*)                         -- match any string that is not a open paranthases character
	//          ?                       -- match optional space character
	//           (                      -- start of paranthases matching
	//            [(]                     -- match open paranthases character
	//               ([^)]*)              -- match paranthases content
	//                      [)]           -- match close paranthases character
	//                         )?         -- end of paranthases matching
	//                           *      -- match optional spaces
	//                            $     -- match end of string
	MeaningRegexp = regexp.MustCompile("^([^(]*) ?([(]([^)]*)[)])? *$")

	// Noun:
	// ^                                                                               -- match beginning of string
	//  ([A-ZÄÖÜ][A-ZÄÖÜßa-zäöü ]+)                                                    -- match noun in singular, must start with a capital
	//                             ,                                                   -- match a comma
	//                              ([A-ZÄÖÜa-zäöü~⍨() -]*)                            -- match plural part, can be an extension only starting with a ⍨, ~
	//                                                     (,[A-ZÄÖÜßa-zäöü~⍨ ]*)?     -- match optional genitive, can be an extension
	//                                                                            $    -- match end of string
	NounRegexp = regexp.MustCompile("^([A-ZÄÖÜ][A-ZÄÖÜßa-zäöü ]+),([A-ZÄÖÜa-zäöü~⍨() -]*)(,[A-ZÄÖÜßa-zäöü~⍨ ]*)?$")

	// Adjective:
	// ^                                                       -- match beginning of string
	//  ([a-zäöüß]+)                                           -- match adjective
	//              (,([a-zäöüß~⍨-]*))?                        -- match optional comparative, can be an extension only starting with a ⍨, ~
	//                                 (,([a-zäöüß~⍨-]*))?     -- match optional superlative, can be an extension
	//                                                    $    -- match end of string
	AdjectiveRegexp = regexp.MustCompile("^([a-zäöüß]+)(,([a-zäöüß~⍨-]*))?(,([a-zäöüß~⍨-]*))?$")

	// Verb:
	// ^                                                 -- match beginning of string
	//  ([A-ZÄÖÜßa-zäöü, ]+)                             -- match verb
	//                     ([A-ZÄÖÜßa-zäöü+() ]*)?       -- match extension(s), separated by plus signs
	//                                            $      -- match end of string
	VerbRegexp = regexp.MustCompile("^([A-ZÄÖÜßa-zäöü, ]+)([A-ZÄÖÜßa-zäöü+() ]*)?$")

	// English Word:
	// ^                       -- match beginning of string
	//  [a-zA-Z,.() ]*         -- English words can only contain letters, dots, parantheses and spaces
	//                $        -- match end of string
	EnglishRegexp = regexp.MustCompile("^[a-zA-Z,.() ]*$")

	// German Word:
	// ^                           -- match beginning of string
	//  [a-zA-ZäÄöÖüÜß,.() ]*      -- German words can only contain German letters, dots, parantheses and spaces
	//                       $     -- match end of string
	GermanRegexp = regexp.MustCompile("^[a-zA-ZäÄöÖüÜß,.() ]*$")
)

type Word interface {
	GetGerman() string
	GetEnglish() []Meaning
	GetThird() []Meaning
	GetCategory() string
	GetUser() string
	GetScore() int
	GetLearned() time.Time
	IsOk() bool
}

type Argument struct {
	Preposition string `bson:"prep" json:"prep"`
	Case        string `bson:"case" json:"case"`
}

func NewArgument(allArguments string) []Argument {
	arguments := []Argument{}

	for _, word := range TrimSplit(allArguments, argumentSeparator) {
		matches := ArgumentRegexp.FindStringSubmatch(word)
		if len(matches) < 3 {
			continue
		}

		p := strings.Trim(matches[1], defaultWhitespace)
		c := strings.Trim(matches[2], defaultWhitespace)

		arguments = append(arguments, Argument{p, c})
	}

	return arguments
}

type Meaning struct {
	Main        string `bson:"main" json:"main"`
	Paranthases string `bson:"paranthases" json:"paranthases"`
}

func NewMeanings(allMeanings string) []Meaning {
	meanings := []Meaning{}

	for _, word := range TrimSplit(allMeanings, meaningSeparator) {
		matches := MeaningRegexp.FindStringSubmatch(word)
		if len(matches) < 3 {
			continue
		}

		m := strings.Trim(matches[1], defaultWhitespace)
		p := strings.Trim(matches[2], defaultWhitespace)

		meanings = append(meanings, Meaning{m, p})
	}

	return meanings
}

type DefaultWord struct {
	German   string    `bson:"german" json:"german"`
	English  []Meaning `bson:"english" json:"english"`
	Third    []Meaning `bson:"third" json:"third"`
	Category string    `bson:"category" json:"category"`
	User     string    `bson:"user" json:"user"`
	Learned  time.Time `bson:"learned" json:"learned"`
	Score    int       `bson:"score" json:"score"`
	Tags     []string  `bson:"tags" json:"tags"`
	Ok       bool      `bson:"ok", json:"ok"`
}

func NewDefaultWord(german, english, third, category, user, learned, score, tags string) DefaultWord {
	englishMeanings, thirdMeanings := NewMeanings(english), NewMeanings(third)

	scoreParsed, err := strconv.ParseInt(score, 0, 0)
	if err != nil || scoreParsed < 1 || scoreParsed > 10 {
		scoreParsed = 5
	}

	learnedParsed, err := time.Parse(learnedForm, learned)
	if err != nil {
		learnedParsed = time.Now()
	}

	return DefaultWord{
		german,
		englishMeanings,
		thirdMeanings,
		category,
		user,
		learnedParsed,
		int(scoreParsed),
		TrimSplit(tags, tagSeparator),
		true,
	}
}

func (w *DefaultWord) GetGerman() string {
	return w.German
}

func (w *DefaultWord) GetEnglish() []Meaning {
	return w.English
}

func (w *DefaultWord) GetThird() []Meaning {
	return w.Third
}

func (w *DefaultWord) GetCategory() string {
	return w.Category
}

func (w *DefaultWord) GetScore() int {
	return w.Score
}

func (w *DefaultWord) GetUser() string {
	return w.User
}

func (w *DefaultWord) GetLearned() time.Time {
	return w.Learned
}

func (w *DefaultWord) IsOk() bool {
	return w.Ok
}

type Verb struct {
	DefaultWord    `bson:"word" json:"word"`
	Auxiliary      []string  `bson:"auxiliary" json:"auxiliary"`
	Noun           string    `bson:"noun" json:"noun"`
	Adjective      string    `bson:"adjective" json:"adjective"`
	PastParticiple []string  `bson:"pastParticiple" json:"pastParticiple"`
	Preterite      []string  `bson:"preterite" json:"preterite"`
	S1             []string  `bson:"s1" json:"s1"`
	S2             []string  `bson:"s2" json:"s2"`
	S3             []string  `bson:"s3" json:"s3"`
	P1             []string  `bson:"p1" json:"p1"`
	P2             []string  `bson:"p2" json:"p2"`
	P3             []string  `bson:"p3" json:"p3"`
	Reflexive      Reflexive `bson:"reflexive" json:"reflexive"`
	Arguments      []string  `bson:"arguments" json:"arguments"`
}

func NewVerb(auxiliary, german, english, third, user, learned, score, tags string) *Verb {
	pastParticiple, preterite, ich, du, er, wir, ihr, sie := "", "", "", "", "", "", "", ""

	matches := VerbRegexp.FindStringSubmatch(german)
	if len(matches) < 3 {
		return nil
	}

	main := TrimSplit(matches[1], conjugationSeparator)
	switch len(main) {
	case 1:
		german = main[0]
		break
	case 3:
		german, pastParticiple, preterite = main[0], main[1], main[2]
		break
	case 5:
		german, pastParticiple, preterite, du, er = main[0], main[1], main[2], main[3], main[4]
		break
	case 9:
		german, ich, du, er, wir, ihr, sie, pastParticiple, preterite = main[0], main[1], main[2], main[3], main[4], main[5], main[6], main[7], main[8]
		break
	default:
		return nil
	}

	sich, arguments, err := parseArguments(matches[2])
	if err != nil {
		return nil
	}

	return &Verb{
		NewDefaultWord(german, english, third, "verb", user, learned, score, tags),
		TrimSplit(auxiliary, alternativeSeparator),
		"",
		"",
		TrimSplit(pastParticiple, alternativeSeparator),
		TrimSplit(preterite, alternativeSeparator),
		TrimSplit(ich, alternativeSeparator),
		TrimSplit(du, alternativeSeparator),
		TrimSplit(er, alternativeSeparator),
		TrimSplit(wir, alternativeSeparator),
		TrimSplit(ihr, alternativeSeparator),
		TrimSplit(sie, alternativeSeparator),
		sich,
		arguments,
	}
}

func parseArguments(rawArguments string) (Reflexive, []string, error) {
	if rawArguments == "" {
		return ReflexiveWithout, []string{}, nil
	}
	arguments := TrimSplit(rawArguments, argumentSeparator)

	if strings.Contains(arguments[0], "sich (A)") {
		return ReflexiveAcusative, arguments[1:], nil
	}

	if strings.Contains(arguments[0], "sich (D)") {
		return ReflexiveDative, arguments[1:], nil
	}

	if strings.Contains(arguments[0], "sich") {
		return ReflexiveWithout, []string{}, errors.New("Reflexive definition is invalid")
	}

	return ReflexiveWithout, []string{}, nil
}

type Noun struct {
	DefaultWord `bson:"word" json:"word"`
	Articles    []Article `bson:"article" json:"article"`
	Plural      []string  `bson:"plural" json:"plural"`
}

func NewNoun(articles, german, english, third, user, learned, score, tags string) *Noun {
	matches := NounRegexp.FindStringSubmatch(german)

	articleList := []Article{}
	for _, article := range TrimSplit(articles, alternativeSeparator) {
		switch article {
		case "r":
			articleList = append(articleList, Der)
			break
		case "e":
			articleList = append(articleList, Die)
			break
		case "s":
			articleList = append(articleList, Das)
			break
		default:
			return nil
		}
	}

	return &Noun{
		NewDefaultWord(german, english, third, "noun", user, learned, score, tags),
		articleList,
		TrimSplit(matches[2], alternativeSeparator),
	}
}

type Adjective struct {
	DefaultWord `bson:"word" json:"word"`
	Comparative []string `bson:"comparative" json:"comparative"`
	Superlative []string `bson:"superlative" json:"superlative"`
}

func NewAdjective(german, english, third, user, learned, score, tags string) *Adjective {
	adjectiveParts := TrimSplit(german, conjugationSeparator)

	comparative := []string{}
	superlative := []string{}

	if len(adjectiveParts) > 1 {
		comparative = TrimSplit(adjectiveParts[1], alternativeSeparator)
	}
	if len(adjectiveParts) > 2 {
		superlative = TrimSplit(adjectiveParts[2], alternativeSeparator)
	}

	return &Adjective{
		NewDefaultWord(german, english, third, "adjective", user, learned, score, tags),
		comparative,
		superlative,
	}
}

type Any struct {
	DefaultWord `bson:"word" json:"word"`
}

func NewAny(german, english, third, category, user, learned, score, tags string, ok bool) *Any {
	d := NewDefaultWord(german, english, third, category, user, learned, score, tags)

	d.Ok = ok

	return &Any{d}
}
<|endoftext|>