<commit_before>package dpn

import (
	"fmt"
	"github.com/APTrust/bagman/bagman"
	"path/filepath"
	"strings"
)

// fetcher.go fetches APTrust files from S3 so we can put them into
// a DPN bag. This is used for DPN bags that we create at APTrust.
// For copying bags from remote DPN nodes, see copier.go, which copies
// files to our local staging area so we can replicate them.

type DPNFetchResult struct {
	FetchResult *bagman.FetchResult
	GenericFile *bagman.GenericFile
}

func (result *DPNFetchResult) Succeeded() (bool) {
	if result.FetchResult == nil || result.GenericFile == nil || result.FetchResult.ErrorMessage != "" {
		return false
	}
	ourMd5FromIngest := result.GenericFile.GetChecksum("md5")
	if ourMd5FromIngest == nil {
		result.FetchResult.ErrorMessage = "Cannot verify md5 checksum on file, because the system cannot find the GenericFile's original md5 digest."
		return false  // This should never happen.
	}
	checksumMatches := result.FetchResult.LocalMd5 == ourMd5FromIngest.Digest
	if !checksumMatches {
		result.FetchResult.ErrorMessage = fmt.Sprintf("Checksum mismatch: md5 checksum of file fetched from S3 is '%s'; md5 checksum of GenericFile is '%s'", result.FetchResult.LocalMd5, ourMd5FromIngest.Digest)
	}
	return checksumMatches
}

type FetchResultCollection struct {
	Items []*DPNFetchResult
	idMap map[string]*DPNFetchResult
}

func NewFetchResultCollection() (*FetchResultCollection) {
	return &FetchResultCollection {
		Items: make([]*DPNFetchResult, 0),
		idMap: make(map[string]*DPNFetchResult, 0),
	}
}

func (results *FetchResultCollection) SuccessCount() (int) {
	// Map prevents the same file being counted twice
	// if it was added twice with Add().
	succeeded := make(map[string]bool)
	for _, result := range results.Items {
		_, alreadyCounted := succeeded[result.GenericFile.Identifier]
		if result.Succeeded() && !alreadyCounted {
			succeeded[result.GenericFile.Identifier] = true
		}
	}
	return len(succeeded)
}

func (results *FetchResultCollection) Errors() ([]string) {
	errors := make([]string, 0)
	for _, result := range results.Items {
		if !result.Succeeded() {
			errors = append(errors, result.FetchResult.ErrorMessage)
		}
	}
	return errors
}

func (results *FetchResultCollection) Add(result *DPNFetchResult) {
	results.Items = append(results.Items, result)
}

// Finds a DPNFetchResult by GenericFile.Identifier
func (results *FetchResultCollection) FindByIdentifier(identifier string) (*DPNFetchResult) {
	if len(results.idMap) != len(results.Items) {
		results.buildMap()
	}
	dpnFetchResult, _ := results.idMap[identifier]
	return dpnFetchResult
}

func (results *FetchResultCollection) buildMap() {
	results.idMap = make(map[string]*DPNFetchResult, len(results.Items))
	for i := range results.Items {
		item := results.Items[i]
		gf := item.GenericFile
		results.idMap[gf.Identifier] = item
	}
}

// FetchFiles fetches remote S3 files that make up the specified
// IntellectualObject into the specified directory.
func FetchObjectFiles(s3Client *bagman.S3Client, genericFiles []*bagman.GenericFile, dir string) (*FetchResultCollection, error) {
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}
	results := NewFetchResultCollection()
	for _, gf := range genericFiles {
		origPath, err := gf.OriginalPath()
		if err != nil {
			return nil, err
		}
		// APTrust bags allow misc tag files in top directory,
		// but DPN BagIt spec doesn't explicitly allow that,
		// so we'll put those files in custom tag dir, which
		// DPN does allow.
		newPath := origPath
		if !strings.Contains(origPath, "/") {
			newPath = fmt.Sprintf("aptrust-tags/%s", origPath)
		}
		localPath := filepath.Join(dir, newPath)
		fetchResult := s3Client.FetchURLToFile(gf.URI, localPath)
		result := &DPNFetchResult{
			FetchResult: fetchResult,
			GenericFile: gf,
		}
		results.Add(result)
	}
	return results, nil
}
<commit_msg>PT #126728057: Log specific reason for failure.<commit_after>package dpn

import (
	"fmt"
	"github.com/APTrust/bagman/bagman"
	"path/filepath"
	"strings"
)

// fetcher.go fetches APTrust files from S3 so we can put them into
// a DPN bag. This is used for DPN bags that we create at APTrust.
// For copying bags from remote DPN nodes, see copier.go, which copies
// files to our local staging area so we can replicate them.

type DPNFetchResult struct {
	FetchResult *bagman.FetchResult
	GenericFile *bagman.GenericFile
}

func (result *DPNFetchResult) Succeeded() (bool) {
	if result.FetchResult == nil || result.GenericFile == nil || result.FetchResult.ErrorMessage != "" {
		return false
	}
	ourMd5FromIngest := result.GenericFile.GetChecksum("md5")
	if ourMd5FromIngest == nil {
		result.FetchResult.ErrorMessage = fmt.Sprintf("Cannot verify md5 checksum on file '%s', because the system cannot find the GenericFile's original md5 digest.", result.GenericFile.Identifier)
		return false  // This should never happen.
	}
	checksumMatches := result.FetchResult.LocalMd5 == ourMd5FromIngest.Digest
	if !checksumMatches {
		result.FetchResult.ErrorMessage = fmt.Sprintf("Checksum mismatch for GenericFile '%s' (URI '%s'): md5 checksum of file fetched from S3 is '%s'; md5 checksum of GenericFile is '%s'", result.GenericFile.Identifier, result.GenericFile.URI, result.FetchResult.LocalMd5, ourMd5FromIngest.Digest)
	}
	return checksumMatches
}

type FetchResultCollection struct {
	Items []*DPNFetchResult
	idMap map[string]*DPNFetchResult
}

func NewFetchResultCollection() (*FetchResultCollection) {
	return &FetchResultCollection {
		Items: make([]*DPNFetchResult, 0),
		idMap: make(map[string]*DPNFetchResult, 0),
	}
}

func (results *FetchResultCollection) SuccessCount() (int) {
	// Map prevents the same file being counted twice
	// if it was added twice with Add().
	succeeded := make(map[string]bool)
	for _, result := range results.Items {
		_, alreadyCounted := succeeded[result.GenericFile.Identifier]
		if result.Succeeded() && !alreadyCounted {
			succeeded[result.GenericFile.Identifier] = true
		}
	}
	return len(succeeded)
}

func (results *FetchResultCollection) Errors() ([]string) {
	errors := make([]string, 0)
	for _, result := range results.Items {
		if !result.Succeeded() {
			errors = append(errors, result.FetchResult.ErrorMessage)
		}
	}
	return errors
}

func (results *FetchResultCollection) Add(result *DPNFetchResult) {
	results.Items = append(results.Items, result)
}

// Finds a DPNFetchResult by GenericFile.Identifier
func (results *FetchResultCollection) FindByIdentifier(identifier string) (*DPNFetchResult) {
	if len(results.idMap) != len(results.Items) {
		results.buildMap()
	}
	dpnFetchResult, _ := results.idMap[identifier]
	return dpnFetchResult
}

func (results *FetchResultCollection) buildMap() {
	results.idMap = make(map[string]*DPNFetchResult, len(results.Items))
	for i := range results.Items {
		item := results.Items[i]
		gf := item.GenericFile
		results.idMap[gf.Identifier] = item
	}
}

// FetchFiles fetches remote S3 files that make up the specified
// IntellectualObject into the specified directory.
func FetchObjectFiles(s3Client *bagman.S3Client, genericFiles []*bagman.GenericFile, dir string) (*FetchResultCollection, error) {
	if !strings.HasSuffix(dir, "/") {
		dir += "/"
	}
	results := NewFetchResultCollection()
	for _, gf := range genericFiles {
		origPath, err := gf.OriginalPath()
		if err != nil {
			return nil, err
		}
		// APTrust bags allow misc tag files in top directory,
		// but DPN BagIt spec doesn't explicitly allow that,
		// so we'll put those files in custom tag dir, which
		// DPN does allow.
		newPath := origPath
		if !strings.Contains(origPath, "/") {
			newPath = fmt.Sprintf("aptrust-tags/%s", origPath)
		}
		localPath := filepath.Join(dir, newPath)
		fetchResult := s3Client.FetchURLToFile(gf.URI, localPath)
		result := &DPNFetchResult{
			FetchResult: fetchResult,
			GenericFile: gf,
		}
		results.Add(result)
	}
	return results, nil
}
<|endoftext|>


<commit_before>package fi

import (
	"encoding/json"
	"fmt"
	"reflect"
)

func StringValue(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

func String(s string) *string {
	return &s
}

func Bool(v bool) *bool {
	return &v
}

func BoolValue(v *bool) bool {
	if v == nil {
		return false
	}
	return *v
}

func Int(v int) *int {
	return &v
}

func IntValue(v *int) int {
	if v == nil {
		return 0
	}
	return *v
}

func Int64(v int64) *int64 {
	return &v
}

func DebugPrint(o interface{}) string {
	if o == nil {
		return "<nil>"
	}
	if rh, ok := o.(*ResourceHolder); ok {
		if rh == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}

		if rh.Resource == nil {
			return fmt.Sprintf("unknown resource %q", rh.Name)
		}
	}
	if resource, ok := o.(Resource); ok {
		if resource == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}

		s, err := ResourceAsString(resource)
		if err != nil {
			return fmt.Sprintf("error converting resource to string: %v", err)
		}
		if len(s) >= 256 {
			s = s[:256] + "... (truncated)"
		}
		return s
	}

	v := reflect.ValueOf(o)
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return "<nil>"
		}
		v = v.Elem()
	}
	if !v.IsValid() {
		return "<?>"
	}
	o = v.Interface()
	if stringer, ok := o.(fmt.Stringer); ok {
		if stringer == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}
		return stringer.String()
	}

	return fmt.Sprint(o)
}

func DebugAsJsonString(v interface{}) string {
	data, err := json.Marshal(v)
	if err != nil {
		return fmt.Sprintf("error marshalling: %v", err)
	}
	return string(data)
}

func DebugAsJsonStringIndent(v interface{}) string {
	data, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return fmt.Sprintf("error marshalling: %v", err)
	}
	return string(data)
}
<commit_msg>Add more value helpers<commit_after>package fi

import (
	"encoding/json"
	"fmt"
	"reflect"
)

func StringValue(s *string) string {
	if s == nil {
		return ""
	}
	return *s
}

func String(s string) *string {
	return &s
}

func Bool(v bool) *bool {
	return &v
}

func BoolValue(v *bool) bool {
	if v == nil {
		return false
	}
	return *v
}

func Int(v int) *int {
	return &v
}

func IntValue(v *int) int {
	if v == nil {
		return 0
	}
	return *v
}

func Int64(v int64) *int64 {
	return &v
}

func Int64Value(v *int64) int64 {
	if v == nil {
		return 0
	}
	return *v
}

func Uint64Value(v *uint64) uint64 {
	if v == nil {
		return 0
	}
	return *v
}

func DebugPrint(o interface{}) string {
	if o == nil {
		return "<nil>"
	}
	if rh, ok := o.(*ResourceHolder); ok {
		if rh == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}

		if rh.Resource == nil {
			return fmt.Sprintf("unknown resource %q", rh.Name)
		}
	}
	if resource, ok := o.(Resource); ok {
		if resource == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}

		s, err := ResourceAsString(resource)
		if err != nil {
			return fmt.Sprintf("error converting resource to string: %v", err)
		}
		if len(s) >= 256 {
			s = s[:256] + "... (truncated)"
		}
		return s
	}

	v := reflect.ValueOf(o)
	if v.Kind() == reflect.Ptr {
		if v.IsNil() {
			return "<nil>"
		}
		v = v.Elem()
	}
	if !v.IsValid() {
		return "<?>"
	}
	o = v.Interface()
	if stringer, ok := o.(fmt.Stringer); ok {
		if stringer == nil {
			// Avoid go nil vs interface problems
			return "<nil>"
		}
		return stringer.String()
	}

	return fmt.Sprint(o)
}

func DebugAsJsonString(v interface{}) string {
	data, err := json.Marshal(v)
	if err != nil {
		return fmt.Sprintf("error marshalling: %v", err)
	}
	return string(data)
}

func DebugAsJsonStringIndent(v interface{}) string {
	data, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return fmt.Sprintf("error marshalling: %v", err)
	}
	return string(data)
}
<|endoftext|>


<commit_before>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2016 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

// +build go1.8

package firebirdsql

import (
	"database/sql/driver"
	"errors"

	"context"
)

func (stmt *firebirdsqlStmt) ExecContext(ctx context.Context, namedargs []driver.NamedValue) (result driver.Result, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}

	return stmt.exec(ctx, args)
}

func (stmt *firebirdsqlStmt) QueryContext(ctx context.Context, namedargs []driver.NamedValue) (rows driver.Rows, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}

	return stmt.query(ctx, args)
}

func (fc *firebirdsqlConn) BeginContext(ctx context.Context) (driver.Tx, error) {
	isolationLevel := ISOLATION_LEVEL_READ_COMMITED
	readOnly := false
	return fc.begin(isolationLevel, readOnly)
}

func (fc *firebirdsqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
	return fc.prepare(ctx, query)
}

func (fc *firebirdsqlConn) ExecContext(ctx context.Context, query string, namedargs []driver.NamedValue) (result driver.Result, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}
	return fc.exec(ctx, query, args)
}

func (fc *firebirdsqlConn) Ping(ctx context.Context) error {
	if fc == nil {
		return errors.New("Connection was closed")
	}
	return nil
}

func (fc *firebirdsqlConn) QueryContext(ctx context.Context, query string, namedargs []driver.NamedValue) (rows driver.Rows, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}
	return fc.query(ctx, query, args)
}
<commit_msg>move build constraints to top of lines<commit_after>// +build go1.8

/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2016 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

package firebirdsql

import (
	"database/sql/driver"
	"errors"

	"context"
)

func (stmt *firebirdsqlStmt) ExecContext(ctx context.Context, namedargs []driver.NamedValue) (result driver.Result, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}

	return stmt.exec(ctx, args)
}

func (stmt *firebirdsqlStmt) QueryContext(ctx context.Context, namedargs []driver.NamedValue) (rows driver.Rows, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}

	return stmt.query(ctx, args)
}

func (fc *firebirdsqlConn) BeginContext(ctx context.Context) (driver.Tx, error) {
	isolationLevel := ISOLATION_LEVEL_READ_COMMITED
	readOnly := false
	return fc.begin(isolationLevel, readOnly)
}

func (fc *firebirdsqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
	return fc.prepare(ctx, query)
}

func (fc *firebirdsqlConn) ExecContext(ctx context.Context, query string, namedargs []driver.NamedValue) (result driver.Result, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}
	return fc.exec(ctx, query, args)
}

func (fc *firebirdsqlConn) Ping(ctx context.Context) error {
	if fc == nil {
		return errors.New("Connection was closed")
	}
	return nil
}

func (fc *firebirdsqlConn) QueryContext(ctx context.Context, query string, namedargs []driver.NamedValue) (rows driver.Rows, err error) {
	args := make([]driver.Value, len(namedargs))
	for i, nv := range namedargs {
		args[i] = nv.Value
	}
	return fc.query(ctx, query, args)
}
<|endoftext|>


<commit_before>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2013 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

package firebirdsql

import (
    "fmt"
    "testing"
    "time"
    "database/sql"
)

func TestConnect(t *testing.T) {
    conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
    if err != nil {
        t.Fatalf("Error connecting: %v", err)
    }
    var sql string
    var n int

    sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='FOO'"
    err = conn.QueryRow(sql).Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n > 0 {
        conn.Exec("DROP TABLE foo")
    }

    sql = `
        CREATE TABLE foo (
            a INTEGER NOT NULL,
            b VARCHAR(30) NOT NULL UNIQUE,
            c VARCHAR(1024),
            d DECIMAL(16,3) DEFAULT -0.123,
            e DATE DEFAULT '1967-08-11',
            f TIMESTAMP DEFAULT '1967-08-11 23:45:01',
            g TIME DEFAULT '23:45:01',
            h BLOB SUB_TYPE 1,
            i DOUBLE PRECISION DEFAULT 0.0,
            j FLOAT DEFAULT 0.0,
            PRIMARY KEY (a),
            CONSTRAINT CHECK_A CHECK (a <> 0)
        )
    `
    conn.Exec(sql)
    // 3 records insert
    conn.Exec("insert into foo(a, b, c,h) values (1, 'a', 'b','This is a memo')")
    conn.Exec("insert into foo(a, b, c, e, g, i, j) values (2, 'A', 'B', '1999-01-25', '00:00:01', 0.1, 0.1)")
    conn.Exec("insert into foo(a, b, c, e, g, i, j) values (3, 'X', 'Y', '2001-07-05', '00:01:02', 0.2, 0.2)")

    err = conn.QueryRow("select count(*) cnt from foo").Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n != 3 {
        t.Fatalf("Error bad record count: %v", n)
    }

    rows, err := conn.Query("select a, b, c, d, e, f, g, i, j from foo")
    var a int
    var b, c string
    var d float64
    var e time.Time
    var f time.Time
    var g time.Time
    var i float64
    var j float32
    for rows.Next() {
        rows.Scan(&a, &b, &c, &d, &e, &f, &g, &i, &j)
        fmt.Println(a, b, c, d, e, f, g, i, j)
    }

    stmt, _ := conn.Prepare("select count(*) from foo where a=?")
    err = stmt.QueryRow(1).Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n != 1 {
        t.Fatalf("Error bad record count: %v", n)
    }

    defer conn.Close()
}
<commit_msg>modify test<commit_after>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2013 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

package firebirdsql

import (
    "fmt"
    "testing"
    "time"
    "database/sql"
)

func TestConnect(t *testing.T) {
    conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
    if err != nil {
        t.Fatalf("Error connecting: %v", err)
    }
    var sql string
    var n int

    sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='FOO'"
    err = conn.QueryRow(sql).Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n > 0 {
        conn.Exec("DROP TABLE foo")
    }

    sql = `
        CREATE TABLE foo (
            a INTEGER NOT NULL,
            b VARCHAR(30) NOT NULL UNIQUE,
            c VARCHAR(1024),
            d DECIMAL(16,3) DEFAULT -0.123,
            e DATE DEFAULT '1967-08-11',
            f TIMESTAMP DEFAULT '1967-08-11 23:45:01',
            g TIME DEFAULT '23:45:01',
            h BLOB SUB_TYPE 1,
            i DOUBLE PRECISION DEFAULT 0.0,
            j FLOAT DEFAULT 0.0,
            PRIMARY KEY (a),
            CONSTRAINT CHECK_A CHECK (a <> 0)
        )
    `
    conn.Exec(sql)
    // 3 records insert
    conn.Exec("insert into foo(a, b, c,h) values (1, 'a', 'b','This is a memo')")
    conn.Exec("insert into foo(a, b, c, e, g, i, j) values (2, 'A', 'B', '1999-01-25', '00:00:01', 0.1, 0.1)")
    conn.Exec("insert into foo(a, b, c, e, g, i, j) values (3, 'X', 'Y', '2001-07-05', '00:01:02', 0.2, 0.2)")

    err = conn.QueryRow("select count(*) cnt from foo").Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n != 3 {
        t.Fatalf("Error bad record count: %v", n)
    }

    rows, err := conn.Query("select a, b, c, d, e, f, g, i, j from foo")
    var a int
    var b, c string
    var d float64
    var e time.Time
    var f time.Time
    var g time.Time
    var i float64
    var j float32
    for rows.Next() {
        rows.Scan(&a, &b, &c, &d, &e, &f, &g, &i, &j)
        fmt.Println(a, b, c, d, e, f, g, i, j)
    }

    stmt, _ := conn.Prepare("select count(*) from foo where a=? and b=?")
    err = stmt.QueryRow(1, "a").Scan(&n)
    if err != nil {
        t.Fatalf("Error QueryRow: %v", err)
    }
    if n != 1 {
        t.Fatalf("Error bad record count: %v", n)
    }

    defer conn.Close()
}
<|endoftext|>


<commit_before>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2013 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

package firebirdsql

import (
	"database/sql"
	"testing"
	"time"
)

func TestBasic(t *testing.T) {
	conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	var sql string
	var n int

	sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='FOO'"
	err = conn.QueryRow(sql).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n > 0 {
		conn.Exec("DROP TABLE foo")
	}

	sql = `
        CREATE TABLE foo (
            a INTEGER NOT NULL,
            b VARCHAR(30) NOT NULL UNIQUE,
            c VARCHAR(1024),
            d DECIMAL(16,3) DEFAULT -0.123,
            e DATE DEFAULT '1967-08-11',
            f TIMESTAMP DEFAULT '1967-08-11 23:45:01',
            g TIME DEFAULT '23:45:01',
            h BLOB SUB_TYPE 1,
            i DOUBLE PRECISION DEFAULT 0.0,
            j FLOAT DEFAULT 0.0,
            PRIMARY KEY (a),
            CONSTRAINT CHECK_A CHECK (a <> 0)
        )
    `
	conn.Exec(sql)
	_, err = conn.Exec("CREATE TABLE foo (a INTEGER)")
	if err == nil {
		t.Fatalf("Need metadata update error")
	}
	if err.Error() != "unsuccessful metadata update\nTable FOO already exists\n" {
		t.Fatalf("Bad message:%v", err.Error())
	}

	// 3 records insert
	conn.Exec("insert into foo(a, b, c,h) values (1, 'a', 'b','This is a memo')")
	conn.Exec("insert into foo(a, b, c, e, g, i, j) values (2, 'A', 'B', '1999-01-25', '00:00:01', 0.1, 0.1)")
	conn.Exec("insert into foo(a, b, c, e, g, i, j) values (3, 'X', 'Y', '2001-07-05', '00:01:02', 0.2, 0.2)")

	err = conn.QueryRow("select count(*) cnt from foo").Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n != 3 {
		t.Fatalf("Error bad record count: %v", n)
	}

	rows, err := conn.Query("select a, b, c, d, e, f, g, i, j from foo")
	var a int
	var b, c string
	var d float64
	var e time.Time
	var f time.Time
	var g time.Time
	var i float64
	var j float32

	for rows.Next() {
		rows.Scan(&a, &b, &c, &d, &e, &f, &g, &i, &j)
	}

	stmt, _ := conn.Prepare("select count(*) from foo where a=? and b=? and d=? and e=? and f=? and g=?")
	ep := time.Date(1967, 8, 11, 0, 0, 0, 0, time.UTC)
	fp := time.Date(1967, 8, 11, 23, 45, 1, 0, time.UTC)
	gp, err := time.Parse("15:04:05", "23:45:01")
	err = stmt.QueryRow(1, "a", -0.123, ep, fp, gp).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n != 1 {
		t.Fatalf("Error bad record count: %v", n)
	}

	defer conn.Close()
}

/*
func TestFB3(t *testing.T) {
	conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	var sql string
	var n int

	sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='TEST_FB3'"
	err = conn.QueryRow(sql).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n > 0 {
		conn.Exec("DROP TABLE test_fb3")
	}

	sql = `
        CREATE TABLE test_fb3 (
            b BOOLEAN
        )
    `
	conn.Exec(sql)
	conn.Exec("insert into test_fb3(b) values (true)")
	conn.Exec("insert into test_fb3(b) values (false)")
    var b bool
	err = conn.QueryRow("select * from test_fb3 where b is true").Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != true{
		conn.Exec("Invalid boolean value")
	}
	err = conn.QueryRow("select * from test_fb3 where b is false").Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != false{
		conn.Exec("Invalid boolean value")
	}

	stmt, _ := conn.Prepare("select * from test_fb3 where b=?")
	err = stmt.QueryRow(true).Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != false{
		conn.Exec("Invalid boolean value")
	}

	defer conn.Close()
}
*/
<commit_msg>test for error treatment.<commit_after>/*******************************************************************************
The MIT License (MIT)

Copyright (c) 2013 Hajime Nakagami

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*******************************************************************************/

package firebirdsql

import (
	"database/sql"
	"testing"
	"time"
)

func TestBasic(t *testing.T) {
	conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	var sql string
	var n int

	sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='FOO'"
	err = conn.QueryRow(sql).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n > 0 {
		conn.Exec("DROP TABLE foo")
	}

	sql = `
        CREATE TABLE foo (
            a INTEGER NOT NULL,
            b VARCHAR(30) NOT NULL UNIQUE,
            c VARCHAR(1024),
            d DECIMAL(16,3) DEFAULT -0.123,
            e DATE DEFAULT '1967-08-11',
            f TIMESTAMP DEFAULT '1967-08-11 23:45:01',
            g TIME DEFAULT '23:45:01',
            h BLOB SUB_TYPE 1,
            i DOUBLE PRECISION DEFAULT 0.0,
            j FLOAT DEFAULT 0.0,
            PRIMARY KEY (a),
            CONSTRAINT CHECK_A CHECK (a <> 0)
        )
    `
	conn.Exec(sql)
	_, err = conn.Exec("CREATE TABLE foo (a INTEGER)")
	if err == nil {
		t.Fatalf("Need metadata update error")
	}
	if err.Error() != "unsuccessful metadata update\nTable FOO already exists\n" {
		t.Fatalf("Bad message:%v", err.Error())
	}

	// 3 records insert
	conn.Exec("insert into foo(a, b, c,h) values (1, 'a', 'b','This is a memo')")
	conn.Exec("insert into foo(a, b, c, e, g, i, j) values (2, 'A', 'B', '1999-01-25', '00:00:01', 0.1, 0.1)")
	conn.Exec("insert into foo(a, b, c, e, g, i, j) values (3, 'X', 'Y', '2001-07-05', '00:01:02', 0.2, 0.2)")

	err = conn.QueryRow("select count(*) cnt from foo").Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n != 3 {
		t.Fatalf("Error bad record count: %v", n)
	}

	rows, err := conn.Query("select a, b, c, d, e, f, g, i, j from foo")
	var a int
	var b, c string
	var d float64
	var e time.Time
	var f time.Time
	var g time.Time
	var i float64
	var j float32

	for rows.Next() {
		rows.Scan(&a, &b, &c, &d, &e, &f, &g, &i, &j)
	}

	stmt, _ := conn.Prepare("select count(*) from foo where a=? and b=? and d=? and e=? and f=? and g=?")
	ep := time.Date(1967, 8, 11, 0, 0, 0, 0, time.UTC)
	fp := time.Date(1967, 8, 11, 23, 45, 1, 0, time.UTC)
	gp, err := time.Parse("15:04:05", "23:45:01")
	err = stmt.QueryRow(1, "a", -0.123, ep, fp, gp).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n != 1 {
		t.Fatalf("Error bad record count: %v", n)
	}

	defer conn.Close()
}

func TestError(t *testing.T) {
	conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	_, err = conn.Exec("bad sql")
	if err != nil {
		t.Fatalf("Error conn.Exec(): %v", err)
	}
}

/*
func TestFB3(t *testing.T) {
	conn, err := sql.Open("firebirdsql", "sysdba:masterkey@localhost:3050/tmp/go_test.fdb")
	if err != nil {
		t.Fatalf("Error connecting: %v", err)
	}
	var sql string
	var n int

	sql = "SELECT Count(*) FROM rdb$relations where rdb$relation_name='TEST_FB3'"
	err = conn.QueryRow(sql).Scan(&n)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if n > 0 {
		conn.Exec("DROP TABLE test_fb3")
	}

	sql = `
        CREATE TABLE test_fb3 (
            b BOOLEAN
        )
    `
	conn.Exec(sql)
	conn.Exec("insert into test_fb3(b) values (true)")
	conn.Exec("insert into test_fb3(b) values (false)")
    var b bool
	err = conn.QueryRow("select * from test_fb3 where b is true").Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != true{
		conn.Exec("Invalid boolean value")
	}
	err = conn.QueryRow("select * from test_fb3 where b is false").Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != false{
		conn.Exec("Invalid boolean value")
	}

	stmt, _ := conn.Prepare("select * from test_fb3 where b=?")
	err = stmt.QueryRow(true).Scan(&b)
	if err != nil {
		t.Fatalf("Error QueryRow: %v", err)
	}
	if b != false{
		conn.Exec("Invalid boolean value")
	}

	defer conn.Close()
}
*/
<|endoftext|>


<commit_before>// An introspect page is mapped into a Collection which is basically a
// list of Element.

package main

import "fmt"
import "log"

import "github.com/moovweb/gokogiri/xml"

// A Collection describes and contains a list of Elements.
type Collection struct {
	url      string
	descCol  DescCollection
	// The node containing the whole XML, for instance the whole
	// loaded XML Page.
	rootNode     xml.Node
	elements []Element
}

type Elements []Element

type Element struct {
	node xml.Node
	desc DescElement
}

// This contains informations to generate and query a Collection
type DescCollection struct {
	// Names of arguments required from the user to get datas from
	// introspect
	PageArgs    []string
	// A function that takes the list of arguments specified by
	// the user
	PageBuilder (func([]string) Sourcer)
	// The root Xpath
	BaseXpath   string
	// Description of Collection's Elements
	DescElt     DescElement
	// Name of the attribute used to search in the collection
	PrimaryField string
}

type DescElement struct {
	// Xpath used to generate the short version of an element
	ShortDetailXpath string
	// Used to generate the long version of an element
	LongDetail       LongFormatter
}

func (col *Collection) Init() {
	ss, _ := col.rootNode.Search(col.descCol.BaseXpath + "/*")
	col.elements = make([]Element, len(ss))
	for i, s := range ss {
		col.elements[i] = Element{node: s, desc: col.descCol.DescElt}
	}
}

func (col *Collection) SearchXpathFuzzy(pattern string) string{
	return col.descCol.BaseXpath + "/*/" + col.descCol.PrimaryField + "[contains(text(),'" + pattern + "')]/.."
}

func (col *Collection) SearchXpathStrict(pattern string) string{
	return col.descCol.BaseXpath + "/*/" + col.descCol.PrimaryField + "[text()='" + pattern + "']/.."
}

func (col *Collection) SearchFuzzy(pattern string) Elements {
	return col.Search(col.SearchXpathFuzzy, pattern)
}

func (col *Collection) SearchStrict(pattern string) Elements {
	return col.Search(col.SearchXpathStrict, pattern)
}

func (col *Collection) SearchFuzzyUnique(pattern string) Element {
	res := col.SearchFuzzy(pattern)
	if len(res) > 1 {
		fmt.Printf("Pattern %s matches:", pattern)
		for _ ,e := range(res) {
			fmt.Printf("\t%s", e)
		}
		log.Fatal("Pattern must match exactly one element")
	}
	return res[0]
}

func (col *Collection) Search(searchPredicate (func(string) string), pattern string) Elements {
	ss, _ := col.rootNode.Search(searchPredicate(pattern))
	var elements []Element = make([]Element, len(ss))
	for i, s := range ss {
		elements[i] = Element{node: s, desc: col.descCol.DescElt}
	}
	return Elements(elements)
}

func (e Element) GetField(field string) string {
	s, _ := e.node.Search(fmt.Sprintf("%s/text()", field))
	for i, _ := range s {
		if s[i].String() != s[0].String() {
			log.Fatal(fmt.Sprintf("All fields values must be equal (values: %s)", s))
		}
	}
	if len(s) < 1 {
		log.Fatal(fmt.Sprintf("Field %s has not be found.", field))
	}
	return s[0].String()
}

// Several representations of resources
type Shower interface {
	Long()
	Short()
	Xml()
}

func (e Element) Xml() {
	fmt.Printf("%s", e.node)
}
func (elts Elements) Xml() {
	for _, e := range elts {
		e.Xml()
	}
}
func (c Collection) Xml() {
	fmt.Printf("%s", c.rootNode)
}

func (e Element) Short() {
	s, _ := e.node.Search(e.desc.ShortDetailXpath)
	if len(s) != 1 {
		log.Fatal("Xpath '" + e.desc.ShortDetailXpath + "' is not valid (verify ShortDetailXpath)")
	}
	fmt.Printf("%s\n", s[0])
}
func (col Collection) Short() {
	Elements(col.elements).Short()
}
func (elts Elements) Short() {
	for _, e := range elts {
		e.Short()
	}
}
func (e Element) Long() {
	e.desc.LongDetail.LongFormat(e)
}
func (col Collection) Long() {
	Elements(col.elements).Long()
}
func (elts Elements) Long() {
	for _, e := range elts {
		e.Long()
		fmt.Printf("\n")
	}
}

// This is used to show the long version of an Element.
type LongFormatter interface {
	LongFormat(e Element)
}

type LongFormatFn (func(Element))
type LongFormatXpaths []string

func (fn LongFormatFn) LongFormat(e Element) {
	fn(e)
}
func (xpaths LongFormatXpaths) LongFormat(e Element) {
	for _, xpath := range xpaths {
		s, _ := e.node.Search(xpath + "/text()")
		if len(s) == 1 {
			fmt.Printf("%s ", Pretty(s))
		}
	}
}

<commit_msg>collection: long format uses uitable<commit_after>// An introspect page is mapped into a Collection which is basically a
// list of Element.

package main

import "fmt"
import "log"

import "github.com/moovweb/gokogiri/xml"
import "github.com/gosuri/uitable"

// A Collection describes and contains a list of Elements.
type Collection struct {
	url      string
	descCol  DescCollection
	// The node containing the whole XML, for instance the whole
	// loaded XML Page.
	rootNode     xml.Node
	elements []Element
}

type Elements []Element

type Element struct {
	node xml.Node
	desc DescElement
}

// This contains informations to generate and query a Collection
type DescCollection struct {
	// Names of arguments required from the user to get datas from
	// introspect
	PageArgs    []string
	// A function that takes the list of arguments specified by
	// the user
	PageBuilder (func([]string) Sourcer)
	// The root Xpath
	BaseXpath   string
	// Description of Collection's Elements
	DescElt     DescElement
	// Name of the attribute used to search in the collection
	PrimaryField string
}

type DescElement struct {
	// Xpath used to generate the short version of an element
	ShortDetailXpath string
	// Used to generate the long version of an element
	LongDetail       LongFormatter
}

func (col *Collection) Init() {
	ss, _ := col.rootNode.Search(col.descCol.BaseXpath + "/*")
	col.elements = make([]Element, len(ss))
	for i, s := range ss {
		col.elements[i] = Element{node: s, desc: col.descCol.DescElt}
	}
}

func (col *Collection) SearchXpathFuzzy(pattern string) string{
	return col.descCol.BaseXpath + "/*/" + col.descCol.PrimaryField + "[contains(text(),'" + pattern + "')]/.."
}

func (col *Collection) SearchXpathStrict(pattern string) string{
	return col.descCol.BaseXpath + "/*/" + col.descCol.PrimaryField + "[text()='" + pattern + "']/.."
}

func (col *Collection) SearchFuzzy(pattern string) Elements {
	return col.Search(col.SearchXpathFuzzy, pattern)
}

func (col *Collection) SearchStrict(pattern string) Elements {
	return col.Search(col.SearchXpathStrict, pattern)
}

func (col *Collection) SearchFuzzyUnique(pattern string) Element {
	res := col.SearchFuzzy(pattern)
	if len(res) > 1 {
		fmt.Printf("Pattern %s matches:", pattern)
		for _ ,e := range(res) {
			fmt.Printf("\t%s", e)
		}
		log.Fatal("Pattern must match exactly one element")
	}
	return res[0]
}

func (col *Collection) Search(searchPredicate (func(string) string), pattern string) Elements {
	ss, _ := col.rootNode.Search(searchPredicate(pattern))
	var elements []Element = make([]Element, len(ss))
	for i, s := range ss {
		elements[i] = Element{node: s, desc: col.descCol.DescElt}
	}
	return Elements(elements)
}

func (e Element) GetField(field string) string {
	s, _ := e.node.Search(fmt.Sprintf("%s/text()", field))
	for i, _ := range s {
		if s[i].String() != s[0].String() {
			log.Fatal(fmt.Sprintf("All fields values must be equal (values: %s)", s))
		}
	}
	if len(s) < 1 {
		log.Fatal(fmt.Sprintf("Field %s has not be found.", field))
	}
	return s[0].String()
}

// Several representations of resources
type Shower interface {
	Long()
	Short()
	Xml()
}

func (e Element) Xml() {
	fmt.Printf("%s", e.node)
}
func (elts Elements) Xml() {
	for _, e := range elts {
		e.Xml()
	}
}
func (c Collection) Xml() {
	fmt.Printf("%s", c.rootNode)
}

func (e Element) Short() {
	s, _ := e.node.Search(e.desc.ShortDetailXpath)
	if len(s) != 1 {
		log.Fatal("Xpath '" + e.desc.ShortDetailXpath + "' is not valid (verify ShortDetailXpath)")
	}
	fmt.Printf("%s\n", s[0])
}
func (col Collection) Short() {
	Elements(col.elements).Short()
}
func (elts Elements) Short() {
	for _, e := range elts {
		e.Short()
	}
}
func (e Element) Long() {
	e.desc.LongDetail.LongFormat(FORMAT_TEXT, e)
}
func (col Collection) Long() {
	Elements(col.elements).Long()
}
func (elts Elements) Long() {
	for i, e := range elts {
		format := FORMAT_TABLE
		if i == 0 {
			format = FORMAT_TABLE_HEADER
		}
		e.desc.LongDetail.LongFormat(format, e)
		fmt.Printf("\n")
	}
}

// This is used to show the long version of an Element.
type LongFormatter interface {
	LongFormat(f Format, e Element)
}

type LongFormatFn (func(Element))
type LongFormatXpaths []string

type Format uint8
const (
	FORMAT_TEXT Format = 1
	FORMAT_TABLE_HEADER Format   = 2
	FORMAT_TABLE Format = 3
)

func (fn LongFormatFn) LongFormat(format Format, e Element) {
	fn(e)
}

func (xpaths LongFormatXpaths) LongFormat(format Format, e Element) {
	if format == FORMAT_TABLE_HEADER || format == FORMAT_TABLE {
		longFormatTable(format, e, xpaths)
	} else {
		for _, xpath := range xpaths {
			s, _ := e.node.Search(xpath + "/text()")
			if len(s) == 1 {
				fmt.Printf("%s ", Pretty(s))
			}
		}
	}
}

func longFormatTable(format Format, e Element, xpaths LongFormatXpaths) {
	table := uitable.New()
	table.MaxColWidth = 80

	if format == FORMAT_TABLE_HEADER {
		tmp := make([]interface{}, len(xpaths))
		for i, v := range xpaths {
			tmp[i] = v
		}
		table.AddRow(tmp...)
	}

	tmp := make([]interface{}, len(xpaths))
	for i, xpath := range xpaths {
		s, _ := e.node.Search(xpath + "/text()")
		if len(s) == 1 {
			tmp[i] = Pretty(s)
		}
	}
	table.AddRow(tmp...)

	fmt.Print(table)
}

<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"errors"

	"fmt"
	"github.com/thiderman/drunkenfall/websockets"
	"golang.org/x/net/websocket"
)

// Setup variables for the cookies. Can be used outside of this file.
var (
	CookieStoreKey = []byte("dtf")
	CookieStore    = sessions.NewCookieStore(CookieStoreKey)
)

// Server is an abstraction that runs via a web interface
type Server struct {
	DB     *Database
	router http.Handler
	logger http.Handler
	ws     *websockets.Server
}

// JSONMessage defines a message to be returned to the frontend
type JSONMessage struct {
	Message  string `json:"message"`
	Redirect string `json:"redirect"`
}

// PermissionRedirect is an explicit permission failure
type PermissionRedirect JSONMessage

// TournamentMessage returns a single tournament
type TournamentMessage struct {
	Tournament *Tournament `json:"tournament"`
}

// UpdateMessage returns an update to the current tournament
type UpdateMessage TournamentMessage

// UpdateMatchMessage returns an update to the current match
type UpdateMatchMessage struct {
	Match *Match `json:"match"`
}

// TournamentList returns a list with tournaments
type TournamentList struct {
	Tournaments []*Tournament `json:"tournaments"`
}

// UpdateStateMessage returns an update to the current match
type UpdateStateMessage TournamentList

// NewRequest is the request to make a new tournament
type NewRequest struct {
	Name string `json:"name"`
	ID   string `json:"id"`
	Fake bool   `json:"fake"`
}

// CommitPlayer is one state for a player in a commit message
type CommitPlayer struct {
	Ups    int    `json:"ups"`
	Downs  int    `json:"downs"`
	Shot   bool   `json:"shot"`
	Reason string `json:"reason"`
}

// CommitRequest is a request to commit a match state
type CommitRequest struct {
	State []CommitPlayer `json:"state"`
}

// NewServer instantiates a server with an active database
func NewServer(db *Database) *Server {
	s := Server{DB: db}
	s.ws = websockets.NewServer()
	s.router = s.BuildRouter(s.ws)

	return &s
}

// RegisterHandlersAndListeners registers the routes and the websocket listeners
func (s *Server) RegisterHandlersAndListeners() {
	http.Handle("/", s.router)
	s.logger = handlers.LoggingHandler(os.Stdout, s.router)

	// Also websocket listener
	go s.ws.Listen()
}

// NewHandler shows the page to create a new tournament
func (s *Server) NewHandler(w http.ResponseWriter, r *http.Request) {
	var req NewRequest
	var t *Tournament

	if !HasPermission(r, PermissionProducer) {
		PermissionFailure(w, r, "Cannot create match unless producer")
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Fatal(err)
	}

	if req.Fake {
		t = SetupFakeTournament(s)
	} else {
		t, _ = NewTournament(req.Name, req.ID, s)
	}

	log.Printf("Created tournament %s!", t.Name)

	s.DB.Tournaments = append(s.DB.Tournaments, t)
	s.DB.tournamentRef[t.ID] = t

	s.Redirect(w, t.URL())
}

// TournamentHandler returns the current state of the tournament
func (s *Server) TournamentHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	tm := s.DB.tournamentRef[vars["id"]]
	out := &TournamentMessage{tm}

	data, err := json.Marshal(out)
	if err != nil {
		log.Fatal(err)
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// JoinHandler shows the tournament view and handles tournaments
func (s *Server) JoinHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionPlayer) {
		PermissionFailure(w, r, "You need to sign in to join a tournament")
		return
	}

	tm := s.getTournament(r)
	p := PersonFromSession(s, r)

	err := tm.AddPlayer(p)
	if err != nil {
		PermissionFailure(w, r, err.Error())
		return
	}

	log.Printf("%s has joined %s!", p.Name, tm.Name)
	s.Redirect(w, tm.URL())
}

// StartTournamentHandler starts tournaments
func (s *Server) StartTournamentHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot start tournament unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	err := tm.StartTournament()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, tm.URL())
}

// UsurpTournamentHandler usurps tournaments
func (s *Server) UsurpTournamentHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot usurp tournament unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	err := tm.UsurpTournament()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, tm.URL())
}

// NextHandler sets the tournament up to play the next match
func (s *Server) NextHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot goto next match unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	m, err := tm.NextMatch()
	m.SetTime()

	tm.Persist() // TODO(thiderman): Move into NextMatch, probably. Should not be here.
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, m.URL())
}

// Common function for usage in MatchHandler
type MatchFunctor func(w http.ResponseWriter, r *http.Request, match *Match) error

// MatchHandler is the common function for match operations.
func (s *Server) MatchHandler(w http.ResponseWriter, r *http.Request, functor MatchFunctor) {
	if !HasPermission(r, PermissionJudge) {
		PermissionFailure(w, r, "Cannot stop match unless judge or above")
		return
	}

	m := s.getMatch(r)
	log.Printf("Got match %s", m.String())
	err := functor(w, r, m)
	if err != nil {
		msg := err.Error()
		log.Print(msg)
		ErrorResponse(w, r, msg)
		return
	}

	data, err := json.Marshal(UpdateMessage{
		Tournament: m.Tournament,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// MatchEndHandler ends matches
func (s *Server) MatchEndHandler(w http.ResponseWriter, r *http.Request) {
	s.MatchHandler(w, r, func(w http.ResponseWriter, r *http.Request, m *Match) error {
		if !m.IsStarted() {
			error_msg := fmt.Sprintf("Cannot end the match `%s` that is in not started.", m.String())
			return errors.New(error_msg)
		}
		log.Printf("%s ended", m.String())
		m.End()
		return nil
	})
}

// MatchStartHandler starts matches
func (s *Server) MatchStartHandler(w http.ResponseWriter, r *http.Request) {
	s.MatchHandler(w, r, func(w http.ResponseWriter, r *http.Request, m *Match) error {
		log.Print("Trying to start match!")
		if m.IsStarted() {
			log.Print("Trying to send error. Wäääääääää")
			error_msg := fmt.Sprintf("Cannot start the match `%s` that is already in progress.", m.String())
			return errors.New(error_msg)
		}
		log.Printf("%s started", m.String())
		err := m.Start()
		if err != nil {
			log.Fatal(err)
		}
		return nil
	})
}
// MatchCommitHandler commits a single round of a match
func (s *Server) MatchCommitHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionJudge) {
		PermissionFailure(w, r, "Cannot commit match unless judge or above")
		return
	}

	var req CommitRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Print(err)
		return
	}

	c := NewMatchCommit(req)
	m := s.getMatch(r)
	m.Commit(c)

	data, err := json.Marshal(UpdateMatchMessage{
		Match: m,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)

	return
}

// TournamentListHandler returns a list of all tournaments
func (s *Server) TournamentListHandler(w http.ResponseWriter, r *http.Request) {
	data, err := json.Marshal(&TournamentList{
		Tournaments: s.DB.Tournaments,
	})
	if err != nil {
		log.Fatal(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

// BuildRouter sets up the routes
func (s *Server) BuildRouter(ws *websockets.Server) http.Handler {
	n := mux.NewRouter()
	a := n.PathPrefix("/api").Subrouter()
	r := a.PathPrefix("/towerfall").Subrouter()

	r.HandleFunc("/tournament/", s.TournamentListHandler)
	r.HandleFunc("/tournament/{id}/", s.TournamentHandler)
	// TODO: Normalize for all to use /tournament
	r.HandleFunc("/new/", s.NewHandler)
	r.HandleFunc("/{id}/start/", s.StartTournamentHandler)
	r.HandleFunc("/{id}/usurp/", s.UsurpTournamentHandler)
	r.HandleFunc("/{id}/join/", s.JoinHandler)
	r.HandleFunc("/{id}/next/", s.NextHandler)

	// Install the websockets
	r.Handle("/auto-updater", websocket.Handler(ws.OnConnected))

	// Handle Facebook
	s.FacebookRouter(a)

	m := r.PathPrefix("/tournament/{id}/{kind:(tryout|runnerup|semi|final)}/{index:[0-9]+}").Subrouter()

	m.HandleFunc("/end/", s.MatchEndHandler)
	m.HandleFunc("/start/", s.MatchStartHandler)

	m.HandleFunc("/commit/", s.MatchCommitHandler)

	return n
}

// Serve serves forever
func (s *Server) Serve() error {
	log.Print("Listening on :42001")
	return http.ListenAndServe(":42001", s.logger)
}

// SendWebsocketUpdate sends an update to all listening sockets
func (s *Server) SendWebsocketUpdate() {
	msg := websockets.Message{
		Data: UpdateStateMessage{
			Tournaments: s.DB.Tournaments,
		},
	}

	s.ws.SendAll(&msg)
}

func (s *Server) getMatch(r *http.Request) *Match {
	var m *Match
	vars := mux.Vars(r)

	tm := s.DB.tournamentRef[vars["id"]]
	kind := vars["kind"]
	index, _ := strconv.Atoi(vars["index"])

	if kind == "tryout" {
		m = tm.Tryouts[index]
	} else if kind == "semi" {
		m = tm.Semis[index]
	} else if kind == "final" {
		m = tm.Final
	}

	return m
}

func (s *Server) getTournament(r *http.Request) *Tournament {
	vars := mux.Vars(r)
	tm := s.DB.tournamentRef[vars["id"]]
	return tm
}

// Redirect creates a JSON Redirect
func (s *Server) Redirect(w http.ResponseWriter, url string) {
	data, err := json.Marshal(JSONMessage{
		Redirect: url,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// HasPermission checks that the user is allowed to do an action
func HasPermission(r *http.Request, lvl int) bool {
	s, _ := CookieStore.Get(r, "session")
	l, ok := s.Values["userlevel"]
	if !ok {
		// log.Print("Userlevel missing for auth")
		return false
	}

	log.Print(fmt.Sprintf("Auth check: %s: %d", s.Values, lvl))
	return l.(int) >= lvl
}

// PermissionFailure returns an error 401
func PermissionFailure(w http.ResponseWriter, r *http.Request, msg string) {
	GeneralResponse(w, r, http.StatusUnauthorized, msg)
}

// ErrorResponse returns an error with the statuscode of 401
func ErrorResponse(w http.ResponseWriter, r *http.Request, msg string) {
	GeneralResponse(w, r, http.StatusBadRequest, msg)
}

// ErrorResponse returns an error with the statuscode of 401
func GeneralResponse(w http.ResponseWriter, r *http.Request, status int, msg string) {
	data, err := json.Marshal(PermissionRedirect{
		Message:  msg,
		Redirect: "/",
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_, _ = w.Write(data)
}

func main() {
	db, err := NewDatabase("production.db")
	if err != nil {
		log.Fatal(err)
	}

	s := NewServer(db)
	db.Server = s

	err = db.LoadTournaments()
	if err != nil {
		log.Fatal(err)
	}

	// Set up the paths and the websocket listeners
	s.RegisterHandlersAndListeners()
	err = s.Serve()

	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Add database migration to app startup<commit_after>package main

import (
	"encoding/json"
	"github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"strconv"
	"errors"

	"fmt"
	"github.com/thiderman/drunkenfall/websockets"
	"golang.org/x/net/websocket"
)

// Setup variables for the cookies. Can be used outside of this file.
var (
	CookieStoreKey = []byte("dtf")
	CookieStore    = sessions.NewCookieStore(CookieStoreKey)
)

// Server is an abstraction that runs via a web interface
type Server struct {
	DB     *Database
	router http.Handler
	logger http.Handler
	ws     *websockets.Server
}

// JSONMessage defines a message to be returned to the frontend
type JSONMessage struct {
	Message  string `json:"message"`
	Redirect string `json:"redirect"`
}

// PermissionRedirect is an explicit permission failure
type PermissionRedirect JSONMessage

// TournamentMessage returns a single tournament
type TournamentMessage struct {
	Tournament *Tournament `json:"tournament"`
}

// UpdateMessage returns an update to the current tournament
type UpdateMessage TournamentMessage

// UpdateMatchMessage returns an update to the current match
type UpdateMatchMessage struct {
	Match *Match `json:"match"`
}

// TournamentList returns a list with tournaments
type TournamentList struct {
	Tournaments []*Tournament `json:"tournaments"`
}

// UpdateStateMessage returns an update to the current match
type UpdateStateMessage TournamentList

// NewRequest is the request to make a new tournament
type NewRequest struct {
	Name string `json:"name"`
	ID   string `json:"id"`
	Fake bool   `json:"fake"`
}

// CommitPlayer is one state for a player in a commit message
type CommitPlayer struct {
	Ups    int    `json:"ups"`
	Downs  int    `json:"downs"`
	Shot   bool   `json:"shot"`
	Reason string `json:"reason"`
}

// CommitRequest is a request to commit a match state
type CommitRequest struct {
	State []CommitPlayer `json:"state"`
}

// NewServer instantiates a server with an active database
func NewServer(db *Database) *Server {
	s := Server{DB: db}
	s.ws = websockets.NewServer()
	s.router = s.BuildRouter(s.ws)

	return &s
}

// RegisterHandlersAndListeners registers the routes and the websocket listeners
func (s *Server) RegisterHandlersAndListeners() {
	http.Handle("/", s.router)
	s.logger = handlers.LoggingHandler(os.Stdout, s.router)

	// Also websocket listener
	go s.ws.Listen()
}

// NewHandler shows the page to create a new tournament
func (s *Server) NewHandler(w http.ResponseWriter, r *http.Request) {
	var req NewRequest
	var t *Tournament

	if !HasPermission(r, PermissionProducer) {
		PermissionFailure(w, r, "Cannot create match unless producer")
		return
	}

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Fatal(err)
	}

	if req.Fake {
		t = SetupFakeTournament(s)
	} else {
		t, _ = NewTournament(req.Name, req.ID, s)
	}

	log.Printf("Created tournament %s!", t.Name)

	s.DB.Tournaments = append(s.DB.Tournaments, t)
	s.DB.tournamentRef[t.ID] = t

	s.Redirect(w, t.URL())
}

// TournamentHandler returns the current state of the tournament
func (s *Server) TournamentHandler(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)

	tm := s.DB.tournamentRef[vars["id"]]
	out := &TournamentMessage{tm}

	data, err := json.Marshal(out)
	if err != nil {
		log.Fatal(err)
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// JoinHandler shows the tournament view and handles tournaments
func (s *Server) JoinHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionPlayer) {
		PermissionFailure(w, r, "You need to sign in to join a tournament")
		return
	}

	tm := s.getTournament(r)
	p := PersonFromSession(s, r)

	err := tm.AddPlayer(p)
	if err != nil {
		PermissionFailure(w, r, err.Error())
		return
	}

	log.Printf("%s has joined %s!", p.Name, tm.Name)
	s.Redirect(w, tm.URL())
}

// StartTournamentHandler starts tournaments
func (s *Server) StartTournamentHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot start tournament unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	err := tm.StartTournament()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, tm.URL())
}

// UsurpTournamentHandler usurps tournaments
func (s *Server) UsurpTournamentHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot usurp tournament unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	err := tm.UsurpTournament()
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, tm.URL())
}

// NextHandler sets the tournament up to play the next match
func (s *Server) NextHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionCommentator) {
		PermissionFailure(w, r, "Cannot goto next match unless commentator or above")
		return
	}

	tm := s.getTournament(r)
	m, err := tm.NextMatch()
	m.SetTime()

	tm.Persist() // TODO(thiderman): Move into NextMatch, probably. Should not be here.
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	s.Redirect(w, m.URL())
}

// Common function for usage in MatchHandler
type MatchFunctor func(w http.ResponseWriter, r *http.Request, match *Match) error

// MatchHandler is the common function for match operations.
func (s *Server) MatchHandler(w http.ResponseWriter, r *http.Request, functor MatchFunctor) {
	if !HasPermission(r, PermissionJudge) {
		PermissionFailure(w, r, "Cannot stop match unless judge or above")
		return
	}

	m := s.getMatch(r)
	log.Printf("Got match %s", m.String())
	err := functor(w, r, m)
	if err != nil {
		msg := err.Error()
		log.Print(msg)
		ErrorResponse(w, r, msg)
		return
	}

	data, err := json.Marshal(UpdateMessage{
		Tournament: m.Tournament,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// MatchEndHandler ends matches
func (s *Server) MatchEndHandler(w http.ResponseWriter, r *http.Request) {
	s.MatchHandler(w, r, func(w http.ResponseWriter, r *http.Request, m *Match) error {
		if !m.IsStarted() {
			error_msg := fmt.Sprintf("Cannot end the match `%s` that is in not started.", m.String())
			return errors.New(error_msg)
		}
		log.Printf("%s ended", m.String())
		m.End()
		return nil
	})
}

// MatchStartHandler starts matches
func (s *Server) MatchStartHandler(w http.ResponseWriter, r *http.Request) {
	s.MatchHandler(w, r, func(w http.ResponseWriter, r *http.Request, m *Match) error {
		log.Print("Trying to start match!")
		if m.IsStarted() {
			log.Print("Trying to send error. Wäääääääää")
			error_msg := fmt.Sprintf("Cannot start the match `%s` that is already in progress.", m.String())
			return errors.New(error_msg)
		}
		log.Printf("%s started", m.String())
		err := m.Start()
		if err != nil {
			log.Fatal(err)
		}
		return nil
	})
}
// MatchCommitHandler commits a single round of a match
func (s *Server) MatchCommitHandler(w http.ResponseWriter, r *http.Request) {
	if !HasPermission(r, PermissionJudge) {
		PermissionFailure(w, r, "Cannot commit match unless judge or above")
		return
	}

	var req CommitRequest

	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		log.Print(err)
		return
	}

	err = json.Unmarshal(body, &req)
	if err != nil {
		log.Print(err)
		return
	}

	c := NewMatchCommit(req)
	m := s.getMatch(r)
	m.Commit(c)

	data, err := json.Marshal(UpdateMatchMessage{
		Match: m,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)

	return
}

// TournamentListHandler returns a list of all tournaments
func (s *Server) TournamentListHandler(w http.ResponseWriter, r *http.Request) {
	data, err := json.Marshal(&TournamentList{
		Tournaments: s.DB.Tournaments,
	})
	if err != nil {
		log.Fatal(err)
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

// BuildRouter sets up the routes
func (s *Server) BuildRouter(ws *websockets.Server) http.Handler {
	n := mux.NewRouter()
	a := n.PathPrefix("/api").Subrouter()
	r := a.PathPrefix("/towerfall").Subrouter()

	r.HandleFunc("/tournament/", s.TournamentListHandler)
	r.HandleFunc("/tournament/{id}/", s.TournamentHandler)
	// TODO: Normalize for all to use /tournament
	r.HandleFunc("/new/", s.NewHandler)
	r.HandleFunc("/{id}/start/", s.StartTournamentHandler)
	r.HandleFunc("/{id}/usurp/", s.UsurpTournamentHandler)
	r.HandleFunc("/{id}/join/", s.JoinHandler)
	r.HandleFunc("/{id}/next/", s.NextHandler)

	// Install the websockets
	r.Handle("/auto-updater", websocket.Handler(ws.OnConnected))

	// Handle Facebook
	s.FacebookRouter(a)

	m := r.PathPrefix("/tournament/{id}/{kind:(tryout|runnerup|semi|final)}/{index:[0-9]+}").Subrouter()

	m.HandleFunc("/end/", s.MatchEndHandler)
	m.HandleFunc("/start/", s.MatchStartHandler)

	m.HandleFunc("/commit/", s.MatchCommitHandler)

	return n
}

// Serve serves forever
func (s *Server) Serve() error {
	log.Print("Listening on :42001")
	return http.ListenAndServe(":42001", s.logger)
}

// SendWebsocketUpdate sends an update to all listening sockets
func (s *Server) SendWebsocketUpdate() {
	msg := websockets.Message{
		Data: UpdateStateMessage{
			Tournaments: s.DB.Tournaments,
		},
	}

	s.ws.SendAll(&msg)
}

func (s *Server) getMatch(r *http.Request) *Match {
	var m *Match
	vars := mux.Vars(r)

	tm := s.DB.tournamentRef[vars["id"]]
	kind := vars["kind"]
	index, _ := strconv.Atoi(vars["index"])

	if kind == "tryout" {
		m = tm.Tryouts[index]
	} else if kind == "semi" {
		m = tm.Semis[index]
	} else if kind == "final" {
		m = tm.Final
	}

	return m
}

func (s *Server) getTournament(r *http.Request) *Tournament {
	vars := mux.Vars(r)
	tm := s.DB.tournamentRef[vars["id"]]
	return tm
}

// Redirect creates a JSON Redirect
func (s *Server) Redirect(w http.ResponseWriter, url string) {
	data, err := json.Marshal(JSONMessage{
		Redirect: url,
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_, _ = w.Write(data)
}

// HasPermission checks that the user is allowed to do an action
func HasPermission(r *http.Request, lvl int) bool {
	s, _ := CookieStore.Get(r, "session")
	l, ok := s.Values["userlevel"]
	if !ok {
		// log.Print("Userlevel missing for auth")
		return false
	}

	log.Print(fmt.Sprintf("Auth check: %s: %d", s.Values, lvl))
	return l.(int) >= lvl
}

// PermissionFailure returns an error 401
func PermissionFailure(w http.ResponseWriter, r *http.Request, msg string) {
	GeneralResponse(w, r, http.StatusUnauthorized, msg)
}

// ErrorResponse returns an error with the statuscode of 401
func ErrorResponse(w http.ResponseWriter, r *http.Request, msg string) {
	GeneralResponse(w, r, http.StatusBadRequest, msg)
}

// ErrorResponse returns an error with the statuscode of 401
func GeneralResponse(w http.ResponseWriter, r *http.Request, status int, msg string) {
	data, err := json.Marshal(PermissionRedirect{
		Message:  msg,
		Redirect: "/",
	})
	if err != nil {
		log.Print(err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_, _ = w.Write(data)
}

func main() {
	// Instantiate the database
	db, err := NewDatabase("production.db")
	if err != nil {
		log.Fatal(err)
	}

	// Apply any applicable migrations
	err = Migrate(db.DB)
	if err != nil {
		log.Fatal(err)
	}

	// Create the server instance...
	s := NewServer(db)
	// ...and give the db a reference to the server.
	// Not the cleanest, but y'know... here we are.
	db.Server = s

	// Load tournaments from the database
	err = db.LoadTournaments()
	if err != nil {
		log.Fatal(err)
	}

	// Set up the paths and the websocket listeners
	s.RegisterHandlersAndListeners()

	// Actually start serving
	if err := s.Serve(); err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cldr

import (
	"encoding/xml"
	"regexp"
	"strconv"
)

// Elem is implemented by every XML element.
type Elem interface {
	setEnclosing(Elem)
	setName(string)
	enclosing() Elem

	GetCommon() *Common
}

type hidden struct {
	CharData string `xml:",chardata"`
	Alias    *struct {
		Common
		Source string `xml:"source,attr"`
		Path   string `xml:"path,attr"`
	} `xml:"alias"`
	Def *struct {
		Common
		Choice string `xml:"choice,attr,omitempty"`
		Type   string `xml:"type,attr,omitempty"`
	} `xml:"default"`
}

// Common holds several of the most common attributes and sub elements
// of an XML element.
type Common struct {
	XMLName         xml.Name
	name            string
	enclElem        Elem
	Type            string `xml:"type,attr,omitempty"`
	Reference       string `xml:"reference,attr,omitempty"`
	Alt             string `xml:"alt,attr,omitempty"`
	ValidSubLocales string `xml:"validSubLocales,attr,omitempty"`
	Draft           string `xml:"draft,attr,omitempty"`
	hidden
}

// Default returns the default type to select from the enclosed list
// or "" if no default value is specified.
func (e *Common) Default() string {
	if e.Def == nil {
		return ""
	}
	if e.Def.Choice != "" {
		return e.Def.Choice
	} else if e.Def.Type != "" {
		// Type is still used by the default element in collation.
		return e.Def.Type
	}
	return ""
}

// GetCommon returns e. It is provided such that Common implements Elem.
func (e *Common) GetCommon() *Common {
	return e
}

// Data returns the character data accumulated for this element.
func (e *Common) Data() string {
	e.CharData = charRe.ReplaceAllStringFunc(e.CharData, replaceUnicode)
	return e.CharData
}

func (e *Common) setName(s string) {
	e.name = s
}

func (e *Common) enclosing() Elem {
	return e.enclElem
}

func (e *Common) setEnclosing(en Elem) {
	e.enclElem = en
}

// Escape characters that can be escaped without further escaping the string.
var charRe = regexp.MustCompile(`&#x[0-9a-fA-F]*;|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\[abtnvfr]`)

// replaceUnicode converts hexadecimal Unicode codepoint notations to a one-rune string.
// It assumes the input string is correctly formatted.
func replaceUnicode(s string) string {
	if s[1] == '#' {
		r, _ := strconv.ParseInt(s[3:len(s)-1], 16, 32)
		return string(r)
	}
	r, _, _, _ := strconv.UnquoteChar(s, 0)
	return string(r)
}
<commit_msg>unicode/cldr: expose element name<commit_after>// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cldr

import (
	"encoding/xml"
	"regexp"
	"strconv"
)

// Elem is implemented by every XML element.
type Elem interface {
	setEnclosing(Elem)
	setName(string)
	enclosing() Elem

	GetCommon() *Common
}

type hidden struct {
	CharData string `xml:",chardata"`
	Alias    *struct {
		Common
		Source string `xml:"source,attr"`
		Path   string `xml:"path,attr"`
	} `xml:"alias"`
	Def *struct {
		Common
		Choice string `xml:"choice,attr,omitempty"`
		Type   string `xml:"type,attr,omitempty"`
	} `xml:"default"`
}

// Common holds several of the most common attributes and sub elements
// of an XML element.
type Common struct {
	XMLName         xml.Name
	name            string
	enclElem        Elem
	Type            string `xml:"type,attr,omitempty"`
	Reference       string `xml:"reference,attr,omitempty"`
	Alt             string `xml:"alt,attr,omitempty"`
	ValidSubLocales string `xml:"validSubLocales,attr,omitempty"`
	Draft           string `xml:"draft,attr,omitempty"`
	hidden
}

// Default returns the default type to select from the enclosed list
// or "" if no default value is specified.
func (e *Common) Default() string {
	if e.Def == nil {
		return ""
	}
	if e.Def.Choice != "" {
		return e.Def.Choice
	} else if e.Def.Type != "" {
		// Type is still used by the default element in collation.
		return e.Def.Type
	}
	return ""
}

// Element returns the XML element name.
func (e *Common) Element() string {
	return e.name
}

// GetCommon returns e. It is provided such that Common implements Elem.
func (e *Common) GetCommon() *Common {
	return e
}

// Data returns the character data accumulated for this element.
func (e *Common) Data() string {
	e.CharData = charRe.ReplaceAllStringFunc(e.CharData, replaceUnicode)
	return e.CharData
}

func (e *Common) setName(s string) {
	e.name = s
}

func (e *Common) enclosing() Elem {
	return e.enclElem
}

func (e *Common) setEnclosing(en Elem) {
	e.enclElem = en
}

// Escape characters that can be escaped without further escaping the string.
var charRe = regexp.MustCompile(`&#x[0-9a-fA-F]*;|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|\\x[0-9a-fA-F]{2}|\\[0-7]{3}|\\[abtnvfr]`)

// replaceUnicode converts hexadecimal Unicode codepoint notations to a one-rune string.
// It assumes the input string is correctly formatted.
func replaceUnicode(s string) string {
	if s[1] == '#' {
		r, _ := strconv.ParseInt(s[3:len(s)-1], 16, 32)
		return string(r)
	}
	r, _, _, _ := strconv.UnquoteChar(s, 0)
	return string(r)
}
<|endoftext|>


<commit_before>package uploader

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lomik/stop"
	"github.com/uber-go/zap"
)

type Option func(u *Uploader)

func Path(path string) Option {
	return func(u *Uploader) {
		u.path = path
	}
}

func ClickHouse(dsn string) Option {
	return func(u *Uploader) {
		u.clickHouseDSN = dsn
	}
}

func DataTable(t string) Option {
	return func(u *Uploader) {
		u.dataTable = t
	}
}

func DataTimeout(t time.Duration) Option {
	return func(u *Uploader) {
		u.dataTimeout = t
	}
}

func TreeTable(t string) Option {
	return func(u *Uploader) {
		u.treeTable = t
	}
}

func TreeDate(t string) Option {
	return func(u *Uploader) {
		u.treeDate = t
	}
}

func TreeTimeout(t time.Duration) Option {
	return func(u *Uploader) {
		u.treeTimeout = t
	}
}

func InProgressCallback(cb func(string) bool) Option {
	return func(u *Uploader) {
		u.inProgressCallback = cb
	}
}

func Threads(t int) Option {
	return func(u *Uploader) {
		u.threads = t
	}
}

func Logger(logger zap.Logger) Option {
	return func(u *Uploader) {
		u.logger = logger
	}
}

// Uploader upload files from local directory to clickhouse
type Uploader struct {
	stop.Struct
	sync.Mutex
	path               string
	clickHouseDSN      string
	dataTable          string
	dataTimeout        time.Duration
	treeTable          string
	treeTimeout        time.Duration
	treeDate           string
	filesUploaded      uint32 // stat "files"
	filesFailed        uint32 // stat
	threads            int
	inProgressCallback func(string) bool
	queue              chan string
	inQueue            map[string]bool // current uploading files
	treeExists         CMap            // store known keys and don't load it to clickhouse tree
	logger             zap.Logger
}

func New(options ...Option) *Uploader {

	u := &Uploader{
		path:               "/data/carbon-clickhouse/",
		dataTable:          "graphite",
		treeTable:          "graphite_tree",
		dataTimeout:        time.Minute,
		treeTimeout:        time.Minute,
		treeDate:           "2016-11-01",
		inProgressCallback: func(string) bool { return false },
		queue:              make(chan string, 1024),
		inQueue:            make(map[string]bool),
		threads:            1,
		treeExists:         NewCMap(),
		logger:             zap.New(zap.NullEncoder()),
	}

	for _, o := range options {
		o(u)
	}

	return u
}

func (u *Uploader) Start() error {
	return u.StartFunc(func() error {
		u.Go(u.watchWorker)

		for i := 0; i < u.threads; i++ {
			u.Go(u.uploadWorker)
		}

		return nil
	})
}

func (u *Uploader) Stat(send func(metric string, value float64)) {
	filesUploaded := atomic.LoadUint32(&u.filesUploaded)
	atomic.AddUint32(&u.filesUploaded, -filesUploaded)
	send("filesUploaded", float64(filesUploaded))

	filesFailed := atomic.LoadUint32(&u.filesFailed)
	atomic.AddUint32(&u.filesFailed, -filesFailed)
	send("filesFailed", float64(filesFailed))
}

func uploadData(chUrl string, table string, timeout time.Duration, data io.Reader) error {
	p, err := url.Parse(chUrl)
	if err != nil {
		return err
	}

	q := p.Query()

	q.Set("query", fmt.Sprintf("INSERT INTO %s FORMAT TabSeparated", table))

	p.RawQuery = q.Encode()
	queryUrl := p.String()

	req, err := http.NewRequest("POST", queryUrl, data)
	if err != nil {
		return err
	}

	client := &http.Client{Timeout: timeout}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("clickhouse response status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}

func (u *Uploader) upload(exit chan struct{}, filename string) (err error) {
	startTime := time.Now()

	logger := u.logger.With(zap.String("filename", filename))
	logger.Info("start handle")

	defer func() {
		if err != nil {
			atomic.AddUint32(&u.filesFailed, 1)
			logger.Error("upload failed",
				zap.Error(err),
				zap.String("time", time.Now().Sub(startTime).String()),
			)
		} else {
			atomic.AddUint32(&u.filesUploaded, 1)
			logger.Info("upload success",
				zap.String("time", time.Now().Sub(startTime).String()),
			)
		}
	}()

	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		return err
	}

	if fi.Size() == 0 {
		logger.Info("file is empty")
		return nil
	}

	err = uploadData(u.clickHouseDSN, u.dataTable, u.dataTimeout, file)

	if err != nil {
		return err
	}

	if u.treeTable == "" { // don't make index in clickhouse
		return nil
	}

	// MAKE INDEX

	// reopen file
	file, err = os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	if err != nil {
		return err
	}

	reader := bufio.NewReaderSize(file, 1024*1024)

	treeData := bytes.NewBuffer(nil)

	localUniq := make(map[string]bool)

	var key string
	var level int
	var exists bool

LineLoop:
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}

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

		row := strings.Split(string(line), "\t")
		metric := row[0]

		if u.treeExists.Exists(metric) {
			continue LineLoop
		}

		if _, exists = localUniq[metric]; exists {
			continue LineLoop
		}

		offset := 0
		for level = 1; ; level++ {
			p := strings.IndexByte(metric[offset:], '.')
			if p < 0 {
				break
			}
			key = metric[:offset+p+1]

			if !u.treeExists.Exists(key) {
				if _, exists := localUniq[key]; !exists {
					localUniq[key] = true
					fmt.Fprintf(treeData, "%s\t%d\t%s\n", u.treeDate, level, key)
				}
			}

			offset += p + 1
		}

		localUniq[metric] = true
		fmt.Fprintf(treeData, "%s\t%d\t%s\n", u.treeDate, level, metric)
	}

	// @TODO: insert to tree data metrics
	err = uploadData(u.clickHouseDSN, u.treeTable, u.treeTimeout, treeData)
	if err != nil {
		return err
	}

	// copy data from localUniq to global
	for key, _ = range localUniq {
		u.treeExists.Add(key)
	}

	return nil
}

func (u *Uploader) uploadWorker(exit chan struct{}) {
	for {
		select {
		case <-exit:
			return
		case filename := <-u.queue:
			err := u.upload(exit, filename)
			if err == nil {
				err := os.Remove(filename)
				if err != nil {
					u.logger.Error("file delete failed",
						zap.String("filename", filename),
						zap.Error(err),
					)
				} else {
					u.logger.Info("file deleted",
						zap.String("filename", filename),
					)
				}
			}
			u.Lock()
			delete(u.inQueue, filename)
			u.Unlock()
		}
	}
}

func (u *Uploader) watch(exit chan struct{}) {
	flist, err := ioutil.ReadDir(u.path)
	if err != nil {
		u.logger.Error("ReadDir failed", zap.Error(err))
		return
	}

	files := make([]string, 0)
	for _, f := range flist {
		if f.IsDir() {
			continue
		}
		if !strings.HasPrefix(f.Name(), "default.") {
			continue
		}

		files = append(files, path.Join(u.path, f.Name()))
	}

	if len(files) == 0 {
		return
	}

	sort.Strings(files)

	for _, fn := range files {
		if u.inProgressCallback(fn) { // write in progress
			continue
		}

		u.Lock()
		if u.inQueue[fn] {
			u.Unlock()
			continue
		} else {
			u.inQueue[fn] = true
		}
		u.Unlock()

		select {
		case u.queue <- fn:
			// pass
		case <-exit:
			return
		}
	}
}

func (u *Uploader) watchWorker(exit chan struct{}) {
	t := time.NewTicker(time.Second)
	defer t.Stop()

	for {
		select {
		case <-exit:
			return
		case <-t.C:
			u.watch(exit)
		}
	}
}
<commit_msg>uploaded stats<commit_after>package uploader

import (
	"bufio"
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"sort"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lomik/stop"
	"github.com/uber-go/zap"
)

type Option func(u *Uploader)

func Path(path string) Option {
	return func(u *Uploader) {
		u.path = path
	}
}

func ClickHouse(dsn string) Option {
	return func(u *Uploader) {
		u.clickHouseDSN = dsn
	}
}

func DataTable(t string) Option {
	return func(u *Uploader) {
		u.dataTable = t
	}
}

func DataTimeout(t time.Duration) Option {
	return func(u *Uploader) {
		u.dataTimeout = t
	}
}

func TreeTable(t string) Option {
	return func(u *Uploader) {
		u.treeTable = t
	}
}

func TreeDate(t string) Option {
	return func(u *Uploader) {
		u.treeDate = t
	}
}

func TreeTimeout(t time.Duration) Option {
	return func(u *Uploader) {
		u.treeTimeout = t
	}
}

func InProgressCallback(cb func(string) bool) Option {
	return func(u *Uploader) {
		u.inProgressCallback = cb
	}
}

func Threads(t int) Option {
	return func(u *Uploader) {
		u.threads = t
	}
}

func Logger(logger zap.Logger) Option {
	return func(u *Uploader) {
		u.logger = logger
	}
}

// Uploader upload files from local directory to clickhouse
type Uploader struct {
	stop.Struct
	sync.Mutex
	stat struct {
		uploaded  uint32
		errors    uint32
		unhandled uint32
	}
	path               string
	clickHouseDSN      string
	dataTable          string
	dataTimeout        time.Duration
	treeTable          string
	treeTimeout        time.Duration
	treeDate           string
	threads            int
	inProgressCallback func(string) bool
	queue              chan string
	inQueue            map[string]bool // current uploading files
	treeExists         CMap            // store known keys and don't load it to clickhouse tree
	logger             zap.Logger
}

func New(options ...Option) *Uploader {

	u := &Uploader{
		path:               "/data/carbon-clickhouse/",
		dataTable:          "graphite",
		treeTable:          "graphite_tree",
		dataTimeout:        time.Minute,
		treeTimeout:        time.Minute,
		treeDate:           "2016-11-01",
		inProgressCallback: func(string) bool { return false },
		queue:              make(chan string, 1024),
		inQueue:            make(map[string]bool),
		threads:            1,
		treeExists:         NewCMap(),
		logger:             zap.New(zap.NullEncoder()),
	}

	for _, o := range options {
		o(u)
	}

	return u
}

func (u *Uploader) Start() error {
	return u.StartFunc(func() error {
		u.Go(u.watchWorker)

		for i := 0; i < u.threads; i++ {
			u.Go(u.uploadWorker)
		}

		return nil
	})
}

func (u *Uploader) Stat(send func(metric string, value float64)) {
	uploaded := atomic.LoadUint32(&u.stat.uploaded)
	atomic.AddUint32(&u.stat.uploaded, -uploaded)
	send("uploaded", float64(uploaded))

	errors := atomic.LoadUint32(&u.stat.errors)
	atomic.AddUint32(&u.stat.errors, -errors)
	send("errors", float64(errors))

	send("unhandled", float64(atomic.LoadUint32(&u.stat.unhandled)))
}

func uploadData(chUrl string, table string, timeout time.Duration, data io.Reader) error {
	p, err := url.Parse(chUrl)
	if err != nil {
		return err
	}

	q := p.Query()

	q.Set("query", fmt.Sprintf("INSERT INTO %s FORMAT RowBinary", table))

	p.RawQuery = q.Encode()
	queryUrl := p.String()

	req, err := http.NewRequest("POST", queryUrl, data)
	if err != nil {
		return err
	}

	client := &http.Client{Timeout: timeout}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, _ := ioutil.ReadAll(resp.Body)

	if resp.StatusCode != 200 {
		return fmt.Errorf("clickhouse response status %d: %s", resp.StatusCode, string(body))
	}

	return nil
}

func (u *Uploader) upload(exit chan struct{}, filename string) (err error) {
	startTime := time.Now()

	logger := u.logger.With(zap.String("filename", filename))
	logger.Info("start handle")

	defer func() {
		if err != nil {
			atomic.AddUint32(&u.stat.errors, 1)
			logger.Error("upload failed",
				zap.Error(err),
				zap.String("time", time.Now().Sub(startTime).String()),
			)
		} else {
			atomic.AddUint32(&u.stat.uploaded, 1)
			logger.Info("upload success",
				zap.String("time", time.Now().Sub(startTime).String()),
			)
		}
	}()

	file, err := os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	fi, err := file.Stat()
	if err != nil {
		return err
	}

	if fi.Size() == 0 {
		logger.Info("file is empty")
		return nil
	}

	err = uploadData(u.clickHouseDSN, u.dataTable, u.dataTimeout, file)

	if err != nil {
		return err
	}

	if u.treeTable == "" { // don't make index in clickhouse
		return nil
	}

	// MAKE INDEX

	// reopen file
	file, err = os.Open(filename)
	if err != nil {
		return err
	}
	defer file.Close()

	if err != nil {
		return err
	}

	reader := bufio.NewReaderSize(file, 1024*1024)

	treeData := bytes.NewBuffer(nil)

	localUniq := make(map[string]bool)

	var key string
	var level int
	var exists bool

LineLoop:
	for {
		line, _, err := reader.ReadLine()
		if err == io.EOF {
			break
		}

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

		row := strings.Split(string(line), "\t")
		metric := row[0]

		if u.treeExists.Exists(metric) {
			continue LineLoop
		}

		if _, exists = localUniq[metric]; exists {
			continue LineLoop
		}

		offset := 0
		for level = 1; ; level++ {
			p := strings.IndexByte(metric[offset:], '.')
			if p < 0 {
				break
			}
			key = metric[:offset+p+1]

			if !u.treeExists.Exists(key) {
				if _, exists := localUniq[key]; !exists {
					localUniq[key] = true
					fmt.Fprintf(treeData, "%s\t%d\t%s\n", u.treeDate, level, key)
				}
			}

			offset += p + 1
		}

		localUniq[metric] = true
		fmt.Fprintf(treeData, "%s\t%d\t%s\n", u.treeDate, level, metric)
	}

	// @TODO: insert to tree data metrics
	err = uploadData(u.clickHouseDSN, u.treeTable, u.treeTimeout, treeData)
	if err != nil {
		return err
	}

	// copy data from localUniq to global
	for key, _ = range localUniq {
		u.treeExists.Add(key)
	}

	return nil
}

func (u *Uploader) uploadWorker(exit chan struct{}) {
	for {
		select {
		case <-exit:
			return
		case filename := <-u.queue:
			err := u.upload(exit, filename)
			if err == nil {
				err := os.Remove(filename)
				if err != nil {
					u.logger.Error("file delete failed",
						zap.String("filename", filename),
						zap.Error(err),
					)
				} else {
					u.logger.Info("file deleted",
						zap.String("filename", filename),
					)
				}
			}
			u.Lock()
			delete(u.inQueue, filename)
			u.Unlock()
		}
	}
}

func (u *Uploader) watch(exit chan struct{}) {
	flist, err := ioutil.ReadDir(u.path)
	if err != nil {
		u.logger.Error("ReadDir failed", zap.Error(err))
		return
	}

	files := make([]string, 0)
	for _, f := range flist {
		if f.IsDir() {
			continue
		}
		if !strings.HasPrefix(f.Name(), "default.") {
			continue
		}

		files = append(files, path.Join(u.path, f.Name()))
	}

	atomic.StoreUint32(&u.stat.unhandled, uint32(len(files)))

	if len(files) == 0 {
		return
	}

	sort.Strings(files)

	for _, fn := range files {
		if u.inProgressCallback(fn) { // write in progress
			continue
		}

		u.Lock()
		if u.inQueue[fn] {
			u.Unlock()
			continue
		} else {
			u.inQueue[fn] = true
		}
		u.Unlock()

		select {
		case u.queue <- fn:
			// pass
		case <-exit:
			return
		}
	}
}

func (u *Uploader) watchWorker(exit chan struct{}) {
	t := time.NewTicker(time.Second)
	defer t.Stop()

	for {
		select {
		case <-exit:
			return
		case <-t.C:
			u.watch(exit)
		}
	}
}
<|endoftext|>