<commit_before>/*
Copyright 2020 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package reservedconn

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"

	"vitess.io/vitess/go/test/utils"

	"github.com/google/go-cmp/cmp"
	"github.com/stretchr/testify/require"

	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/test/endtoend/cluster"
)

func TestSetUDV(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	type queriesWithExpectations struct {
		query        string
		expectedRows string
		rowsAffected int
		rowsReturned int
	}

	queries := []queriesWithExpectations{{
		query:        "select @foo",
		expectedRows: "[[NULL]]", rowsReturned: 1,
	}, {
		query: "set @foo = 'abc', @bar = 42, @baz = 30.5, @tablet = concat('foo','bar')",
	}, {
		query: "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE */",
	}, { // This is handled at vtgate.
		query:        "select @foo, @bar, @baz, @tablet",
		expectedRows: `[[VARBINARY("abc") INT64(42) DECIMAL(30.5) VARBINARY("foobar")]]`, rowsReturned: 1,
	}, { // Cannot really check a specific value for sql_mode as it will differ based on database selected to run these tests.
		query:        "select @OLD_SQL_MODE = @@SQL_MODE",
		expectedRows: `[[INT64(1)]]`, rowsReturned: 1,
	}, { // This one is sent to tablet.
		query:        "select @foo, @bar, @baz, @tablet, @OLD_SQL_MODE = @@SQL_MODE",
		expectedRows: `[[VARCHAR("abc") INT64(42) DECIMAL(30.5) VARCHAR("foobar") INT64(1)]]`, rowsReturned: 1,
	}, {
		query:        "insert into test(id, val1, val2, val3) values(1, @foo, null, null), (2, null, @bar, null), (3, null, null, @baz)",
		expectedRows: ``, rowsAffected: 3,
	}, {
		query:        "select id, val1, val2, val3 from test order by id",
		expectedRows: `[[INT64(1) VARCHAR("abc") NULL NULL] [INT64(2) NULL INT32(42) NULL] [INT64(3) NULL NULL FLOAT32(30.5)]]`, rowsReturned: 3,
	}, {
		query:        "select id, val1 from test where val1=@foo",
		expectedRows: `[[INT64(1) VARCHAR("abc")]]`, rowsReturned: 1,
	}, {
		query:        "select id, val2 from test where val2=@bar",
		expectedRows: `[[INT64(2) INT32(42)]]`, rowsReturned: 1,
	}, {
		query:        "select id, val3 from test where val3=@baz",
		expectedRows: `[[INT64(3) FLOAT32(30.5)]]`, rowsReturned: 1,
	}, {
		query:        "delete from test where val2 = @bar",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val2 from test where val2=@bar",
		expectedRows: ``,
	}, {
		query:        "update test set val2 = @bar where val1 = @foo",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val1, val2 from test where val1=@foo",
		expectedRows: `[[INT64(1) VARCHAR("abc") INT32(42)]]`, rowsReturned: 1,
	}, {
		query:        "insert into test(id, val1, val2, val3) values (42, @tablet, null, null)",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val1 from test where val1 = @tablet",
		expectedRows: `[[INT64(42) VARCHAR("foobar")]]`, rowsReturned: 1,
	}, {
		query:        "set @foo = now(), @bar = now(), @dd = date('2020-10-20'), @tt = time('10:15')",
		expectedRows: `[]`,
	}, {
		query:        "select @foo = @bar, @dd, @tt",
		expectedRows: `[[UINT64(1) DATE("2020-10-20") TIME("10:15:00")]]`, rowsReturned: 1,
	}}

	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()
	_, err = exec(t, conn, "delete from test")
	require.NoError(t, err)

	for i, q := range queries {
		t.Run(fmt.Sprintf("%d-%s", i, q.query), func(t *testing.T) {
			qr, err := exec(t, conn, q.query)
			require.NoError(t, err)
			assert.EqualValues(t, q.rowsAffected, qr.RowsAffected, "rows affected wrong for query: %s", q.query)
			assert.EqualValues(t, q.rowsReturned, len(qr.Rows), "rows returned wrong for query: %s", q.query)
			if q.expectedRows != "" {
				result := fmt.Sprintf("%v", qr.Rows)
				if diff := cmp.Diff(q.expectedRows, result); diff != "" {
					t.Errorf("%s\nfor query: %s", diff, q.query)
				}
			}
		})
	}
}

func TestMysqlDumpInitialLog(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	queries := []string{
		"/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;",
		"/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;",
		"/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;",
		"/*!50503 SET NAMES utf8mb4 */;",
		"/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;",
		"/*!40103 SET TIME_ZONE='+00:00' */;",
		"/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;",
		"/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;",
		"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;",
		"/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;",
		"/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;",
		"/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;",
		"/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;",
		"/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;",
		"/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;",
		"/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;",
		"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;",
		"/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;",
	}

	for _, query := range queries {
		t.Run(query, func(t *testing.T) {
			_, more, err := conn.ExecuteFetchMulti(query, 1000, true)
			require.NoError(t, err)
			require.False(t, more)
		})
	}
}

func TestUserDefinedVariableResolvedAtTablet(t *testing.T) {
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	// this should set the UDV foo to a value that has to be evaluated by mysqld
	exec(t, conn, "set @foo = CONCAT('Any','Expression','Is','Valid')")

	// now getting that value should return the value from the tablet
	qr, err := exec(t, conn, "select @foo")
	require.NoError(t, err)
	got := fmt.Sprintf("%v", qr.Rows)
	utils.MustMatch(t, `[[VARBINARY("AnyExpressionIsValid")]]`, got, "didnt match")
}
<commit_msg>reservedconn: fix type in evaluation result<commit_after>/*
Copyright 2020 The Vitess Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package reservedconn

import (
	"context"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"

	"vitess.io/vitess/go/test/utils"

	"github.com/google/go-cmp/cmp"
	"github.com/stretchr/testify/require"

	"vitess.io/vitess/go/mysql"
	"vitess.io/vitess/go/test/endtoend/cluster"
)

func TestSetUDV(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	type queriesWithExpectations struct {
		query        string
		expectedRows string
		rowsAffected int
		rowsReturned int
	}

	queries := []queriesWithExpectations{{
		query:        "select @foo",
		expectedRows: "[[NULL]]", rowsReturned: 1,
	}, {
		query: "set @foo = 'abc', @bar = 42, @baz = 30.5, @tablet = concat('foo','bar')",
	}, {
		query: "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE */",
	}, { // This is handled at vtgate.
		query:        "select @foo, @bar, @baz, @tablet",
		expectedRows: `[[VARBINARY("abc") INT64(42) DECIMAL(30.5) VARBINARY("foobar")]]`, rowsReturned: 1,
	}, { // Cannot really check a specific value for sql_mode as it will differ based on database selected to run these tests.
		query:        "select @OLD_SQL_MODE = @@SQL_MODE",
		expectedRows: `[[INT64(1)]]`, rowsReturned: 1,
	}, { // This one is sent to tablet.
		query:        "select @foo, @bar, @baz, @tablet, @OLD_SQL_MODE = @@SQL_MODE",
		expectedRows: `[[VARCHAR("abc") INT64(42) DECIMAL(30.5) VARCHAR("foobar") INT64(1)]]`, rowsReturned: 1,
	}, {
		query:        "insert into test(id, val1, val2, val3) values(1, @foo, null, null), (2, null, @bar, null), (3, null, null, @baz)",
		expectedRows: ``, rowsAffected: 3,
	}, {
		query:        "select id, val1, val2, val3 from test order by id",
		expectedRows: `[[INT64(1) VARCHAR("abc") NULL NULL] [INT64(2) NULL INT32(42) NULL] [INT64(3) NULL NULL FLOAT32(30.5)]]`, rowsReturned: 3,
	}, {
		query:        "select id, val1 from test where val1=@foo",
		expectedRows: `[[INT64(1) VARCHAR("abc")]]`, rowsReturned: 1,
	}, {
		query:        "select id, val2 from test where val2=@bar",
		expectedRows: `[[INT64(2) INT32(42)]]`, rowsReturned: 1,
	}, {
		query:        "select id, val3 from test where val3=@baz",
		expectedRows: `[[INT64(3) FLOAT32(30.5)]]`, rowsReturned: 1,
	}, {
		query:        "delete from test where val2 = @bar",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val2 from test where val2=@bar",
		expectedRows: ``,
	}, {
		query:        "update test set val2 = @bar where val1 = @foo",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val1, val2 from test where val1=@foo",
		expectedRows: `[[INT64(1) VARCHAR("abc") INT32(42)]]`, rowsReturned: 1,
	}, {
		query:        "insert into test(id, val1, val2, val3) values (42, @tablet, null, null)",
		expectedRows: ``, rowsAffected: 1,
	}, {
		query:        "select id, val1 from test where val1 = @tablet",
		expectedRows: `[[INT64(42) VARCHAR("foobar")]]`, rowsReturned: 1,
	}, {
		query:        "set @foo = now(), @bar = now(), @dd = date('2020-10-20'), @tt = time('10:15')",
		expectedRows: `[]`,
	}, {
		query:        "select @foo = @bar, @dd, @tt",
		expectedRows: `[[DECIMAL(1) DATE("2020-10-20") TIME("10:15:00")]]`, rowsReturned: 1,
	}}

	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()
	_, err = exec(t, conn, "delete from test")
	require.NoError(t, err)

	for i, q := range queries {
		t.Run(fmt.Sprintf("%d-%s", i, q.query), func(t *testing.T) {
			qr, err := exec(t, conn, q.query)
			require.NoError(t, err)
			assert.EqualValues(t, q.rowsAffected, qr.RowsAffected, "rows affected wrong for query: %s", q.query)
			assert.EqualValues(t, q.rowsReturned, len(qr.Rows), "rows returned wrong for query: %s", q.query)
			if q.expectedRows != "" {
				result := fmt.Sprintf("%v", qr.Rows)
				if diff := cmp.Diff(q.expectedRows, result); diff != "" {
					t.Errorf("%s\nfor query: %s", diff, q.query)
				}
			}
		})
	}
}

func TestMysqlDumpInitialLog(t *testing.T) {
	defer cluster.PanicHandler(t)
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	queries := []string{
		"/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;",
		"/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;",
		"/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;",
		"/*!50503 SET NAMES utf8mb4 */;",
		"/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;",
		"/*!40103 SET TIME_ZONE='+00:00' */;",
		"/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;",
		"/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;",
		"/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;",
		"/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;",
		"/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;",
		"/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;",
		"/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;",
		"/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;",
		"/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;",
		"/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;",
		"/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;",
		"/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;",
	}

	for _, query := range queries {
		t.Run(query, func(t *testing.T) {
			_, more, err := conn.ExecuteFetchMulti(query, 1000, true)
			require.NoError(t, err)
			require.False(t, more)
		})
	}
}

func TestUserDefinedVariableResolvedAtTablet(t *testing.T) {
	ctx := context.Background()
	vtParams := mysql.ConnParams{
		Host: "localhost",
		Port: clusterInstance.VtgateMySQLPort,
	}
	conn, err := mysql.Connect(ctx, &vtParams)
	require.NoError(t, err)
	defer conn.Close()

	// this should set the UDV foo to a value that has to be evaluated by mysqld
	exec(t, conn, "set @foo = CONCAT('Any','Expression','Is','Valid')")

	// now getting that value should return the value from the tablet
	qr, err := exec(t, conn, "select @foo")
	require.NoError(t, err)
	got := fmt.Sprintf("%v", qr.Rows)
	utils.MustMatch(t, `[[VARBINARY("AnyExpressionIsValid")]]`, got, "didnt match")
}
<|endoftext|>


<commit_before>// Copyright 2021 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.

//go:build go1.18
// +build go1.18

package objectpath_test

import (
	"go/types"
	"testing"

	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/types/objectpath"
)

func TestGenericPaths(t *testing.T) {
	pkgs := map[string]map[string]string{
		"b": {"b.go": `
package b

const C int = 1

type T[TP0 any, TP1 interface{ M0(); M1() }] struct{}

func (T[RP0, RP1]) M() {}

type N int

func (N) M0()
func (N) M1()

type A = T[int, N]

func F[FP0, FP1 any](FP0, FP1) {}
`},
	}
	paths := []pathTest{
		// Good paths
		{"b", "T", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		{"b", "T.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		{"b", "T.M0", "func (b.T[b.RP0, b.RP1]).M()", ""},
		{"b", "T.T0O", "type TP0 b.TP0", ""},
		{"b", "T.T1O", "type TP1 b.TP1", ""},
		{"b", "T.T1CM0", "func (interface).M0()", ""},
		// Obj of an instance is the generic declaration.
		{"b", "A.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		{"b", "A.M0", "func (b.T[int, b.N]).M()", ""},

		// Bad paths
		{"b", "N.C", "", "invalid path: ends with 'C', want [AFMO]"},
		{"b", "N.CO", "", "cannot apply 'C' to b.N (got *types.Named, want type parameter)"},
		{"b", "N.T", "", `invalid path: bad numeric operand "" for code 'T'`},
		{"b", "N.T0", "", "tuple index 0 out of range [0-0)"},
		{"b", "T.T2O", "", "tuple index 2 out of range [0-2)"},
		{"b", "T.T1M0", "", "cannot apply 'M' to b.TP1 (got *types.TypeParam, want interface or named)"},
		{"b", "C.T0", "", "cannot apply 'T' to int (got *types.Basic, want named or signature)"},
	}

	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
	conf.Import("b")
	prog, err := conf.Load()
	if err != nil {
		t.Fatal(err)
	}

	for _, test := range paths {
		if err := testPath(prog, test); err != nil {
			t.Error(err)
		}
	}

	// bad objects
	for _, test := range []struct {
		obj     types.Object
		wantErr string
	}{
		{types.Universe.Lookup("any"), "predeclared type any = interface{} has no path"},
		{types.Universe.Lookup("comparable"), "predeclared type comparable interface{} has no path"},
	} {
		path, err := objectpath.For(test.obj)
		if err == nil {
			t.Errorf("Object(%s) = %q, want error", test.obj, path)
			continue
		}
		if err.Error() != test.wantErr {
			t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
			continue
		}
	}
}
<commit_msg>go/types/objectpath: temporarily comment-out some tests<commit_after>// Copyright 2021 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.

//go:build go1.18
// +build go1.18

package objectpath_test

import (
	"go/types"
	"testing"

	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
	"golang.org/x/tools/go/types/objectpath"
)

func TestGenericPaths(t *testing.T) {
	pkgs := map[string]map[string]string{
		"b": {"b.go": `
package b

const C int = 1

type T[TP0 any, TP1 interface{ M0(); M1() }] struct{}

func (T[RP0, RP1]) M() {}

type N int

func (N) M0()
func (N) M1()

type A = T[int, N]

func F[FP0, FP1 any](FP0, FP1) {}
`},
	}
	paths := []pathTest{
		// TODO: uncomment tests once type parameter strings have been adjusted.
		// Good paths
		// {"b", "T", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		// {"b", "T.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		// {"b", "T.M0", "func (b.T[b.RP0, b.RP1]).M()", ""},
		// {"b", "T.T0O", "type TP0 b.TP0", ""},
		// {"b", "T.T1O", "type TP1 b.TP1", ""},
		{"b", "T.T1CM0", "func (interface).M0()", ""},
		// Obj of an instance is the generic declaration.
		// {"b", "A.O", "type b.T[b.TP0 interface{}, b.TP1 interface{M0(); M1()}] struct{}", ""},
		{"b", "A.M0", "func (b.T[int, b.N]).M()", ""},

		// Bad paths
		{"b", "N.C", "", "invalid path: ends with 'C', want [AFMO]"},
		{"b", "N.CO", "", "cannot apply 'C' to b.N (got *types.Named, want type parameter)"},
		{"b", "N.T", "", `invalid path: bad numeric operand "" for code 'T'`},
		{"b", "N.T0", "", "tuple index 0 out of range [0-0)"},
		{"b", "T.T2O", "", "tuple index 2 out of range [0-2)"},
		// {"b", "T.T1M0", "", "cannot apply 'M' to b.TP1 (got *types.TypeParam, want interface or named)"},
		{"b", "C.T0", "", "cannot apply 'T' to int (got *types.Basic, want named or signature)"},
	}

	conf := loader.Config{Build: buildutil.FakeContext(pkgs)}
	conf.Import("b")
	prog, err := conf.Load()
	if err != nil {
		t.Fatal(err)
	}

	for _, test := range paths {
		if err := testPath(prog, test); err != nil {
			t.Error(err)
		}
	}

	// bad objects
	for _, test := range []struct {
		obj     types.Object
		wantErr string
	}{
		{types.Universe.Lookup("any"), "predeclared type any = interface{} has no path"},
		{types.Universe.Lookup("comparable"), "predeclared type comparable interface{} has no path"},
	} {
		path, err := objectpath.For(test.obj)
		if err == nil {
			t.Errorf("Object(%s) = %q, want error", test.obj, path)
			continue
		}
		if err.Error() != test.wantErr {
			t.Errorf("Object(%s) error was %q, want %q", test.obj, err, test.wantErr)
			continue
		}
	}
}
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"golang_sample/db"
	"net/http"
)

//Represents service Registration
type Registration string

//Holds arguments to be passed to service Arith in RPC call
type RegistrationArgs struct {
	Firstname, Lastname, Email, Micl_uuid, Url string
}

type Result struct {
	NewId int
	Msg   string
}

type Error struct {
	s string
}

//This procedure is invoked by rpc and calls rpcexample.Multiply which stores product of args.A and args.B in result pointer
func (t *Registration) Add(r *http.Request, args *db.User, result *Result) error {
	newId := new(db.User).Add(args)
	msg := fmt.Sprintf("User with name: %s succcessfuly insert with Id: %d in the DB", args.Firstname, newId)
	// Result is already initialized from the function signature
	result.NewId = newId
	result.Msg = msg
	localError := new(Error)
	localError.s = "An Error occured when trying to save to the DB"
	return localError
}

func (e *Error) Error() string {
	return e.s
}
<commit_msg>- Add some validation in register service for Firstname, Lastname and   Email so we can show how Error object could be used and return through   the JSON-RPC service<commit_after>package service

import (
	"fmt"
	"golang_sample/db"
	"net/http"
)

//Represents service Registration
type Registration string

//Holds arguments to be passed to service Arith in RPC call
type RegistrationArgs struct {
	Firstname, Lastname, Email, Micl_uuid, Url string
}

type Result struct {
	NewId int
	Msg   string
}

type Error struct {
	s string
}

//This procedure is invoked by rpc and calls rpcexample.Multiply which stores product of args.A and args.B in result pointer
func (t *Registration) Add(r *http.Request, args *db.User, result *Result) error {
	/*
	 Not allowing Empty Firstname and Lastname
	*/
	if args.Firstname == "" || args.Lastname == "" || args.Email == "" {
		localError := new(Error)
		localError.s = "Firstname, Lastname and Email are required!"
		return localError
	}
	newId := new(db.User).Add(args)
	msg := fmt.Sprintf("User with name: %s succcessfuly insert with Id: %d in the DB", args.Firstname, newId)
	// Result is already initialized from the function signature
	result.NewId = newId
	result.Msg = msg

	return nil
}

func (e *Error) Error() string {
	return e.s
}
<|endoftext|>


<commit_before>package sdees

import (
	"errors"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"time"
)

// ListBranches returns a slice of the branch names of the repo
// excluding the master branch
func ListBranches(folder string) ([]string, error) {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Listing branches %s", id, folder)
	defer timeTrack(time.Now(), "["+id+"]Listing branches "+folder)
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	err := os.Chdir(folder)
	if err != nil {
		return []string{}, errors.New("Cannot chdir into " + folder)
	}

	// run git command
	cmd := exec.Command("git", "branch", "--list")
	stdout, err := cmd.Output()
	if err != nil {
		return []string{}, errors.New("Unable to find branches in " + folder)
	}
	possibleBranches := strings.Split(string(stdout), "\n")

	// clean names for branches
	branches := []string{}
	for _, name := range possibleBranches {
		possibleName := strings.TrimSpace(strings.Replace(name, "*", "", -1))
		if possibleName != "master" && len(possibleName) > 0 {
			branches = append(branches, possibleName)
		}
	}

	return branches, nil
}

// GetLatest assumes you already have a cloned repo in `gitfolder`
// and it will fetch the latest and compare the before and after
// to update the cache incrementally
func GetLatest(gitfolder string) ([]string, []string, error) {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Getting latest for %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Getting latest "+gitfolder)
	var err error
	err = nil
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	addedBranches := []string{}
	deletedBranches := []string{}

	oldBranches, err := ListBranches("./")

	if err != nil {
		return []string{}, []string{}, err
	}

	err = Fetch(gitfolder)
	if err != nil {
		return []string{}, []string{}, err
	}

	newBranches, err := ListBranches("./")
	if err != nil {
		return []string{}, []string{}, err
	}

	oldBranchesMap := make(map[string]bool)
	for _, branch := range oldBranches {
		oldBranchesMap[branch] = true
	}
	for _, branch := range newBranches {
		if _, ok := oldBranchesMap[branch]; !ok {
			addedBranches = append(addedBranches, branch)
		}
	}
	if len(addedBranches) > 0 {
		logger.Debug("Found %d remote branches that have been added: %v", len(addedBranches), addedBranches)
	}

	newBranchesMap := make(map[string]bool)
	for _, branch := range newBranches {
		newBranchesMap[branch] = true
	}
	for _, branch := range oldBranches {
		if _, ok := newBranchesMap[branch]; !ok {
			deletedBranches = append(deletedBranches, branch)
		}
	}
	if len(deletedBranches) > 0 {
		logger.Debug("Found %d remote branches that have been deleted: %v", len(deletedBranches), deletedBranches)
	}

	return addedBranches, deletedBranches, err

}

// Delete will permanetly delete by deleting and creating a new orphan
// branch. This will erase history on all copies!
func Delete(gitfolder string, branch string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Deleting branch %s in %s", id, branch, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Deleting "+branch)

	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	// Make sure we aren't on that branch
	cmd := exec.Command("git", "checkout", "master")
	_, err := cmd.Output()
	if err != nil {
		return errors.New("Problem switching to master")
	}

	// Delete branch
	cmd = exec.Command("git", "branch", "-D", branch)
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Problem deleting branch " + branch)
	}

	// Create empty file and commit to same branch
	NewDocument(gitfolder, ".deleted", "", "deleted", "Thu, 07 Apr 2005 22:13:13 +0200", branch)

	return nil
}

func DeleteBranch(branch string) error {
	err := Delete(RemoteFolder, branch)
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	err = DeleteCache()
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	err = Push(RemoteFolder)
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	return nil
}

// Fetch will force fetch and update tracking and rebase all branches so
// that it matches the remote origin. It will not destroy local copies of things.
func Fetch(gitfolder string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Fetching %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Fetching "+gitfolder)
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	// Fetch all
	cmd := exec.Command("git", "fetch", "--all", "--force", "--prune")
	_, err2 := cmd.Output()
	if err2 != nil {
		logger.Debug("Problem fetching all")
	}

	// Get branchces
	cmd = exec.Command("git", "branch", "-r")
	stdout, err := cmd.Output()
	if err != nil {
		return errors.New("Cannot branch -r")
	}
	branches := []string{}
	remotelyTrackedBranches := make(map[string]bool)
	for _, line := range strings.Split(string(stdout), "\n") {
		branchName := strings.TrimSpace(line)
		if strings.Contains(branchName, "->") {
			branchName = strings.TrimSpace(strings.Split(branchName, "->")[1])
		}
		if strings.Contains(branchName, "origin/") {
			branchName = strings.TrimSpace(strings.Split(branchName, "origin/")[1])
		}
		if len(branchName) == 0 || branchName == "master" {
			continue
		}
		remotelyTrackedBranches[branchName] = true
		branches = append(branches, branchName)
	}

	// Find all locally tracked branches with
	//		git branch -vv
	cmd = exec.Command("git", "branch", "-vv")
	stdout, err = cmd.Output()
	if err != nil {
		return errors.New("Cannot branch -vv")
	}
	branchesToReset := []string{}
	locallyTrackedBranches := make(map[string]bool)
	for _, line := range strings.Split(string(stdout), "\n") {
		if strings.Contains(line, "[") && strings.Contains(line, "]") {
			split1 := strings.Split(line, "[")
			split2 := strings.Split(split1[1], "]")
			insides := split2[0]
			split1 = strings.Split(insides, ":")
			branch := split1[0]
			if strings.Contains(branch, "/") {
				split2 = strings.Split(branch, "/")
				branch = split2[len(split2)-1]
			}
			branch = strings.TrimSpace(branch)
			if len(branch) == 0 {
				continue
			}
			if strings.Contains(insides, "ahead ") || strings.Contains(insides, "behind ") {
				branchesToReset = append(branchesToReset, branch)
			}
			locallyTrackedBranches[branch] = true
		}
	}

	// Track branches not being tracked.
	//       BRANCHES NOT BEING TRACKED
	//                 =
	//   SET OF BRANCHES FROM git branch -r
	//                  -
	//   SET OF BRANCHES FROM git branch -vv
	start := time.Now()
	for branch := range remotelyTrackedBranches {
		if _, ok := locallyTrackedBranches[branch]; !ok {
			cmd = exec.Command("git", "branch", "--track", branch, "origin/"+branch)
			cmd.Output()
		}
	}
	logger.Debug("Tracking took " + time.Since(start).String())

	// Find ANY that have "ahead" or "behind", and do
	//      git checkout branch
	//      git reset --hard HEAD
	// 			git rebase
	for _, branch := range branchesToReset {
		logger.Debug("Resetting branch %s", branch)
		cmd = exec.Command("git", "checkout", branch)
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot checkout" + branch)
		}
		cmd = exec.Command("git", "reset", "--hard", "HEAD")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot reset --hard HEAD of " + branch)
		}
		cmd = exec.Command("git", "rebase")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot rebase " + branch)
		}
		cmd = exec.Command("git", "checkout", "master")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot checkout master")
		}
	}

	// NOT NEEDED - THIS IS TAKEN CARE OF WITH FETCH --FORCE
	// // Find if branches are no longer on remote and delete them locally
	// localBranches, _ := ListBranches("./")
	// for _, localBranch := range localBranches {
	// 	if _, ok := allBranches[localBranch]; !ok {
	// 		logger.Debug("Deleted locally '%s' - branch no longer on remote", localBranch)
	// 		cmd = exec.Command("git", "branch", "-D", localBranch)
	// 		_, err = cmd.Output()
	// 		if err != nil {
	// 			return errors.New("Problem deleting branch " + localBranch)
	// 		}
	// 	}
	// }

	return err2
}

func NewDocument(gitfolder string, documentname string, fulltext string, message string, datestring string, branchNameOverride string) (string, error) {
	defer timeTrack(time.Now(), "NewDocument")
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	newBranch := branchNameOverride
	if len(branchNameOverride) == 0 {
		newBranch = MakeAlliteration()
	}
	cmd := exec.Command("git", "checkout", "--orphan", newBranch)
	_, err = cmd.Output()
	if err != nil {
		cmd2 := exec.Command("git", "checkout", newBranch)
		_, err2 := cmd2.Output()
		if err2 != nil {
			return newBranch, errors.New("Cannot checkout branch " + newBranch)
		}
	}

	err = ioutil.WriteFile(documentname, []byte(fulltext), 0644)
	if err != nil {
		return newBranch, errors.New("Cannot write file " + documentname)
	}
	if Encrypt {
		err = EncryptFile(documentname, Passphrase)
		if err != nil {
			return newBranch, err
		}
		documentname += ".gpg"
		message = EncryptString(message, Passphrase)
	}

	cmd = exec.Command("git", "add", documentname)
	_, err = cmd.Output()
	if err != nil {
		return newBranch, errors.New("Cannot add file " + documentname)
	}

	cmd = exec.Command("git", "commit", "--date", datestring, "-m", message, documentname)
	_, err = cmd.Output()
	if err != nil {
		return newBranch, errors.New("Cannot commit " + documentname + " error: " + err.Error())
	}

	logger.Debug("Updated document %s in branch %s", documentname, newBranch)

	// Check if its a new document
	_, errExistence := GetTextOfOne("./", "master", documentname)
	if errExistence != nil {
		logger.Debug("It seems %s doesn't exist yet, making a index file for it in master", documentname)
		dir, _ := os.Getwd()
		logger.Debug("Current DIR: %s", dir)
		cmd2 := exec.Command("git", "checkout", "-f", "master")
		_, err2 := cmd2.Output()
		if err2 != nil {
			cmd2 = exec.Command("git", "checkout", "--orphan", "master")
			_, err2 = cmd2.Output()
			if err2 != nil {
				logger.Error("Couldn't checkout master")
			}
		}
		text := "Yay some text!"
		if Encrypt {
			text = EncryptString(text, Passphrase)
		}
		err2 = ioutil.WriteFile(documentname, []byte(text), 0644)
		if err != nil {
			logger.Warn("Something wrong with writing " + documentname)
		}
		cmd2 = exec.Command("git", "add", documentname)
		_, err2 = cmd2.Output()
		if err2 != nil {
			logger.Warn("Something wrong adding " + documentname)
		}
		cmd2 = exec.Command("git", "commit", "--date", datestring, "-m", message, documentname)
		_, err2 = cmd2.Output()
		if err2 != nil {
			logger.Warn("Something wrong: %s ", strings.Join([]string{"git", "commit", "--date", datestring, "-m", message, documentname}, " "))
		}
	}

	return newBranch, err
}

func Push(gitfolder string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Pushing %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Pushing")
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	cmd := exec.Command("git", "push", "--force", "--all", "origin")
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Cannot push " + gitfolder)
	}

	return nil
}

func Clone(folder string, remote string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Cloning %s", id, remote)
	defer timeTrack(time.Now(), "["+id+"]Cloning")
	var err error
	logger.Debug("Cloning %s into directory at %s", remote, folder)
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)

	cmd := exec.Command("git", "clone", remote, folder)
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Cloning command failed: '" + strings.Join([]string{"git", "clone", remote, folder}, " ") + "'")
	}

	Fetch(folder)

	return nil
}
<commit_msg>Added stderr pipe<commit_after>package sdees

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strings"
	"time"
)

// ListBranches returns a slice of the branch names of the repo
// excluding the master branch
func ListBranches(folder string) ([]string, error) {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Listing branches %s", id, folder)
	defer timeTrack(time.Now(), "["+id+"]Listing branches "+folder)
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	err := os.Chdir(folder)
	if err != nil {
		return []string{}, errors.New("Cannot chdir into " + folder)
	}

	// run git command
	cmd := exec.Command("git", "branch", "--list")
	stdout, err := cmd.Output()
	if err != nil {
		return []string{}, errors.New("Unable to find branches in " + folder)
	}
	possibleBranches := strings.Split(string(stdout), "\n")

	// clean names for branches
	branches := []string{}
	for _, name := range possibleBranches {
		possibleName := strings.TrimSpace(strings.Replace(name, "*", "", -1))
		if possibleName != "master" && len(possibleName) > 0 {
			branches = append(branches, possibleName)
		}
	}

	return branches, nil
}

// GetLatest assumes you already have a cloned repo in `gitfolder`
// and it will fetch the latest and compare the before and after
// to update the cache incrementally
func GetLatest(gitfolder string) ([]string, []string, error) {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Getting latest for %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Getting latest "+gitfolder)
	var err error
	err = nil
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	addedBranches := []string{}
	deletedBranches := []string{}

	oldBranches, err := ListBranches("./")

	if err != nil {
		return []string{}, []string{}, err
	}

	err = Fetch(gitfolder)
	if err != nil {
		return []string{}, []string{}, err
	}

	newBranches, err := ListBranches("./")
	if err != nil {
		return []string{}, []string{}, err
	}

	oldBranchesMap := make(map[string]bool)
	for _, branch := range oldBranches {
		oldBranchesMap[branch] = true
	}
	for _, branch := range newBranches {
		if _, ok := oldBranchesMap[branch]; !ok {
			addedBranches = append(addedBranches, branch)
		}
	}
	if len(addedBranches) > 0 {
		logger.Debug("Found %d remote branches that have been added: %v", len(addedBranches), addedBranches)
	}

	newBranchesMap := make(map[string]bool)
	for _, branch := range newBranches {
		newBranchesMap[branch] = true
	}
	for _, branch := range oldBranches {
		if _, ok := newBranchesMap[branch]; !ok {
			deletedBranches = append(deletedBranches, branch)
		}
	}
	if len(deletedBranches) > 0 {
		logger.Debug("Found %d remote branches that have been deleted: %v", len(deletedBranches), deletedBranches)
	}

	return addedBranches, deletedBranches, err

}

// Delete will permanetly delete by deleting and creating a new orphan
// branch. This will erase history on all copies!
func Delete(gitfolder string, branch string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Deleting branch %s in %s", id, branch, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Deleting "+branch)

	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	// Make sure we aren't on that branch
	cmd := exec.Command("git", "checkout", "master")
	_, err := cmd.Output()
	if err != nil {
		return errors.New("Problem switching to master")
	}

	// Delete branch
	cmd = exec.Command("git", "branch", "-D", branch)
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Problem deleting branch " + branch)
	}

	// Create empty file and commit to same branch
	NewDocument(gitfolder, ".deleted", "", "deleted", "Thu, 07 Apr 2005 22:13:13 +0200", branch)

	return nil
}

func DeleteBranch(branch string) error {
	err := Delete(RemoteFolder, branch)
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	err = DeleteCache()
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	err = Push(RemoteFolder)
	if err != nil {
		logger.Debug(err.Error())
		return err
	}
	return nil
}

// Fetch will force fetch and update tracking and rebase all branches so
// that it matches the remote origin. It will not destroy local copies of things.
func Fetch(gitfolder string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Fetching %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Fetching "+gitfolder)
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	// Fetch all
	cmd := exec.Command("git", "fetch", "--all", "--force", "--prune")
	_, err2 := cmd.Output()
	if err2 != nil {
		logger.Debug("Problem fetching all")
	}

	// Get branchces
	cmd = exec.Command("git", "branch", "-r")
	stdout, err := cmd.Output()
	if err != nil {
		return errors.New("Cannot branch -r")
	}
	branches := []string{}
	remotelyTrackedBranches := make(map[string]bool)
	for _, line := range strings.Split(string(stdout), "\n") {
		branchName := strings.TrimSpace(line)
		if strings.Contains(branchName, "->") {
			branchName = strings.TrimSpace(strings.Split(branchName, "->")[1])
		}
		if strings.Contains(branchName, "origin/") {
			branchName = strings.TrimSpace(strings.Split(branchName, "origin/")[1])
		}
		if len(branchName) == 0 || branchName == "master" {
			continue
		}
		remotelyTrackedBranches[branchName] = true
		branches = append(branches, branchName)
	}

	// Find all locally tracked branches with
	//		git branch -vv
	cmd = exec.Command("git", "branch", "-vv")
	stdout, err = cmd.Output()
	if err != nil {
		return errors.New("Cannot branch -vv")
	}
	branchesToReset := []string{}
	locallyTrackedBranches := make(map[string]bool)
	for _, line := range strings.Split(string(stdout), "\n") {
		if strings.Contains(line, "[") && strings.Contains(line, "]") {
			split1 := strings.Split(line, "[")
			split2 := strings.Split(split1[1], "]")
			insides := split2[0]
			split1 = strings.Split(insides, ":")
			branch := split1[0]
			if strings.Contains(branch, "/") {
				split2 = strings.Split(branch, "/")
				branch = split2[len(split2)-1]
			}
			branch = strings.TrimSpace(branch)
			if len(branch) == 0 {
				continue
			}
			if strings.Contains(insides, "ahead ") || strings.Contains(insides, "behind ") {
				branchesToReset = append(branchesToReset, branch)
			}
			locallyTrackedBranches[branch] = true
		}
	}

	// Track branches not being tracked.
	//       BRANCHES NOT BEING TRACKED
	//                 =
	//   SET OF BRANCHES FROM git branch -r
	//                  -
	//   SET OF BRANCHES FROM git branch -vv
	start := time.Now()
	for branch := range remotelyTrackedBranches {
		if _, ok := locallyTrackedBranches[branch]; !ok {
			cmd = exec.Command("git", "branch", "--track", branch, "origin/"+branch)
			cmd.Output()
		}
	}
	logger.Debug("Tracking took " + time.Since(start).String())

	// Find ANY that have "ahead" or "behind", and do
	//      git checkout branch
	//      git reset --hard HEAD
	// 			git rebase
	for _, branch := range branchesToReset {
		logger.Debug("Resetting branch %s", branch)
		cmd = exec.Command("git", "checkout", branch)
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot checkout" + branch)
		}
		cmd = exec.Command("git", "reset", "--hard", "HEAD")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot reset --hard HEAD of " + branch)
		}
		cmd = exec.Command("git", "rebase")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot rebase " + branch)
		}
		cmd = exec.Command("git", "checkout", "master")
		stdout, err = cmd.Output()
		if err != nil {
			return errors.New("Cannot checkout master")
		}
	}

	// NOT NEEDED - THIS IS TAKEN CARE OF WITH FETCH --FORCE
	// // Find if branches are no longer on remote and delete them locally
	// localBranches, _ := ListBranches("./")
	// for _, localBranch := range localBranches {
	// 	if _, ok := allBranches[localBranch]; !ok {
	// 		logger.Debug("Deleted locally '%s' - branch no longer on remote", localBranch)
	// 		cmd = exec.Command("git", "branch", "-D", localBranch)
	// 		_, err = cmd.Output()
	// 		if err != nil {
	// 			return errors.New("Problem deleting branch " + localBranch)
	// 		}
	// 	}
	// }

	return err2
}

func NewDocument(gitfolder string, documentname string, fulltext string, message string, datestring string, branchNameOverride string) (string, error) {
	defer timeTrack(time.Now(), "NewDocument")
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	newBranch := branchNameOverride
	if len(branchNameOverride) == 0 {
		newBranch = MakeAlliteration()
	}
	cmd := exec.Command("git", "checkout", "--orphan", newBranch)
	_, err = cmd.Output()
	if err != nil {
		cmd2 := exec.Command("git", "checkout", newBranch)
		_, err2 := cmd2.Output()
		if err2 != nil {
			return newBranch, errors.New("Cannot checkout branch " + newBranch)
		}
	}

	err = ioutil.WriteFile(documentname, []byte(fulltext), 0644)
	if err != nil {
		return newBranch, errors.New("Cannot write file " + documentname)
	}
	if Encrypt {
		err = EncryptFile(documentname, Passphrase)
		if err != nil {
			return newBranch, err
		}
		documentname += ".gpg"
		message = EncryptString(message, Passphrase)
	}

	cmd = exec.Command("git", "add", documentname)
	_, err = cmd.Output()
	if err != nil {
		return newBranch, errors.New("Cannot add file " + documentname)
	}

	cmd = exec.Command("git", "commit", "--date", datestring, "-m", message, documentname)
	out2, _ := cmd.StderrPipe()
	cmd.Start()
	out2b, _ := ioutil.ReadAll(out2)
	cmd.Wait()
	if strings.Contains(string(out2b), "error") {
		fmt.Println(string(out2b))
		return newBranch, errors.New("Cannot commit " + documentname + " error: " + err.Error())
	}

	logger.Debug("Updated document %s in branch %s", documentname, newBranch)

	// Check if its a new document
	_, errExistence := GetTextOfOne("./", "master", documentname)
	if errExistence != nil {
		logger.Debug("It seems %s doesn't exist yet, making a index file for it in master", documentname)
		dir, _ := os.Getwd()
		logger.Debug("Current DIR: %s", dir)
		cmd2 := exec.Command("git", "checkout", "-f", "master")
		_, err2 := cmd2.Output()
		if err2 != nil {
			cmd2 = exec.Command("git", "checkout", "--orphan", "master")
			_, err2 = cmd2.Output()
			if err2 != nil {
				logger.Error("Couldn't checkout master")
			}
		}
		text := "Yay some text!"
		if Encrypt {
			text = EncryptString(text, Passphrase)
		}
		err2 = ioutil.WriteFile(documentname, []byte(text), 0644)
		if err != nil {
			logger.Warn("Something wrong with writing " + documentname)
		}
		cmd2 = exec.Command("git", "add", documentname)
		_, err2 = cmd2.Output()
		if err2 != nil {
			logger.Warn("Something wrong adding " + documentname)
		}
		cmd2 = exec.Command("git", "commit", "--date", datestring, "-m", message, documentname)
		_, err2 = cmd2.Output()
		if err2 != nil {
			logger.Warn("Something wrong: %s ", strings.Join([]string{"git", "commit", "--date", datestring, "-m", message, documentname}, " "))
		}
	}

	return newBranch, err
}

func Push(gitfolder string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Pushing %s", id, gitfolder)
	defer timeTrack(time.Now(), "["+id+"]Pushing")
	var err error
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)
	os.Chdir(gitfolder)

	cmd := exec.Command("git", "push", "--force", "--all", "origin")
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Cannot push " + gitfolder)
	}

	return nil
}

func Clone(folder string, remote string) error {
	id := RandStringBytesMaskImprSrc(4, time.Now().UnixNano())
	logger.Debug("[%s]Cloning %s", id, remote)
	defer timeTrack(time.Now(), "["+id+"]Cloning")
	var err error
	logger.Debug("Cloning %s into directory at %s", remote, folder)
	cwd, _ := os.Getwd()
	defer os.Chdir(cwd)

	cmd := exec.Command("git", "clone", remote, folder)
	_, err = cmd.Output()
	if err != nil {
		return errors.New("Cloning command failed: '" + strings.Join([]string{"git", "clone", remote, folder}, " ") + "'")
	}

	Fetch(folder)

	return nil
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	"github.com/robfig/humanize"
	"github.com/starius/pasta/gopasta/database"
	"golang.org/x/net/idna"
)

//go:generate go get github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg server -nometadata -nomemcopy -nocompress -prefix $GOPATH/src/github.com/starius/pasta/gopasta/server -o $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.ico
//go:generate go fmt $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go

type IDEncoder interface {
	Encode(id uint64, longID bool) (phrase string, err error)
	Decode(phrase string) (id uint64, longID bool, err error)
}

type Handler struct {
	db        *database.Database
	idEncoder IDEncoder

	mux *http.ServeMux

	maxSize int64

	adminAuth string
}

func NewHandler(db *database.Database, idEncoder IDEncoder, maxSize int, adminAuth string) *Handler {
	faviconReader := bytes.NewReader(MustAsset("favicon.ico"))
	faviconHandler := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
		http.ServeContent(w, r, "favicon.ico", time.Unix(0, 0), faviconReader)
	}

	h := &Handler{db, idEncoder, http.NewServeMux(), int64(maxSize), adminAuth}
	h.mux.HandleFunc("/favicon.ico", faviconHandler)
	h.mux.HandleFunc("/api/create", h.handleUpload)
	h.mux.HandleFunc("/", h.handleRecord)
	return h
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mux.ServeHTTP(w, r)
}

var punycode = idna.New()

func (h *Handler) handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		log.Printf("handleUpload was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, h.maxSize)
	if err := r.ParseMultipartForm(h.maxSize); err != nil {
		log.Printf("ParseMultipartForm: %v", err)
		http.Error(w, "failed to parse the form", http.StatusBadRequest)
		return
	}
	content := []byte(r.FormValue("content"))
	filename := r.FormValue("filename")
	selfBurning := r.FormValue("self_burning") != ""
	redirect := r.FormValue("redirect") != ""
	longID := r.FormValue("long_id") != ""
	if file, header, err := r.FormFile("file"); err == nil {
		if len(content) != 0 || filename != "" {
			log.Printf("Provided both text content and file")
			http.Error(w, "Provided both text content and file", http.StatusBadRequest)
			return
		}
		content, err = ioutil.ReadAll(file)
		if err != nil {
			log.Printf("Reading multipart file: %v.", err)
			http.Error(w, "Failed to read the file", http.StatusBadRequest)
			return
		}
		filename = header.Filename
	}
	if redirect {
		content = bytes.TrimSpace(content)
		u, err := url.Parse(string(content))
		if err != nil {
			log.Printf("URL is invalid: %v.", err)
			http.Error(w, "URL is invalid", http.StatusBadRequest)
			return
		}
		if !u.IsAbs() {
			log.Printf("URL is not absolute.")
			http.Error(w, "URL is  not absolute", http.StatusBadRequest)
			return
		}
		content = []byte(u.String())
	}
	ctype := ""
	if !redirect {
		ctype = mime.TypeByExtension(filepath.Ext(filename))
		if ctype == "" {
			ctype = http.DetectContentType(content)
		}
	}
	record := &database.Record{
		Content:     content,
		Filename:    filename,
		SelfBurning: selfBurning,
		Redirect:    redirect,
		LongId:      longID,
		ContentType: ctype,
	}
	id, err := h.db.Add(record)
	if err != nil {
		log.Printf("Add: %v.", err)
		http.Error(w, "failed to save the record", http.StatusInternalServerError)
		return
	}
	phrase, err := h.idEncoder.Encode(id, longID)
	if err != nil {
		log.Printf("idEncoder.Encode: %v.", err)
		http.Error(w, "failed to create the link", http.StatusInternalServerError)
		return
	}
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	host, err := punycode.ToUnicode(r.Host)
	if err != nil {
		log.Printf("punycode.ToUnicode(%q): %v.", r.Host, err)
		host = r.Host
	}
	targetURL := fmt.Sprintf("%s://%s/%s", scheme, host, phrase)
	if !selfBurning && !redirect && contentTypeIsInline(ctype) {
		http.Redirect(w, r, targetURL, http.StatusFound)
	}
	fmt.Fprintf(w, "Your link: %s\n", targetURL)
}

func (h *Handler) handleRecord(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodDelete {
		log.Printf("handleRecord was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	if (r.Method == http.MethodGet || r.Method == http.MethodHead) && (r.URL.Path == "" || r.URL.Path == "/") {
		h.handleMain(w, r)
		return
	}
	if r.Method == http.MethodDelete {
		auth := r.Header.Get("Authorization")
		if subtle.ConstantTimeCompare([]byte(auth), []byte(h.adminAuth)) != 1 {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
	}
	pathParts := strings.Split(r.URL.Path, "/")
	phrase := pathParts[len(pathParts)-1]
	if phrase == "" {
		if len(pathParts) == 1 {
			log.Printf("Bad URL: %s.", r.URL.Path)
			http.Error(w, "bad link", http.StatusBadRequest)
			return
		}
		phrase = pathParts[len(pathParts)-2]
	}
	id, longID, err := h.idEncoder.Decode(phrase)
	if err != nil {
		log.Printf("idEncoder.Decode(%q): %v.", phrase, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if r.Method == http.MethodDelete {
		if err := h.db.Delete(id); err != nil {
			log.Printf("db.Delete(%d): %v.", id, err)
			http.Error(w, "failed to delete the link", http.StatusInternalServerError)
		}
		return
	}
	record, err := h.db.Lookup(id)
	if err != nil {
		log.Printf("db.Lookup(%d): %v.", id, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.LongId != longID {
		log.Printf("long ID mismatch in %d.", id)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.Redirect {
		status := http.StatusMovedPermanently
		if record.SelfBurning {
			status = http.StatusFound
		}
		http.Redirect(w, r, string(record.Content), status)
	} else {
		if record.Filename != "" {
			w.Header().Add("Content-Disposition", fmt.Sprintf("inline; filename=%q", record.Filename))
		}
		if record.ContentType != "" {
			w.Header().Add("Content-Type", record.ContentType)
		}
		contentReader := bytes.NewReader(record.Content)
		http.ServeContent(w, r, record.Filename, time.Unix(0, 0), contentReader)
	}
}

func (h *Handler) handleMain(w http.ResponseWriter, r *http.Request) {
	vars := struct {
		FileTab bool
		MaxSize string
		Uploads string
	}{
		FileTab: r.FormValue("filetab") == "on",
		MaxSize: humanize.IBytes(uint64(h.maxSize)),
		Uploads: humanize.Comma(h.db.RecordsCount()),
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := mainTemplate.Execute(w, vars); err != nil {
		log.Printf("failed to execute template: %v", err)
	}
}
<commit_msg>gopasta: drop requests with empty content<commit_after>package server

import (
	"bytes"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"path/filepath"
	"strings"
	"time"

	"github.com/robfig/humanize"
	"github.com/starius/pasta/gopasta/database"
	"golang.org/x/net/idna"
)

//go:generate go get github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg server -nometadata -nomemcopy -nocompress -prefix $GOPATH/src/github.com/starius/pasta/gopasta/server -o $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.ico
//go:generate go fmt $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go

type IDEncoder interface {
	Encode(id uint64, longID bool) (phrase string, err error)
	Decode(phrase string) (id uint64, longID bool, err error)
}

type Handler struct {
	db        *database.Database
	idEncoder IDEncoder

	mux *http.ServeMux

	maxSize int64

	adminAuth string
}

func NewHandler(db *database.Database, idEncoder IDEncoder, maxSize int, adminAuth string) *Handler {
	faviconReader := bytes.NewReader(MustAsset("favicon.ico"))
	faviconHandler := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
		http.ServeContent(w, r, "favicon.ico", time.Unix(0, 0), faviconReader)
	}

	h := &Handler{db, idEncoder, http.NewServeMux(), int64(maxSize), adminAuth}
	h.mux.HandleFunc("/favicon.ico", faviconHandler)
	h.mux.HandleFunc("/api/create", h.handleUpload)
	h.mux.HandleFunc("/", h.handleRecord)
	return h
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mux.ServeHTTP(w, r)
}

var punycode = idna.New()

func (h *Handler) handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		log.Printf("handleUpload was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, h.maxSize)
	if err := r.ParseMultipartForm(h.maxSize); err != nil {
		log.Printf("ParseMultipartForm: %v", err)
		http.Error(w, "failed to parse the form", http.StatusBadRequest)
		return
	}
	content := []byte(r.FormValue("content"))
	filename := r.FormValue("filename")
	selfBurning := r.FormValue("self_burning") != ""
	redirect := r.FormValue("redirect") != ""
	longID := r.FormValue("long_id") != ""
	if file, header, err := r.FormFile("file"); err == nil {
		if len(content) != 0 || filename != "" {
			log.Printf("Provided both text content and file")
			http.Error(w, "Provided both text content and file", http.StatusBadRequest)
			return
		}
		content, err = ioutil.ReadAll(file)
		if err != nil {
			log.Printf("Reading multipart file: %v.", err)
			http.Error(w, "Failed to read the file", http.StatusBadRequest)
			return
		}
		filename = header.Filename
	}
	if redirect {
		content = bytes.TrimSpace(content)
		u, err := url.Parse(string(content))
		if err != nil {
			log.Printf("URL is invalid: %v.", err)
			http.Error(w, "URL is invalid", http.StatusBadRequest)
			return
		}
		if !u.IsAbs() {
			log.Printf("URL is not absolute.")
			http.Error(w, "URL is  not absolute", http.StatusBadRequest)
			return
		}
		content = []byte(u.String())
	}
	if len(content) == 0 {
		log.Printf("Empty content.")
		http.Error(w, "Empty content.", http.StatusBadRequest)
		return
	}
	ctype := ""
	if !redirect {
		ctype = mime.TypeByExtension(filepath.Ext(filename))
		if ctype == "" {
			ctype = http.DetectContentType(content)
		}
	}
	record := &database.Record{
		Content:     content,
		Filename:    filename,
		SelfBurning: selfBurning,
		Redirect:    redirect,
		LongId:      longID,
		ContentType: ctype,
	}
	id, err := h.db.Add(record)
	if err != nil {
		log.Printf("Add: %v.", err)
		http.Error(w, "failed to save the record", http.StatusInternalServerError)
		return
	}
	phrase, err := h.idEncoder.Encode(id, longID)
	if err != nil {
		log.Printf("idEncoder.Encode: %v.", err)
		http.Error(w, "failed to create the link", http.StatusInternalServerError)
		return
	}
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	host, err := punycode.ToUnicode(r.Host)
	if err != nil {
		log.Printf("punycode.ToUnicode(%q): %v.", r.Host, err)
		host = r.Host
	}
	targetURL := fmt.Sprintf("%s://%s/%s", scheme, host, phrase)
	if !selfBurning && !redirect && contentTypeIsInline(ctype) {
		http.Redirect(w, r, targetURL, http.StatusFound)
	}
	fmt.Fprintf(w, "Your link: %s\n", targetURL)
}

func (h *Handler) handleRecord(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodDelete {
		log.Printf("handleRecord was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	if (r.Method == http.MethodGet || r.Method == http.MethodHead) && (r.URL.Path == "" || r.URL.Path == "/") {
		h.handleMain(w, r)
		return
	}
	if r.Method == http.MethodDelete {
		auth := r.Header.Get("Authorization")
		if subtle.ConstantTimeCompare([]byte(auth), []byte(h.adminAuth)) != 1 {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
	}
	pathParts := strings.Split(r.URL.Path, "/")
	phrase := pathParts[len(pathParts)-1]
	if phrase == "" {
		if len(pathParts) == 1 {
			log.Printf("Bad URL: %s.", r.URL.Path)
			http.Error(w, "bad link", http.StatusBadRequest)
			return
		}
		phrase = pathParts[len(pathParts)-2]
	}
	id, longID, err := h.idEncoder.Decode(phrase)
	if err != nil {
		log.Printf("idEncoder.Decode(%q): %v.", phrase, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if r.Method == http.MethodDelete {
		if err := h.db.Delete(id); err != nil {
			log.Printf("db.Delete(%d): %v.", id, err)
			http.Error(w, "failed to delete the link", http.StatusInternalServerError)
		}
		return
	}
	record, err := h.db.Lookup(id)
	if err != nil {
		log.Printf("db.Lookup(%d): %v.", id, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.LongId != longID {
		log.Printf("long ID mismatch in %d.", id)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.Redirect {
		status := http.StatusMovedPermanently
		if record.SelfBurning {
			status = http.StatusFound
		}
		http.Redirect(w, r, string(record.Content), status)
	} else {
		if record.Filename != "" {
			w.Header().Add("Content-Disposition", fmt.Sprintf("inline; filename=%q", record.Filename))
		}
		if record.ContentType != "" {
			w.Header().Add("Content-Type", record.ContentType)
		}
		contentReader := bytes.NewReader(record.Content)
		http.ServeContent(w, r, record.Filename, time.Unix(0, 0), contentReader)
	}
}

func (h *Handler) handleMain(w http.ResponseWriter, r *http.Request) {
	vars := struct {
		FileTab bool
		MaxSize string
		Uploads string
	}{
		FileTab: r.FormValue("filetab") == "on",
		MaxSize: humanize.IBytes(uint64(h.maxSize)),
		Uploads: humanize.Comma(h.db.RecordsCount()),
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := mainTemplate.Execute(w, vars); err != nil {
		log.Printf("failed to execute template: %v", err)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/robfig/humanize"
	"github.com/starius/pasta/gopasta/database"
)

//go:generate go get github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg server -nometadata -nomemcopy -nocompress -prefix $GOPATH/src/github.com/starius/pasta/gopasta/server -o $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.ico
//go:generate go fmt $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go

type IDEncoder interface {
	Encode(id uint64, longID bool) (phrase string, err error)
	Decode(phrase string) (id uint64, longID bool, err error)
}

type Handler struct {
	db        *database.Database
	idEncoder IDEncoder

	mux *http.ServeMux

	maxSize int64

	adminAuth string
}

func NewHandler(db *database.Database, idEncoder IDEncoder, maxSize int, adminAuth string) *Handler {
	faviconReader := bytes.NewReader(MustAsset("favicon.ico"))
	faviconHandler := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
		http.ServeContent(w, r, "favicon.ico", time.Unix(0, 0), faviconReader)
	}

	h := &Handler{db, idEncoder, http.NewServeMux(), int64(maxSize), adminAuth}
	h.mux.HandleFunc("/favicon.ico", faviconHandler)
	h.mux.HandleFunc("/api/create", h.handleUpload)
	h.mux.HandleFunc("/", h.handleRecord)
	return h
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mux.ServeHTTP(w, r)
}

func (h *Handler) handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		log.Printf("handleUpload was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, h.maxSize)
	if err := r.ParseMultipartForm(h.maxSize); err != nil {
		log.Printf("ParseMultipartForm: %v", err)
		http.Error(w, "failed to parse the form", http.StatusBadRequest)
		return
	}
	content := []byte(r.FormValue("content"))
	filename := r.FormValue("filename")
	selfBurning := r.FormValue("self_burning") != ""
	redirect := r.FormValue("redirect") != ""
	longID := r.FormValue("long_id") != ""
	if file, header, err := r.FormFile("file"); err == nil {
		if len(content) != 0 || filename != "" {
			log.Printf("Provided both text content and file")
			http.Error(w, "Provided both text content and file", http.StatusBadRequest)
			return
		}
		content, err = ioutil.ReadAll(file)
		if err != nil {
			log.Printf("Reading multipart file: %v.", err)
			http.Error(w, "Failed to read the file", http.StatusBadRequest)
			return
		}
		filename = header.Filename
	}
	if redirect {
		content = bytes.TrimSpace(content)
		u, err := url.Parse(string(content))
		if err != nil {
			log.Printf("URL is invalid: %v.", err)
			http.Error(w, "URL is invalid", http.StatusBadRequest)
			return
		}
		if !u.IsAbs() {
			log.Printf("URL is not absolute.")
			http.Error(w, "URL is  not absolute", http.StatusBadRequest)
			return
		}
		content = []byte(u.String())
	}
	record := &database.Record{
		Content:     content,
		Filename:    filename,
		SelfBurning: selfBurning,
		Redirect:    redirect,
		LongId:      longID,
	}
	id, err := h.db.Add(record)
	if err != nil {
		log.Printf("Add: %v.", err)
		http.Error(w, "failed to save the record", http.StatusInternalServerError)
		return
	}
	phrase, err := h.idEncoder.Encode(id, longID)
	if err != nil {
		log.Printf("idEncoder.Encode: %v.", err)
		http.Error(w, "failed to create the link", http.StatusInternalServerError)
		return
	}
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	targetURL := fmt.Sprintf("%s://%s/%s", scheme, r.Host, phrase)
	if !selfBurning && !redirect {
		http.Redirect(w, r, targetURL, http.StatusFound)
	}
	fmt.Fprintf(w, "Your link: %s\n", targetURL)
}

func (h *Handler) handleRecord(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodDelete {
		log.Printf("handleRecord was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	if (r.Method == http.MethodGet || r.Method == http.MethodHead) && (r.URL.Path == "" || r.URL.Path == "/") {
		h.handleMain(w, r)
		return
	}
	if r.Method == http.MethodDelete {
		auth := r.Header.Get("Authorization")
		if subtle.ConstantTimeCompare([]byte(auth), []byte(h.adminAuth)) != 1 {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
	}
	pathParts := strings.Split(r.URL.Path, "/")
	phrase := pathParts[len(pathParts)-1]
	if phrase == "" {
		if len(pathParts) == 1 {
			log.Printf("Bad URL: %s.", r.URL.Path)
			http.Error(w, "bad link", http.StatusBadRequest)
			return
		}
		phrase = pathParts[len(pathParts)-2]
	}
	id, longID, err := h.idEncoder.Decode(phrase)
	if err != nil {
		log.Printf("idEncoder.Decode(%q): %v.", phrase, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if r.Method == http.MethodDelete {
		if err := h.db.Delete(id); err != nil {
			log.Printf("db.Delete(%d): %v.", id, err)
			http.Error(w, "failed to delete the link", http.StatusInternalServerError)
		}
		return
	}
	record, err := h.db.Lookup(id)
	if err != nil {
		log.Printf("db.Lookup(%d): %v.", id, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.LongId != longID {
		log.Printf("long ID mismatch in %d.", id)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.Redirect {
		status := http.StatusMovedPermanently
		if record.SelfBurning {
			status = http.StatusFound
		}
		http.Redirect(w, r, string(record.Content), status)
	} else {
		if record.Filename != "" {
			w.Header().Add("Content-Disposition", fmt.Sprintf("inline; filename=%q", record.Filename))
		}
		contentReader := bytes.NewReader(record.Content)
		http.ServeContent(w, r, record.Filename, time.Unix(0, 0), contentReader)
	}
}

func (h *Handler) handleMain(w http.ResponseWriter, r *http.Request) {
	vars := struct {
		MaxSize string
		Uploads string
	}{
		MaxSize: humanize.IBytes(uint64(h.maxSize)),
		Uploads: humanize.Comma(h.db.RecordsCount()),
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := mainTemplate.Execute(w, vars); err != nil {
		log.Printf("failed to execute template: %v", err)
	}
}
<commit_msg>gopasta: convert domains to Unicode in the link<commit_after>package server

import (
	"bytes"
	"crypto/subtle"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/robfig/humanize"
	"github.com/starius/pasta/gopasta/database"
	"golang.org/x/net/idna"
)

//go:generate go get github.com/jteeuwen/go-bindata/go-bindata
//go:generate go-bindata -pkg server -nometadata -nomemcopy -nocompress -prefix $GOPATH/src/github.com/starius/pasta/gopasta/server -o $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.ico
//go:generate go fmt $GOPATH/src/github.com/starius/pasta/gopasta/server/favicon.go

type IDEncoder interface {
	Encode(id uint64, longID bool) (phrase string, err error)
	Decode(phrase string) (id uint64, longID bool, err error)
}

type Handler struct {
	db        *database.Database
	idEncoder IDEncoder

	mux *http.ServeMux

	maxSize int64

	adminAuth string
}

func NewHandler(db *database.Database, idEncoder IDEncoder, maxSize int, adminAuth string) *Handler {
	faviconReader := bytes.NewReader(MustAsset("favicon.ico"))
	faviconHandler := func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "image/vnd.microsoft.icon")
		http.ServeContent(w, r, "favicon.ico", time.Unix(0, 0), faviconReader)
	}

	h := &Handler{db, idEncoder, http.NewServeMux(), int64(maxSize), adminAuth}
	h.mux.HandleFunc("/favicon.ico", faviconHandler)
	h.mux.HandleFunc("/api/create", h.handleUpload)
	h.mux.HandleFunc("/", h.handleRecord)
	return h
}

func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.mux.ServeHTTP(w, r)
}

var punycode = idna.New()

func (h *Handler) handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		log.Printf("handleUpload was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	r.Body = http.MaxBytesReader(w, r.Body, h.maxSize)
	if err := r.ParseMultipartForm(h.maxSize); err != nil {
		log.Printf("ParseMultipartForm: %v", err)
		http.Error(w, "failed to parse the form", http.StatusBadRequest)
		return
	}
	content := []byte(r.FormValue("content"))
	filename := r.FormValue("filename")
	selfBurning := r.FormValue("self_burning") != ""
	redirect := r.FormValue("redirect") != ""
	longID := r.FormValue("long_id") != ""
	if file, header, err := r.FormFile("file"); err == nil {
		if len(content) != 0 || filename != "" {
			log.Printf("Provided both text content and file")
			http.Error(w, "Provided both text content and file", http.StatusBadRequest)
			return
		}
		content, err = ioutil.ReadAll(file)
		if err != nil {
			log.Printf("Reading multipart file: %v.", err)
			http.Error(w, "Failed to read the file", http.StatusBadRequest)
			return
		}
		filename = header.Filename
	}
	if redirect {
		content = bytes.TrimSpace(content)
		u, err := url.Parse(string(content))
		if err != nil {
			log.Printf("URL is invalid: %v.", err)
			http.Error(w, "URL is invalid", http.StatusBadRequest)
			return
		}
		if !u.IsAbs() {
			log.Printf("URL is not absolute.")
			http.Error(w, "URL is  not absolute", http.StatusBadRequest)
			return
		}
		content = []byte(u.String())
	}
	record := &database.Record{
		Content:     content,
		Filename:    filename,
		SelfBurning: selfBurning,
		Redirect:    redirect,
		LongId:      longID,
	}
	id, err := h.db.Add(record)
	if err != nil {
		log.Printf("Add: %v.", err)
		http.Error(w, "failed to save the record", http.StatusInternalServerError)
		return
	}
	phrase, err := h.idEncoder.Encode(id, longID)
	if err != nil {
		log.Printf("idEncoder.Encode: %v.", err)
		http.Error(w, "failed to create the link", http.StatusInternalServerError)
		return
	}
	scheme := "http"
	if r.TLS != nil {
		scheme = "https"
	}
	host, err := punycode.ToUnicode(r.Host)
	if err != nil {
		log.Printf("punycode.ToUnicode(%q): %v.", r.Host, err)
		host = r.Host
	}
	targetURL := fmt.Sprintf("%s://%s/%s", scheme, host, phrase)
	if !selfBurning && !redirect {
		http.Redirect(w, r, targetURL, http.StatusFound)
	}
	fmt.Fprintf(w, "Your link: %s\n", targetURL)
}

func (h *Handler) handleRecord(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet && r.Method != http.MethodHead && r.Method != http.MethodDelete {
		log.Printf("handleRecord was called with method %q.", r.Method)
		http.Error(w, "bad method", http.StatusMethodNotAllowed)
		return
	}
	if (r.Method == http.MethodGet || r.Method == http.MethodHead) && (r.URL.Path == "" || r.URL.Path == "/") {
		h.handleMain(w, r)
		return
	}
	if r.Method == http.MethodDelete {
		auth := r.Header.Get("Authorization")
		if subtle.ConstantTimeCompare([]byte(auth), []byte(h.adminAuth)) != 1 {
			http.Error(w, "unauthorized", http.StatusUnauthorized)
			return
		}
	}
	pathParts := strings.Split(r.URL.Path, "/")
	phrase := pathParts[len(pathParts)-1]
	if phrase == "" {
		if len(pathParts) == 1 {
			log.Printf("Bad URL: %s.", r.URL.Path)
			http.Error(w, "bad link", http.StatusBadRequest)
			return
		}
		phrase = pathParts[len(pathParts)-2]
	}
	id, longID, err := h.idEncoder.Decode(phrase)
	if err != nil {
		log.Printf("idEncoder.Decode(%q): %v.", phrase, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if r.Method == http.MethodDelete {
		if err := h.db.Delete(id); err != nil {
			log.Printf("db.Delete(%d): %v.", id, err)
			http.Error(w, "failed to delete the link", http.StatusInternalServerError)
		}
		return
	}
	record, err := h.db.Lookup(id)
	if err != nil {
		log.Printf("db.Lookup(%d): %v.", id, err)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.LongId != longID {
		log.Printf("long ID mismatch in %d.", id)
		http.Error(w, "bad link", http.StatusBadRequest)
		return
	}
	if record.Redirect {
		status := http.StatusMovedPermanently
		if record.SelfBurning {
			status = http.StatusFound
		}
		http.Redirect(w, r, string(record.Content), status)
	} else {
		if record.Filename != "" {
			w.Header().Add("Content-Disposition", fmt.Sprintf("inline; filename=%q", record.Filename))
		}
		contentReader := bytes.NewReader(record.Content)
		http.ServeContent(w, r, record.Filename, time.Unix(0, 0), contentReader)
	}
}

func (h *Handler) handleMain(w http.ResponseWriter, r *http.Request) {
	vars := struct {
		MaxSize string
		Uploads string
	}{
		MaxSize: humanize.IBytes(uint64(h.maxSize)),
		Uploads: humanize.Comma(h.db.RecordsCount()),
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := mainTemplate.Execute(w, vars); err != nil {
		log.Printf("failed to execute template: %v", err)
	}
}
<|endoftext|>


<commit_before>package parse // import "github.com/tdewolff/parse"

import (
	"errors"
	"io"
)

// minBuf and maxBuf are the initial and maximal internal buffer size.
var MinBuf = 1024
var MaxBuf = 4096

// ErrBufferExceeded is returned when the internal buffer exceeds 4096 bytes, a string or comment must thus be smaller than 4kB!
var ErrBufferExceeded = errors.New("max buffer exceeded")

////////////////////////////////////////////////////////////////

// ShiftBuffer is a buffered reader that allows peeking forward and shifting, taking an io.Reader.
type ShiftBuffer struct {
	r       io.Reader
	readErr error

	buf []byte
	pos int
	n   int

	minBuf, maxBuf int
}

// NewShiftBufferReader returns a new ShiftBuffer.
func NewShiftBuffer(r io.Reader) *ShiftBuffer {
	// If reader has the bytes in memory already, use that instead!
	if fr, ok := r.(interface {
		Bytes() []byte
	}); ok {
		return &ShiftBuffer{
			readErr: io.EOF,
			buf:     fr.Bytes(),
		}
	}
	return &ShiftBuffer{
		r:   r,
		buf: make([]byte, 0, MinBuf),
	}
}

// Err returns the error.
func (z ShiftBuffer) Err() error {
	if z.readErr == io.EOF && z.pos+z.n < len(z.buf) {
		return nil
	}
	return z.readErr
}

// Move advances the 0 position of read.
func (z *ShiftBuffer) Move(n int) {
	z.n += n
}

// MoveTo sets the 0 position of read.
func (z *ShiftBuffer) MoveTo(n int) {
	z.n = n
}

// Pos returns the 0 position of read.
func (z ShiftBuffer) Pos() int {
	return z.n
}

// Len returns the length of the buffer.
func (z ShiftBuffer) Len() int {
	return len(z.buf)
}

// Peek returns the ith byte and possible does a reallocation
func (z *ShiftBuffer) Peek(i int) byte {
	if z.pos+z.n+i >= len(z.buf) {
		if z.readErr != nil {
			return 0
		}

		// reallocate a new buffer (possibly larger)
		c := cap(z.buf)
		d := z.n + i
		var buf1 []byte
		if 2*d > c {
			if 2*c > MaxBuf {
				z.readErr = ErrBufferExceeded
				return 0
			}
			buf1 = make([]byte, d, 2*c)
		} else {
			buf1 = z.buf[:d]
		}
		copy(buf1, z.buf[z.pos:z.pos+d])

		// Read in to fill the buffer till capacity
		var n int
		n, z.readErr = z.r.Read(buf1[d:cap(buf1)])
		z.pos, z.buf = 0, buf1[:d+n]
		if n == 0 {
			return 0
		}
	}
	return z.buf[z.pos+z.n+i]
}

// Buffered returns the bytes of the current selection.
func (z ShiftBuffer) Buffered() []byte {
	return z.buf[z.pos : z.pos+z.n]
}

// Shift returns the bytes of the current selection and advances the position.
func (z *ShiftBuffer) Shift() []byte {
	b := z.buf[z.pos : z.pos+z.n]
	z.pos += z.n
	z.n = 0
	return b
}
<commit_msg>Shiftbuffer returns error when moved too far<commit_after>package parse // import "github.com/tdewolff/parse"

import (
	"errors"
	"io"
)

// minBuf and maxBuf are the initial and maximal internal buffer size.
var MinBuf = 1024
var MaxBuf = 4096

// ErrBufferExceeded is returned when the internal buffer exceeds 4096 bytes, a string or comment must thus be smaller than 4kB!
var ErrBufferExceeded = errors.New("max buffer exceeded")

////////////////////////////////////////////////////////////////

// ShiftBuffer is a buffered reader that allows peeking forward and shifting, taking an io.Reader.
type ShiftBuffer struct {
	r       io.Reader
	readErr error

	buf []byte
	pos int
	n   int
}

// NewShiftBufferReader returns a new ShiftBuffer.
func NewShiftBuffer(r io.Reader) *ShiftBuffer {
	// If reader has the bytes in memory already, use that instead!
	if fr, ok := r.(interface {
		Bytes() []byte
	}); ok {
		return &ShiftBuffer{
			readErr: io.EOF,
			buf:     fr.Bytes(),
		}
	}

	b := &ShiftBuffer{
		r:   r,
		buf: make([]byte, 0, MinBuf),
	}
	b.Peek(0)
	return b
}

// Err returns the error.
func (z ShiftBuffer) Err() error {
	if z.readErr == io.EOF && z.pos+z.n < len(z.buf) {
		return nil
	}
	return z.readErr
}

// Move advances the 0 position of read.
func (z *ShiftBuffer) Move(n int) {
	z.n += n
}

// MoveTo sets the 0 position of read.
func (z *ShiftBuffer) MoveTo(n int) {
	z.n = n
}

// Pos returns the 0 position of read.
func (z ShiftBuffer) Pos() int {
	return z.n
}

// Len returns the length of the buffer.
func (z ShiftBuffer) Len() int {
	return len(z.buf) - z.pos
}

// Peek returns the ith byte and possible does a reallocation
func (z *ShiftBuffer) Peek(i int) byte {
	if z.pos+z.n+i >= len(z.buf) {
		if z.readErr != nil {
			return 0
		}

		// reallocate a new buffer (possibly larger)
		c := cap(z.buf)
		d := z.n + i
		var buf1 []byte
		if 2*d > c {
			if 2*c > MaxBuf {
				z.readErr = ErrBufferExceeded
				return 0
			}
			buf1 = make([]byte, d, 2*c)
		} else if d >= len(z.buf) {
			z.readErr = ErrBufferExceeded
			return 0
		} else {
			buf1 = z.buf[:d]
		}
		copy(buf1, z.buf[z.pos:z.pos+d])

		// Read in to fill the buffer till capacity
		var n int
		n, z.readErr = z.r.Read(buf1[d:cap(buf1)])
		z.pos, z.buf = 0, buf1[:d+n]
		if n == 0 {
			return 0
		}
	}
	return z.buf[z.pos+z.n+i]
}

// Buffered returns the bytes of the current selection.
func (z ShiftBuffer) Buffered() []byte {
	return z.buf[z.pos : z.pos+z.n]
}

// Shift returns the bytes of the current selection and advances the position.
func (z *ShiftBuffer) Shift() []byte {
	b := z.buf[z.pos : z.pos+z.n]
	z.pos += z.n
	z.n = 0
	return b
}
<|endoftext|>


<commit_before>/*
 Copyright 2015 Bluek404

 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 nwui

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
)

var (
	// 用于生成随机数
	r    = rand.New(rand.NewSource(time.Now().UnixNano()))
	cons = make(map[string]interface{})
	temp = `<!DOCTYPE html>

<meta charset="utf-8">
<title>%v</title>

%v

`
	defaultTheme = Theme{
		CSS: `button {
    border: 4px solid #304ffe;
    color: white;
    background: #304ffe;
    padding: 6px 12px;
}
button:hover {
    background: white;
    color: #304ffe;
}
button:active {
    color: #fff;
    background: #304ffe;
    box-shadow: 1px 2px 7px rgba(0, 0, 0, 0.3) inset;
}
.frame {
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 32px;
    background-color: #424242;
    -webkit-app-region: drag;
}
.frame .title {
    color: white;
    position: absolute;
    left: 12px;
    width: 80%;
    margin-top: 6px;
    margin-bottom: 6px;
    font-size: 11pt;
}
.frame button#close {
    position: absolute;
    left: auto;
    right: 12px;
    width: auto;
    font-size: 11pt;
    -webkit-app-region: no-drag;
}
.main {
    margin-top: 40px;
}`,
	}
)

func printInfo(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Info]"}, v...)...)
}

func printError(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Error]"}, v...)...)
}

// 生成控件ID
func NewControlID() string { return "_" + strconv.FormatInt(r.Int63(), 36) }

func GetConByID(id string) interface{} {
	return nil
}

// TODO: 将窗口也变为控件
// TODO: 移植自定义窗口控件
// TODO: 分离nwui框架和控件

// nwui窗口
type Window struct {
	Title     string
	Width     int
	Height    int
	MaxWidth  int
	MaxHeight int
	MinWidth  int
	MinHeight int
	Controls  []interface{}
	OnExit    func()
	theme     Theme
	exit      chan bool
}

// 使用主题（CSS+JavaScript）
func (w *Window) UseTheme(t Theme) {
	w.theme = t
}

// 显示窗口
// 必须在全部控件设置完毕后才能调用
func (w *Window) Show() {
	// 查找可用端口
	var port string
	for i := 7072; i <= 65536; i++ {
		p := strconv.Itoa(i)
		ln, err := net.Listen("tcp", "localhost:"+p)
		if err != nil {
			continue
		} else {
			port = p
			ln.Close()
			break
		}
	}
	if port == "" {
		printError("no port can use")
		os.Exit(1)
	}

	w.exit = make(chan bool)
	if w.theme.CSS == "" {
		w.theme.CSS = defaultTheme.CSS
	}
	if w.theme.JavaScript == "" {
		w.theme.JavaScript = defaultTheme.JavaScript
	}

	var (
		html      string
		js        string = w.theme.JavaScript
		allEvents        = make(map[string]func(v string))
		upgrader         = websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		sender = make(chan EventMsg)
	)
	// 初始化控件
	for _, v := range w.Controls {
		// 返回html string, javascript string, events map[string]func(v string)
		v := reflect.ValueOf(v).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

		html += v[0].String()
		js += v[1].String()
		for _, vv := range v[2].MapKeys() {
			allEvents[vv.String()] = v[2].MapIndex(vv).Interface().(func(v string))
		}
	}
	go func() {
		http.HandleFunc("/ws", func(rw http.ResponseWriter, r *http.Request) {
			conn, err := upgrader.Upgrade(rw, r, nil)
			if err != nil && err != io.EOF {
				printError(err)
				return
			}

			// 用于接收事件
			go func() {
				for {
					messageType, p, err := conn.ReadMessage()
					if err != nil {
						printError(err)
						return
					}

					if messageType == websocket.TextMessage {
						var msg EventMsg
						err = json.Unmarshal(p, &msg)
						if err != nil {
							printError(err)
							return
						}
						// 判断事件是否为内置事件
						// 进行相应处理
						switch msg.Event {
						case "exit":
							if w.OnExit != nil {
								w.OnExit()
							}
							w.exit <- true
							return
						}
						// 执行事件所绑定的函数
						f, ok := allEvents[msg.Event]
						if ok {
							f(msg.Value)
						} else {
							printError("unfind event:", msg.Event)
						}
					}
				}
			}()

			for {
				// 发送消息给前端
				m := <-sender
				msg, err := json.Marshal(&m)
				if err != nil {
					printError(err)
					return
				}
				err = conn.WriteMessage(websocket.TextMessage, msg)
				if err != nil {
					printError(err)
				}
			}
		})
		http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(rw, temp, w.Title, w.theme.CSS, html, "localhost:"+port, js)
			r.Body.Close()
		})
		printInfo("running on localhost:" + port)
		err := http.ListenAndServe("localhost:"+port, nil)
		if err != nil {
			panic(err)
		}
	}()
	<-w.exit
}

/*
// 创建一个新的自定义窗口边框
// 一个 Window 中只能有一个 Frame
func NewFrame(title string, con ...Control) Frame {
	return &frame{
		title:    title,
		controls: con,
		events:   make(map[string]func(v string)),
	}
}

// 窗口边框
type Frame interface {
	Control
}

type frame struct {
	title    string
	js       string
	controls []Control
	events   map[string]func(v string)
	send     func(f, v string)
}

func (f *frame) getEvents() map[string]func(v string) {
	return f.events
}

func (f *frame) setSendFunc(fc func(f, v string)) {
	f.send = fc
}

func (f *frame) genHTML() string {
	// 转换内部控件
	var html string
	for _, v := range f.controls {
		v.setSendFunc(f.send)
		html += v.genHTML()
		f.js += v.genJavaScript()
		for e, fc := range v.getEvents() {
			f.events[e] = fc
		}
	}
	return `

` + f.title + `
<button id="close">x</button>

` + html + `

`
}

func (f *frame) genJavaScript() string {
	return f.js + `
(function() {
	document.getElementById("close").onclick = function(){
		open(' ', '_self').close();
	}
})();`
}
*/

type Button struct {
	Control
	ID      string
	Text    string
	OnClick func()
	sender  chan EventMsg
}

func (b *Button) Init(sender chan EventMsg) (string, string, map[string]func(v string)) {
	if b.ID == "" {
		b.ID = NewControlID()
	}
	events := make(map[string]func(v string))
	b.sender = sender
	js := `
function ` + b.ID + `SetText(text) {
	var button = document.getElementById('` + b.ID + `');
	button.textContent = text;
}`
	html := "<button id=\"" + b.ID + "\"/>" + b.Text + "</button>"
	if b.OnClick != nil {
		// 如果用户使用了OnClick事件
		// 那么添加事件和js
		events[b.ID+"OnClick"] = func(v string) {
			b.OnClick()
		}
		js += `
(function() {
	var button = document.getElementById('` + b.ID + `');
	button.onclick = function(){
		socket.send(JSON.stringify({"event": "` + b.ID + `OnClick","value": ""}));
	};
})();`
	}
	return html, js, events
}

// 设置按钮文字
func (b *Button) SetText(text string) {
	// b.send会去调用javascript里名为
	// b.id+"SetText"的函数
	// 并传入参数text
	if b.sender != nil {
		b.sender <- EventMsg{b.ID + "SetText", text}
	}
	b.Text = text
}

// nwui主题
type Theme struct {
	CSS        string
	JavaScript string
}

// nwui控件
type Control struct {
	getEvents     func() map[string]func(v string)
	setSendFunc   func(func(f, v string))
	genHTML       func() string
	genJavaScript func() string
}

type EventMsg struct {
	Event string `json:"event"`
	Value string `json:"value"`
}
<commit_msg>more todo<commit_after>/*
 Copyright 2015 Bluek404

 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 nwui

import (
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/gorilla/websocket"
)

var (
	// 用于生成随机数
	r    = rand.New(rand.NewSource(time.Now().UnixNano()))
	cons = make(map[string]interface{})
	temp = `<!DOCTYPE html>

<meta charset="utf-8">
<title>%v</title>

%v

`
	defaultTheme = Theme{
		CSS: `button {
    border: 4px solid #304ffe;
    color: white;
    background: #304ffe;
    padding: 6px 12px;
}
button:hover {
    background: white;
    color: #304ffe;
}
button:active {
    color: #fff;
    background: #304ffe;
    box-shadow: 1px 2px 7px rgba(0, 0, 0, 0.3) inset;
}
.frame {
    position: absolute;
    left: 0px;
    top: 0px;
    width: 100%;
    height: 32px;
    background-color: #424242;
    -webkit-app-region: drag;
}
.frame .title {
    color: white;
    position: absolute;
    left: 12px;
    width: 80%;
    margin-top: 6px;
    margin-bottom: 6px;
    font-size: 11pt;
}
.frame button#close {
    position: absolute;
    left: auto;
    right: 12px;
    width: auto;
    font-size: 11pt;
    -webkit-app-region: no-drag;
}
.main {
    margin-top: 40px;
}`,
	}
)

func printInfo(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Info]"}, v...)...)
}

func printError(v ...interface{}) {
	log.Println(append([]interface{}{"[nwui][Error]"}, v...)...)
}

// 生成控件ID
func NewControlID() string { return "_" + strconv.FormatInt(r.Int63(), 36) }

func GetConByID(id string) interface{} {
	return nil
}

// TODO: 将窗口也变为控件
// TODO: 移植自定义窗口控件
// TODO: 分离nwui框架和控件
// TODO: 完善根据ID获取控件
// TODO: 更多控件+主题
// TODO: 自动启动nwjs

// nwui窗口
type Window struct {
	Title     string
	Width     int
	Height    int
	MaxWidth  int
	MaxHeight int
	MinWidth  int
	MinHeight int
	Controls  []interface{}
	OnExit    func()
	theme     Theme
	exit      chan bool
}

// 使用主题（CSS+JavaScript）
func (w *Window) UseTheme(t Theme) {
	w.theme = t
}

// 显示窗口
// 必须在全部控件设置完毕后才能调用
func (w *Window) Show() {
	// 查找可用端口
	var port string
	for i := 7072; i <= 65536; i++ {
		p := strconv.Itoa(i)
		ln, err := net.Listen("tcp", "localhost:"+p)
		if err != nil {
			continue
		} else {
			port = p
			ln.Close()
			break
		}
	}
	if port == "" {
		printError("no port can use")
		os.Exit(1)
	}

	w.exit = make(chan bool)
	if w.theme.CSS == "" {
		w.theme.CSS = defaultTheme.CSS
	}
	if w.theme.JavaScript == "" {
		w.theme.JavaScript = defaultTheme.JavaScript
	}

	var (
		html      string
		js        string = w.theme.JavaScript
		allEvents        = make(map[string]func(v string))
		upgrader         = websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
		}
		sender = make(chan EventMsg)
	)
	// 初始化控件
	for _, v := range w.Controls {
		// 返回html string, javascript string, events map[string]func(v string)
		v := reflect.ValueOf(v).MethodByName("Init").Call([]reflect.Value{reflect.ValueOf(sender)})

		html += v[0].String()
		js += v[1].String()
		for _, vv := range v[2].MapKeys() {
			allEvents[vv.String()] = v[2].MapIndex(vv).Interface().(func(v string))
		}
	}
	go func() {
		http.HandleFunc("/ws", func(rw http.ResponseWriter, r *http.Request) {
			conn, err := upgrader.Upgrade(rw, r, nil)
			if err != nil && err != io.EOF {
				printError(err)
				return
			}

			// 用于接收事件
			go func() {
				for {
					messageType, p, err := conn.ReadMessage()
					if err != nil {
						printError(err)
						return
					}

					if messageType == websocket.TextMessage {
						var msg EventMsg
						err = json.Unmarshal(p, &msg)
						if err != nil {
							printError(err)
							return
						}
						// 判断事件是否为内置事件
						// 进行相应处理
						switch msg.Event {
						case "exit":
							if w.OnExit != nil {
								w.OnExit()
							}
							w.exit <- true
							return
						}
						// 执行事件所绑定的函数
						f, ok := allEvents[msg.Event]
						if ok {
							f(msg.Value)
						} else {
							printError("unfind event:", msg.Event)
						}
					}
				}
			}()

			for {
				// 发送消息给前端
				m := <-sender
				msg, err := json.Marshal(&m)
				if err != nil {
					printError(err)
					return
				}
				err = conn.WriteMessage(websocket.TextMessage, msg)
				if err != nil {
					printError(err)
				}
			}
		})
		http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(rw, temp, w.Title, w.theme.CSS, html, "localhost:"+port, js)
			r.Body.Close()
		})
		printInfo("running on localhost:" + port)
		err := http.ListenAndServe("localhost:"+port, nil)
		if err != nil {
			panic(err)
		}
	}()
	<-w.exit
}

/*
// 创建一个新的自定义窗口边框
// 一个 Window 中只能有一个 Frame
func NewFrame(title string, con ...Control) Frame {
	return &frame{
		title:    title,
		controls: con,
		events:   make(map[string]func(v string)),
	}
}

// 窗口边框
type Frame interface {
	Control
}

type frame struct {
	title    string
	js       string
	controls []Control
	events   map[string]func(v string)
	send     func(f, v string)
}

func (f *frame) getEvents() map[string]func(v string) {
	return f.events
}

func (f *frame) setSendFunc(fc func(f, v string)) {
	f.send = fc
}

func (f *frame) genHTML() string {
	// 转换内部控件
	var html string
	for _, v := range f.controls {
		v.setSendFunc(f.send)
		html += v.genHTML()
		f.js += v.genJavaScript()
		for e, fc := range v.getEvents() {
			f.events[e] = fc
		}
	}
	return `

` + f.title + `
<button id="close">x</button>

` + html + `

`
}

func (f *frame) genJavaScript() string {
	return f.js + `
(function() {
	document.getElementById("close").onclick = function(){
		open(' ', '_self').close();
	}
})();`
}
*/

type Button struct {
	Control
	ID      string
	Text    string
	OnClick func()
	sender  chan EventMsg
}

func (b *Button) Init(sender chan EventMsg) (string, string, map[string]func(v string)) {
	if b.ID == "" {
		b.ID = NewControlID()
	}
	events := make(map[string]func(v string))
	b.sender = sender
	js := `
function ` + b.ID + `SetText(text) {
	var button = document.getElementById('` + b.ID + `');
	button.textContent = text;
}`
	html := "<button id=\"" + b.ID + "\"/>" + b.Text + "</button>"
	if b.OnClick != nil {
		// 如果用户使用了OnClick事件
		// 那么添加事件和js
		events[b.ID+"OnClick"] = func(v string) {
			b.OnClick()
		}
		js += `
(function() {
	var button = document.getElementById('` + b.ID + `');
	button.onclick = function(){
		socket.send(JSON.stringify({"event": "` + b.ID + `OnClick","value": ""}));
	};
})();`
	}
	return html, js, events
}

// 设置按钮文字
func (b *Button) SetText(text string) {
	// b.send会去调用javascript里名为
	// b.id+"SetText"的函数
	// 并传入参数text
	if b.sender != nil {
		b.sender <- EventMsg{b.ID + "SetText", text}
	}
	b.Text = text
}

// nwui主题
type Theme struct {
	CSS        string
	JavaScript string
}

// nwui控件
type Control struct {
	getEvents     func() map[string]func(v string)
	setSendFunc   func(func(f, v string))
	genHTML       func() string
	genJavaScript func() string
}

type EventMsg struct {
	Event string `json:"event"`
	Value string `json:"value"`
}
<|endoftext|>