<commit_before>package chyle

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/antham/envh"
)

func TestBuildChangelog(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "json")

	f, err := ioutil.TempFile(p+"/testing-repository", "test")

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

	defer func() {
		if err = f.Close(); err != nil {
			log.Fatal(err)
		}
	}()

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	oldStdout := os.Stdout
	os.Stdout = f

	err = BuildChangelog(&config)

	os.Stdout = oldStdout

	assert.NoError(t, err)

	b, err := ioutil.ReadFile(f.Name())

	assert.NoError(t, err)

	type Data struct {
		ID             string `json:"id"`
		AuthorDate     string `json:"authorDate"`
		AuthorEmail    string `json:"authorEmail"`
		AuthorName     string `json:"authorName"`
		Type           string `json:"type"`
		CommitterEmail string `json:"committerEmail"`
		CommitterName  string `json:"committerName"`
		Message        string `json:"message"`
		Subject        string `json:"subject"`
	}

	results := struct {
		Datas     []Data            `json:"datas"`
		Metadatas map[string]string `json:"metadatas"`
	}{}

	j := json.NewDecoder(bytes.NewBuffer(b))
	err = j.Decode(&results)

	assert.NoError(t, err)
	assert.Len(t, results.Datas, 2)
	assert.Len(t, results.Metadatas, 0)

	expected := []map[string]string{
		{
			"authorEmail":    "whatever@example.com",
			"authorName":     "whatever",
			"committerEmail": "whatever@example.com",
			"committerName":  "whatever",
			"type":           "regular",
			"message":        "feat(file8) : new file 8\n\ncreate a new file 8\n",
			"subject":        "feat(file8) : new file 8",
		},
		{
			"authorEmail":    "whatever@example.com",
			"authorName":     "whatever",
			"committerEmail": "whatever@example.com",
			"committerName":  "whatever",
			"type":           "regular",
			"message":        "feat(file7) : new file 7\n\ncreate a new file 7\n",
			"subject":        "feat(file7) : new file 7",
		},
	}

	for i, r := range results.Datas {
		assert.Equal(t, expected[i]["authorEmail"], r.AuthorEmail)
		assert.Equal(t, expected[i]["authorName"], r.AuthorName)
		assert.Equal(t, expected[i]["type"], r.Type)
		assert.Equal(t, expected[i]["committerEmail"], r.CommitterEmail)
		assert.Equal(t, expected[i]["committerName"], r.CommitterName)
		assert.Equal(t, expected[i]["message"], r.Message)
		assert.Equal(t, expected[i]["subject"], r.Subject)
	}
}

func TestBuildChangelogWithAnErrorFromGitPackage(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/whatever")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "json")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	err = BuildChangelog(&config)

	assert.Error(t, err)
	assert.Regexp(t, `check ".*?" is an existing git repository path`, err.Error())
}

func TestBuildChangelogWithAnErrorFromConfigPackage(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "whatever")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	err = BuildChangelog(&config)

	assert.Error(t, err)
	assert.EqualError(t, err, `"CHYLE_SENDERS_STDOUT_FORMAT" "whatever" doesn't exist`)
}

func TestBuildChangelogWithDebuggingEnabled(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	EnableDebugging = true

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	assert.NoError(t, err)

	tmpLogger := logger

	b := []byte{}
	buffer := bytes.NewBuffer(b)

	logger = log.New(buffer, "CHYLE - ", log.Ldate|log.Ltime)

	err = BuildChangelog(&config)
	assert.NoError(t, err)

	logger = tmpLogger

	EnableDebugging = false

	assert.Regexp(t, `CHYLE - \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} {\n\s+"GIT": {\n\s+"REPOSITORY": {\n`, string(buffer.String()))
}
<commit_msg>chyle : remove unecessary conversion<commit_after>package chyle

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/antham/envh"
)

func TestBuildChangelog(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "json")

	f, err := ioutil.TempFile(p+"/testing-repository", "test")

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

	defer func() {
		if err = f.Close(); err != nil {
			log.Fatal(err)
		}
	}()

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	oldStdout := os.Stdout
	os.Stdout = f

	err = BuildChangelog(&config)

	os.Stdout = oldStdout

	assert.NoError(t, err)

	b, err := ioutil.ReadFile(f.Name())

	assert.NoError(t, err)

	type Data struct {
		ID             string `json:"id"`
		AuthorDate     string `json:"authorDate"`
		AuthorEmail    string `json:"authorEmail"`
		AuthorName     string `json:"authorName"`
		Type           string `json:"type"`
		CommitterEmail string `json:"committerEmail"`
		CommitterName  string `json:"committerName"`
		Message        string `json:"message"`
		Subject        string `json:"subject"`
	}

	results := struct {
		Datas     []Data            `json:"datas"`
		Metadatas map[string]string `json:"metadatas"`
	}{}

	j := json.NewDecoder(bytes.NewBuffer(b))
	err = j.Decode(&results)

	assert.NoError(t, err)
	assert.Len(t, results.Datas, 2)
	assert.Len(t, results.Metadatas, 0)

	expected := []map[string]string{
		{
			"authorEmail":    "whatever@example.com",
			"authorName":     "whatever",
			"committerEmail": "whatever@example.com",
			"committerName":  "whatever",
			"type":           "regular",
			"message":        "feat(file8) : new file 8\n\ncreate a new file 8\n",
			"subject":        "feat(file8) : new file 8",
		},
		{
			"authorEmail":    "whatever@example.com",
			"authorName":     "whatever",
			"committerEmail": "whatever@example.com",
			"committerName":  "whatever",
			"type":           "regular",
			"message":        "feat(file7) : new file 7\n\ncreate a new file 7\n",
			"subject":        "feat(file7) : new file 7",
		},
	}

	for i, r := range results.Datas {
		assert.Equal(t, expected[i]["authorEmail"], r.AuthorEmail)
		assert.Equal(t, expected[i]["authorName"], r.AuthorName)
		assert.Equal(t, expected[i]["type"], r.Type)
		assert.Equal(t, expected[i]["committerEmail"], r.CommitterEmail)
		assert.Equal(t, expected[i]["committerName"], r.CommitterName)
		assert.Equal(t, expected[i]["message"], r.Message)
		assert.Equal(t, expected[i]["subject"], r.Subject)
	}
}

func TestBuildChangelogWithAnErrorFromGitPackage(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/whatever")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "json")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	err = BuildChangelog(&config)

	assert.Error(t, err)
	assert.Regexp(t, `check ".*?" is an existing git repository path`, err.Error())
}

func TestBuildChangelogWithAnErrorFromConfigPackage(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_SENDERS_STDOUT_FORMAT", "whatever")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	err = BuildChangelog(&config)

	assert.Error(t, err)
	assert.EqualError(t, err, `"CHYLE_SENDERS_STDOUT_FORMAT" "whatever" doesn't exist`)
}

func TestBuildChangelogWithDebuggingEnabled(t *testing.T) {
	restoreEnvs()
	p, err := os.Getwd()

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

	EnableDebugging = true

	setenv("CHYLE_GIT_REPOSITORY_PATH", p+"/testing-repository")
	setenv("CHYLE_GIT_REFERENCE_FROM", "test2")
	setenv("CHYLE_GIT_REFERENCE_TO", "head")
	setenv("CHYLE_MATCHERS_TYPE", "regular")
	setenv("CHYLE_EXTRACTORS_MESSAGE_ORIGKEY", "message")
	setenv("CHYLE_EXTRACTORS_MESSAGE_DESTKEY", "subject")
	setenv("CHYLE_EXTRACTORS_MESSAGE_REG", "(.{1,50})")

	config, err := envh.NewEnvTree("CHYLE", "_")

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

	assert.NoError(t, err)

	tmpLogger := logger

	b := []byte{}
	buffer := bytes.NewBuffer(b)

	logger = log.New(buffer, "CHYLE - ", log.Ldate|log.Ltime)

	err = BuildChangelog(&config)
	assert.NoError(t, err)

	logger = tmpLogger

	EnableDebugging = false

	assert.Regexp(t, `CHYLE - \d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} {\n\s+"GIT": {\n\s+"REPOSITORY": {\n`, buffer.String())
}
<|endoftext|>


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

package source

import (
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"

	"golang.org/x/tools/internal/lsp/protocol"
	"golang.org/x/tools/internal/telemetry/event"
	errors "golang.org/x/xerrors"
)

func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]protocol.Location, error) {
	ctx, done := event.StartSpan(ctx, "source.Implementation")
	defer done()

	impls, err := implementations(ctx, s, f, pp)
	if err != nil {
		return nil, err
	}

	var locations []protocol.Location
	for _, impl := range impls {
		if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
			continue
		}
		rng, err := objToMappedRange(s.View(), impl.pkg, impl.obj)
		if err != nil {
			return nil, err
		}
		pr, err := rng.Range()
		if err != nil {
			return nil, err
		}
		locations = append(locations, protocol.Location{
			URI:   protocol.URIFromSpanURI(rng.URI()),
			Range: pr,
		})
	}
	return locations, nil
}

var ErrNotAType = errors.New("not a type name or method")

// implementations returns the concrete implementations of the specified
// interface, or the interfaces implemented by the specified concrete type.
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
	var (
		impls []qualifiedObject
		seen  = make(map[token.Position]bool)
		fset  = s.View().Session().Cache().FileSet()
	)

	qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
	if err != nil {
		return nil, err
	}

	for _, qo := range qos {
		var (
			queryType   types.Type
			queryMethod *types.Func
		)

		switch obj := qo.obj.(type) {
		case *types.Func:
			queryMethod = obj
			if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
				queryType = ensurePointer(recv.Type())
			}
		case *types.TypeName:
			queryType = ensurePointer(obj.Type())
		}

		if queryType == nil {
			return nil, ErrNotAType
		}

		if types.NewMethodSet(queryType).Len() == 0 {
			return nil, nil
		}

		// Find all named types, even local types (which can have methods
		// due to promotion).
		var (
			allNamed []*types.Named
			pkgs     = make(map[*types.Package]Package)
		)
		knownPkgs, err := s.KnownPackages(ctx)
		if err != nil {
			return nil, err
		}
		for _, ph := range knownPkgs {
			pkg, err := ph.Check(ctx)
			if err != nil {
				return nil, err
			}
			pkgs[pkg.GetTypes()] = pkg
			info := pkg.GetTypesInfo()
			for _, obj := range info.Defs {
				obj, ok := obj.(*types.TypeName)
				// We ignore aliases 'type M = N' to avoid duplicate reporting
				// of the Named type N.
				if !ok || obj.IsAlias() {
					continue
				}
				if named, ok := obj.Type().(*types.Named); ok {
					allNamed = append(allNamed, named)
				}
			}
		}

		// Find all the named types that match our query.
		for _, named := range allNamed {
			var (
				candObj  types.Object = named.Obj()
				candType              = ensurePointer(named)
			)

			if !concreteImplementsIntf(candType, queryType) {
				continue
			}

			ms := types.NewMethodSet(candType)
			if ms.Len() == 0 {
				// Skip empty interfaces.
				continue
			}

			// If client queried a method, look up corresponding candType method.
			if queryMethod != nil {
				sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
				if sel == nil {
					continue
				}
				candObj = sel.Obj()
			}

			pos := fset.Position(candObj.Pos())
			if candObj == queryMethod || seen[pos] {
				continue
			}

			seen[pos] = true

			impls = append(impls, qualifiedObject{
				obj: candObj,
				pkg: pkgs[candObj.Pkg()],
			})
		}
	}

	return impls, nil
}

// concreteImplementsIntf returns true if a is an interface type implemented by
// concrete type b, or vice versa.
func concreteImplementsIntf(a, b types.Type) bool {
	aIsIntf, bIsIntf := isInterface(a), isInterface(b)

	// Make sure exactly one is an interface type.
	if aIsIntf == bIsIntf {
		return false
	}

	// Rearrange if needed so "a" is the concrete type.
	if aIsIntf {
		a, b = b, a
	}

	return types.AssignableTo(a, b)
}

// ensurePointer wraps T in a *types.Pointer if T is a named, non-interface
// type. This is useful to make sure you consider a named type's full method
// set.
func ensurePointer(T types.Type) types.Type {
	if _, ok := T.(*types.Named); ok && !isInterface(T) {
		return types.NewPointer(T)
	}

	return T
}

type qualifiedObject struct {
	obj types.Object

	// pkg is the Package that contains obj's definition.
	pkg Package

	// node is the *ast.Ident or *ast.ImportSpec we followed to find obj, if any.
	node ast.Node

	// sourcePkg is the Package that contains node, if any.
	sourcePkg Package
}

// qualifiedObjsAtProtocolPos returns info for all the type.Objects
// referenced at the given position. An object will be returned for
// every package that the file belongs to.
func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
	phs, err := s.PackageHandles(ctx, f)
	if err != nil {
		return nil, err
	}

	var qualifiedObjs []qualifiedObject

	// Check all the packages that the file belongs to.
	for _, ph := range phs {
		pkg, err := ph.Check(ctx)
		if err != nil {
			return nil, err
		}

		astFile, pos, err := getASTFile(pkg, f, pp)
		if err != nil {
			return nil, err
		}

		path := pathEnclosingObjNode(astFile, pos)
		if path == nil {
			return nil, ErrNoIdentFound
		}

		var objs []types.Object
		switch leaf := path[0].(type) {
		case *ast.Ident:
			// If leaf represents an implicit type switch object or the type
			// switch "assign" variable, expand to all of the type switch's
			// implicit objects.
			if implicits := typeSwitchImplicits(pkg, path); len(implicits) > 0 {
				objs = append(objs, implicits...)
			} else {
				obj := pkg.GetTypesInfo().ObjectOf(leaf)
				if obj == nil {
					return nil, fmt.Errorf("no object for %q", leaf.Name)
				}
				objs = append(objs, obj)
			}
		case *ast.ImportSpec:
			// Look up the implicit *types.PkgName.
			obj := pkg.GetTypesInfo().Implicits[leaf]
			if obj == nil {
				return nil, fmt.Errorf("no object for import %q", importPath(leaf))
			}
			objs = append(objs, obj)
		}

		pkgs := make(map[*types.Package]Package)
		pkgs[pkg.GetTypes()] = pkg
		for _, imp := range pkg.Imports() {
			pkgs[imp.GetTypes()] = imp
		}

		for _, obj := range objs {
			qualifiedObjs = append(qualifiedObjs, qualifiedObject{
				obj:       obj,
				pkg:       pkgs[obj.Pkg()],
				sourcePkg: pkg,
				node:      path[0],
			})
		}
	}

	return qualifiedObjs, nil
}

func getASTFile(pkg Package, f FileHandle, pos protocol.Position) (*ast.File, token.Pos, error) {
	pgh, err := pkg.File(f.Identity().URI)
	if err != nil {
		return nil, 0, err
	}

	file, _, m, _, err := pgh.Cached()
	if err != nil {
		return nil, 0, err
	}

	spn, err := m.PointSpan(pos)
	if err != nil {
		return nil, 0, err
	}

	rng, err := spn.Range(m.Converter)
	if err != nil {
		return nil, 0, err
	}

	return file, rng.Start, nil
}

// pathEnclosingObjNode returns the AST path to the object-defining
// node associated with pos. "Object-defining" means either an
// *ast.Ident mapped directly to a types.Object or an ast.Node mapped
// implicitly to a types.Object.
func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
	var (
		path  []ast.Node
		found bool
	)

	ast.Inspect(f, func(n ast.Node) bool {
		if found {
			return false
		}

		if n == nil {
			path = path[:len(path)-1]
			return false
		}

		path = append(path, n)

		switch n := n.(type) {
		case *ast.Ident:
			// Include the position directly after identifier. This handles
			// the common case where the cursor is right after the
			// identifier the user is currently typing. Previously we
			// handled this by calling astutil.PathEnclosingInterval twice,
			// once for "pos" and once for "pos-1".
			found = n.Pos() <= pos && pos <= n.End()
		case *ast.ImportSpec:
			if n.Path.Pos() <= pos && pos < n.Path.End() {
				found = true
				// If import spec has a name, add name to path even though
				// position isn't in the name.
				if n.Name != nil {
					path = append(path, n.Name)
				}
			}
		case *ast.StarExpr:
			// Follow star expressions to the inner identifier.
			if pos == n.Star {
				pos = n.X.Pos()
			}
		case *ast.SelectorExpr:
			// If pos is on the ".", move it into the selector.
			if pos == n.X.End() {
				pos = n.Sel.Pos()
			}
		}

		return !found
	})

	if len(path) == 0 {
		return nil
	}

	// Reverse path so leaf is first element.
	for i := 0; i < len(path)/2; i++ {
		path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i]
	}

	return path
}
<commit_msg>internal/lsp: avoid possible nil pointer in references/rename<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package source

import (
	"context"
	"fmt"
	"go/ast"
	"go/token"
	"go/types"

	"golang.org/x/tools/internal/lsp/protocol"
	"golang.org/x/tools/internal/telemetry/event"
	errors "golang.org/x/xerrors"
)

func Implementation(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]protocol.Location, error) {
	ctx, done := event.StartSpan(ctx, "source.Implementation")
	defer done()

	impls, err := implementations(ctx, s, f, pp)
	if err != nil {
		return nil, err
	}

	var locations []protocol.Location
	for _, impl := range impls {
		if impl.pkg == nil || len(impl.pkg.CompiledGoFiles()) == 0 {
			continue
		}
		rng, err := objToMappedRange(s.View(), impl.pkg, impl.obj)
		if err != nil {
			return nil, err
		}
		pr, err := rng.Range()
		if err != nil {
			return nil, err
		}
		locations = append(locations, protocol.Location{
			URI:   protocol.URIFromSpanURI(rng.URI()),
			Range: pr,
		})
	}
	return locations, nil
}

var ErrNotAType = errors.New("not a type name or method")

// implementations returns the concrete implementations of the specified
// interface, or the interfaces implemented by the specified concrete type.
func implementations(ctx context.Context, s Snapshot, f FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
	var (
		impls []qualifiedObject
		seen  = make(map[token.Position]bool)
		fset  = s.View().Session().Cache().FileSet()
	)

	qos, err := qualifiedObjsAtProtocolPos(ctx, s, f, pp)
	if err != nil {
		return nil, err
	}

	for _, qo := range qos {
		var (
			queryType   types.Type
			queryMethod *types.Func
		)

		switch obj := qo.obj.(type) {
		case *types.Func:
			queryMethod = obj
			if recv := obj.Type().(*types.Signature).Recv(); recv != nil {
				queryType = ensurePointer(recv.Type())
			}
		case *types.TypeName:
			queryType = ensurePointer(obj.Type())
		}

		if queryType == nil {
			return nil, ErrNotAType
		}

		if types.NewMethodSet(queryType).Len() == 0 {
			return nil, nil
		}

		// Find all named types, even local types (which can have methods
		// due to promotion).
		var (
			allNamed []*types.Named
			pkgs     = make(map[*types.Package]Package)
		)
		knownPkgs, err := s.KnownPackages(ctx)
		if err != nil {
			return nil, err
		}
		for _, ph := range knownPkgs {
			pkg, err := ph.Check(ctx)
			if err != nil {
				return nil, err
			}
			pkgs[pkg.GetTypes()] = pkg
			info := pkg.GetTypesInfo()
			for _, obj := range info.Defs {
				obj, ok := obj.(*types.TypeName)
				// We ignore aliases 'type M = N' to avoid duplicate reporting
				// of the Named type N.
				if !ok || obj.IsAlias() {
					continue
				}
				if named, ok := obj.Type().(*types.Named); ok {
					allNamed = append(allNamed, named)
				}
			}
		}

		// Find all the named types that match our query.
		for _, named := range allNamed {
			var (
				candObj  types.Object = named.Obj()
				candType              = ensurePointer(named)
			)

			if !concreteImplementsIntf(candType, queryType) {
				continue
			}

			ms := types.NewMethodSet(candType)
			if ms.Len() == 0 {
				// Skip empty interfaces.
				continue
			}

			// If client queried a method, look up corresponding candType method.
			if queryMethod != nil {
				sel := ms.Lookup(queryMethod.Pkg(), queryMethod.Name())
				if sel == nil {
					continue
				}
				candObj = sel.Obj()
			}

			pos := fset.Position(candObj.Pos())
			if candObj == queryMethod || seen[pos] {
				continue
			}

			seen[pos] = true

			impls = append(impls, qualifiedObject{
				obj: candObj,
				pkg: pkgs[candObj.Pkg()],
			})
		}
	}

	return impls, nil
}

// concreteImplementsIntf returns true if a is an interface type implemented by
// concrete type b, or vice versa.
func concreteImplementsIntf(a, b types.Type) bool {
	aIsIntf, bIsIntf := isInterface(a), isInterface(b)

	// Make sure exactly one is an interface type.
	if aIsIntf == bIsIntf {
		return false
	}

	// Rearrange if needed so "a" is the concrete type.
	if aIsIntf {
		a, b = b, a
	}

	return types.AssignableTo(a, b)
}

// ensurePointer wraps T in a *types.Pointer if T is a named, non-interface
// type. This is useful to make sure you consider a named type's full method
// set.
func ensurePointer(T types.Type) types.Type {
	if _, ok := T.(*types.Named); ok && !isInterface(T) {
		return types.NewPointer(T)
	}

	return T
}

type qualifiedObject struct {
	obj types.Object

	// pkg is the Package that contains obj's definition.
	pkg Package

	// node is the *ast.Ident or *ast.ImportSpec we followed to find obj, if any.
	node ast.Node

	// sourcePkg is the Package that contains node, if any.
	sourcePkg Package
}

// qualifiedObjsAtProtocolPos returns info for all the type.Objects
// referenced at the given position. An object will be returned for
// every package that the file belongs to.
func qualifiedObjsAtProtocolPos(ctx context.Context, s Snapshot, fh FileHandle, pp protocol.Position) ([]qualifiedObject, error) {
	phs, err := s.PackageHandles(ctx, fh)
	if err != nil {
		return nil, err
	}
	// Check all the packages that the file belongs to.
	var qualifiedObjs []qualifiedObject
	for _, ph := range phs {
		pkg, err := ph.Check(ctx)
		if err != nil {
			return nil, err
		}
		astFile, pos, err := getASTFile(pkg, fh, pp)
		if err != nil {
			return nil, err
		}
		path := pathEnclosingObjNode(astFile, pos)
		if path == nil {
			return nil, ErrNoIdentFound
		}
		var objs []types.Object
		switch leaf := path[0].(type) {
		case *ast.Ident:
			// If leaf represents an implicit type switch object or the type
			// switch "assign" variable, expand to all of the type switch's
			// implicit objects.
			if implicits := typeSwitchImplicits(pkg, path); len(implicits) > 0 {
				objs = append(objs, implicits...)
			} else {
				obj := pkg.GetTypesInfo().ObjectOf(leaf)
				if obj == nil {
					return nil, fmt.Errorf("no object for %q", leaf.Name)
				}
				objs = append(objs, obj)
			}
		case *ast.ImportSpec:
			// Look up the implicit *types.PkgName.
			obj := pkg.GetTypesInfo().Implicits[leaf]
			if obj == nil {
				return nil, fmt.Errorf("no object for import %q", importPath(leaf))
			}
			objs = append(objs, obj)
		}
		pkgs := make(map[*types.Package]Package)
		pkgs[pkg.GetTypes()] = pkg
		for _, imp := range pkg.Imports() {
			pkgs[imp.GetTypes()] = imp
		}
		for _, obj := range objs {
			qualifiedObjs = append(qualifiedObjs, qualifiedObject{
				obj:       obj,
				pkg:       pkgs[obj.Pkg()],
				sourcePkg: pkg,
				node:      path[0],
			})
		}
	}
	// Return an error if no objects were found since callers will assume that
	// the slice has at least 1 element.
	if len(qualifiedObjs) == 0 {
		return nil, errors.Errorf("no object found")
	}
	return qualifiedObjs, nil
}

func getASTFile(pkg Package, f FileHandle, pos protocol.Position) (*ast.File, token.Pos, error) {
	pgh, err := pkg.File(f.Identity().URI)
	if err != nil {
		return nil, 0, err
	}
	file, _, m, _, err := pgh.Cached()
	if err != nil {
		return nil, 0, err
	}
	spn, err := m.PointSpan(pos)
	if err != nil {
		return nil, 0, err
	}
	rng, err := spn.Range(m.Converter)
	if err != nil {
		return nil, 0, err
	}
	return file, rng.Start, nil
}

// pathEnclosingObjNode returns the AST path to the object-defining
// node associated with pos. "Object-defining" means either an
// *ast.Ident mapped directly to a types.Object or an ast.Node mapped
// implicitly to a types.Object.
func pathEnclosingObjNode(f *ast.File, pos token.Pos) []ast.Node {
	var (
		path  []ast.Node
		found bool
	)

	ast.Inspect(f, func(n ast.Node) bool {
		if found {
			return false
		}

		if n == nil {
			path = path[:len(path)-1]
			return false
		}

		path = append(path, n)

		switch n := n.(type) {
		case *ast.Ident:
			// Include the position directly after identifier. This handles
			// the common case where the cursor is right after the
			// identifier the user is currently typing. Previously we
			// handled this by calling astutil.PathEnclosingInterval twice,
			// once for "pos" and once for "pos-1".
			found = n.Pos() <= pos && pos <= n.End()
		case *ast.ImportSpec:
			if n.Path.Pos() <= pos && pos < n.Path.End() {
				found = true
				// If import spec has a name, add name to path even though
				// position isn't in the name.
				if n.Name != nil {
					path = append(path, n.Name)
				}
			}
		case *ast.StarExpr:
			// Follow star expressions to the inner identifier.
			if pos == n.Star {
				pos = n.X.Pos()
			}
		case *ast.SelectorExpr:
			// If pos is on the ".", move it into the selector.
			if pos == n.X.End() {
				pos = n.Sel.Pos()
			}
		}

		return !found
	})

	if len(path) == 0 {
		return nil
	}

	// Reverse path so leaf is first element.
	for i := 0; i < len(path)/2; i++ {
		path[i], path[len(path)-1-i] = path[len(path)-1-i], path[i]
	}

	return path
}
<|endoftext|>


<commit_before>package do

import "github.com/digitalocean/godo"

// Volume is a wrapper for godo.Volume.
type Volume struct {
	*godo.Volume
}

// VolumesService is an interface for interacting with DigitalOcean's volume api.
type VolumesService interface {
	List() ([]Volume, error)
	CreateVolume(*godo.VolumeCreateRequest) (*Volume, error)
	DeleteVolume(string) error
	Get(string) (*Volume, error)
	CreateSnapshot(*godo.SnapshotCreateRequest) (*Snapshot, error)
}

type volumesService struct {
	client *godo.Client
}

var _ VolumesService = &volumesService{}

// NewVolumesService builds an NewVolumesService instance.
func NewVolumesService(godoClient *godo.Client) VolumesService {
	return &volumesService{
		client: godoClient,
	}

}

func (a *volumesService) List() ([]Volume, error) {
	f := func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error) {
		list, resp, err := a.client.Storage.ListVolumes(opt)
		if err != nil {
			return nil, nil, err

		}

		si := make([]interface{}, len(list))
		for i := range list {
			si[i] = list[i]
		}

		return si, resp, err

	}

	si, err := PaginateResp(f)
	if err != nil {
		return nil, err

	}

	list := make([]Volume, len(si))
	for i := range si {
		a := si[i].(godo.Volume)
		list[i] = Volume{Volume: &a}

	}

	return list, nil

}

func (a *volumesService) CreateVolume(r *godo.VolumeCreateRequest) (*Volume, error) {
	al, _, err := a.client.Storage.CreateVolume(r)
	if err != nil {
		return nil, err

	}
	return &Volume{Volume: al}, nil

}

func (a *volumesService) DeleteVolume(id string) error {

	_, err := a.client.Storage.DeleteVolume(id)
	if err != nil {
		return err

	}

	return nil

}

func (a *volumesService) Get(id string) (*Volume, error) {
	d, _, err := a.client.Storage.GetVolume(id)
	if err != nil {
		return nil, err

	}

	return &Volume{Volume: d}, nil

}

func (a *volumesService) CreateSnapshot(createRequest *godo.SnapshotCreateRequest) (*Snapshot, error) {

	s, _, err := a.client.Storage.CreateSnapshot(createRequest)
	if err != nil {
		return nil, err
	}

	return &Snapshot{Snapshot: s}, nil
}
<commit_msg>Fix volume list cmd to comply with changes from @ https://github.com/digitalocean/godo/pull/122<commit_after>package do

import "github.com/digitalocean/godo"

// Volume is a wrapper for godo.Volume.
type Volume struct {
	*godo.Volume
}

// VolumesService is an interface for interacting with DigitalOcean's volume api.
type VolumesService interface {
	List() ([]Volume, error)
	CreateVolume(*godo.VolumeCreateRequest) (*Volume, error)
	DeleteVolume(string) error
	Get(string) (*Volume, error)
	CreateSnapshot(*godo.SnapshotCreateRequest) (*Snapshot, error)
}

type volumesService struct {
	client *godo.Client
}

var _ VolumesService = &volumesService{}

// NewVolumesService builds an NewVolumesService instance.
func NewVolumesService(godoClient *godo.Client) VolumesService {
	return &volumesService{
		client: godoClient,
	}

}

func (a *volumesService) List() ([]Volume, error) {
	f := func(opt *godo.ListOptions) ([]interface{}, *godo.Response, error) {
		params := &godo.ListVolumeParams{ListOptions: opt}
		list, resp, err := a.client.Storage.ListVolumes(params)
		if err != nil {
			return nil, nil, err

		}

		si := make([]interface{}, len(list))
		for i := range list {
			si[i] = list[i]
		}

		return si, resp, err

	}

	si, err := PaginateResp(f)
	if err != nil {
		return nil, err

	}

	list := make([]Volume, len(si))
	for i := range si {
		a := si[i].(godo.Volume)
		list[i] = Volume{Volume: &a}

	}

	return list, nil

}

func (a *volumesService) CreateVolume(r *godo.VolumeCreateRequest) (*Volume, error) {
	al, _, err := a.client.Storage.CreateVolume(r)
	if err != nil {
		return nil, err

	}
	return &Volume{Volume: al}, nil

}

func (a *volumesService) DeleteVolume(id string) error {

	_, err := a.client.Storage.DeleteVolume(id)
	if err != nil {
		return err

	}

	return nil

}

func (a *volumesService) Get(id string) (*Volume, error) {
	d, _, err := a.client.Storage.GetVolume(id)
	if err != nil {
		return nil, err

	}

	return &Volume{Volume: d}, nil

}

func (a *volumesService) CreateSnapshot(createRequest *godo.SnapshotCreateRequest) (*Snapshot, error) {

	s, _, err := a.client.Storage.CreateSnapshot(createRequest)
	if err != nil {
		return nil, err
	}

	return &Snapshot{Snapshot: s}, nil
}
<|endoftext|>


<commit_before>// Copyright 2016-2019 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/googlecodelabs/tools/claat/fetch"
	"github.com/googlecodelabs/tools/claat/render"
	"github.com/googlecodelabs/tools/claat/types"
	"github.com/googlecodelabs/tools/claat/util"
)

// Options type to make the CmdExport signature succinct.
type CmdExportOptions struct {
	// AuthToken is the token to use for the Drive API.
	AuthToken string
	// Expenv is the codelab environment to export to.
	Expenv string
	// ExtraVars is extra template variables.
	ExtraVars map[string]string
	// GlobalGA is the global Google Analytics account to use.
	GlobalGA string
	// Output is the output directory, or "-" for stdout.
	Output string
	// PassMetadata are the extra metadata fields to pass along.
	PassMetadata map[string]bool
	// Prefix is a URL prefix to prepend when using HTML format.
	Prefix string
	// Srcs is the sources to export codelabs from.
	Srcs []string
	// Tmplout is the output format.
	Tmplout string
}

// CmdExport is the "claat export ..." subcommand.
// It returns a process exit code.
func CmdExport(opts CmdExportOptions) int {
	var exitCode int
	if len(opts.Srcs) == 0 {
		log.Fatalf("Need at least one source. Try '-h' for options.")
	}
	type result struct {
		src  string
		meta *types.Meta
		err  error
	}
	srcs := util.Unique(opts.Srcs)
	ch := make(chan *result, len(srcs))
	for _, src := range srcs {
		go func(src string) {
			meta, err := exportCodelab(src, opts)
			ch <- &result{src, meta, err}
		}(src)
	}
	for range srcs {
		res := <-ch
		if res.err != nil {
			exitCode = 1
			log.Printf(reportErr, res.src, res.err)
		} else if !isStdout(opts.Output) {
			log.Printf(reportOk, res.meta.ID)
		}
	}
	return exitCode
}

// exportCodelab fetches codelab src from either local disk or remote,
// parses and stores the results on disk, in a dir ancestored by output.
//
// Stored results include codelab content formatted in tmplout, its assets
// and metadata in JSON format.
//
// There's a special case where basedir has a value of "-", in which
// nothing is stored on disk and the only output, codelab formatted content,
// is printed to stdout.
func exportCodelab(src string, opts CmdExportOptions) (*types.Meta, error) {
	f, err := fetch.NewFetcher(opts.AuthToken, opts.PassMetadata, nil)
	if err != nil {
		return nil, err
	}
	clab, err := f.SlurpCodelab(src)
	if err != nil {
		return nil, err
	}

	// codelab export context
	lastmod := types.ContextTime(clab.Mod)
	clab.Meta.Source = src
	meta := &clab.Meta
	ctx := &types.Context{
		Env:     opts.Expenv,
		Format:  opts.Tmplout,
		Prefix:  opts.Prefix,
		MainGA:  opts.GlobalGA,
		Updated: &lastmod,
	}

	dir := opts.Output // output dir or stdout
	if !isStdout(dir) {
		dir = codelabDir(dir, meta)
		// download or copy codelab assets to disk, and rewrite image URLs
		mdir := filepath.Join(dir, util.ImgDirname)
		if _, err := f.SlurpImages(src, mdir, clab.Steps); err != nil {
			return nil, err
		}
	}
	// write codelab and its metadata to disk
	return meta, writeCodelab(dir, clab.Codelab, opts.ExtraVars, ctx)
}

// writeCodelab stores codelab main content in ctx.Format and its metadata
// in JSON format on disk.
// extraVars is extra variables to pass into the template context.
func writeCodelab(dir string, clab *types.Codelab, extraVars map[string]string, ctx *types.Context) error {
	// output to stdout does not include metadata
	if !isStdout(dir) {
		// make sure codelab dir exists
		if err := os.MkdirAll(dir, 0755); err != nil {
			return err
		}
		// codelab metadata
		cm := &types.ContextMeta{Context: *ctx, Meta: clab.Meta}
		f := filepath.Join(dir, metaFilename)
		if err := writeMeta(f, cm); err != nil {
			return err
		}
	}

	// main content file(s)
	data := &struct {
		render.Context
		Current *types.Step
		StepNum int
		Prev    bool
		Next    bool
	}{Context: render.Context{
		Env:      ctx.Env,
		Prefix:   ctx.Prefix,
		GlobalGA: ctx.MainGA,
		Updated:  time.Time(*ctx.Updated).Format(time.RFC3339),
		Meta:     &clab.Meta,
		Steps:    clab.Steps,
		Extra:    extraVars,
	}}
	if ctx.Format != "offline" {
		w := os.Stdout
		if !isStdout(dir) {
			ext := ctx.Format
			if ext != "md" {
				ext = "html"
			}
			f, err := os.Create(filepath.Join(dir, "index."+ext))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		return render.Execute(w, ctx.Format, data)
	}
	for i, step := range clab.Steps {
		data.Current = step
		data.StepNum = i + 1
		data.Prev = i > 0
		data.Next = i < len(clab.Steps)-1
		w := os.Stdout
		if !isStdout(dir) {
			name := "index.html"
			if i > 0 {
				name = fmt.Sprintf("step-%d.html", i+1)
			}
			f, err := os.Create(filepath.Join(dir, name))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		if err := render.Execute(w, ctx.Format, data); err != nil {
			return err
		}
	}
	return nil
}

// writeMeta writes codelab metadata to a local disk location
// specified by path.
func writeMeta(path string, cm *types.ContextMeta) error {
	if cm.Context.Format == "htmlElements" {
		cm.Context.Format = "html"
	}
	b, err := json.MarshalIndent(cm, "", "  ")
	if err != nil {
		return err
	}
	b = append(b, '\n')
	return ioutil.WriteFile(path, b, 0644)
}

// codelabDir returns codelab root directory.
// The base argument is codelab parent directory.
func codelabDir(base string, m *types.Meta) string {
	return filepath.Join(base, m.ID)
}
<commit_msg>Publicize exportCodelab and allow alternate http.Transport from callers.<commit_after>// Copyright 2016-2019 Google LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/googlecodelabs/tools/claat/fetch"
	"github.com/googlecodelabs/tools/claat/render"
	"github.com/googlecodelabs/tools/claat/types"
	"github.com/googlecodelabs/tools/claat/util"
)

// Options type to make the CmdExport signature succinct.
type CmdExportOptions struct {
	// AuthToken is the token to use for the Drive API.
	AuthToken string
	// Expenv is the codelab environment to export to.
	Expenv string
	// ExtraVars is extra template variables.
	ExtraVars map[string]string
	// GlobalGA is the global Google Analytics account to use.
	GlobalGA string
	// Output is the output directory, or "-" for stdout.
	Output string
	// PassMetadata are the extra metadata fields to pass along.
	PassMetadata map[string]bool
	// Prefix is a URL prefix to prepend when using HTML format.
	Prefix string
	// Srcs is the sources to export codelabs from.
	Srcs []string
	// Tmplout is the output format.
	Tmplout string
}

// CmdExport is the "claat export ..." subcommand.
// It returns a process exit code.
func CmdExport(opts CmdExportOptions) int {
	var exitCode int
	if len(opts.Srcs) == 0 {
		log.Fatalf("Need at least one source. Try '-h' for options.")
	}
	type result struct {
		src  string
		meta *types.Meta
		err  error
	}
	srcs := util.Unique(opts.Srcs)
	ch := make(chan *result, len(srcs))
	for _, src := range srcs {
		go func(src string) {
			meta, err := ExportCodelab(src, nil, opts)
			ch <- &result{src, meta, err}
		}(src)
	}
	for range srcs {
		res := <-ch
		if res.err != nil {
			exitCode = 1
			log.Printf(reportErr, res.src, res.err)
		} else if !isStdout(opts.Output) {
			log.Printf(reportOk, res.meta.ID)
		}
	}
	return exitCode
}

// ExportCodelab fetches codelab src from either local disk or remote,
// parses and stores the results on disk, in a dir ancestored by output.
//
// Stored results include codelab content formatted in tmplout, its assets
// and metadata in JSON format.
//
// There's a special case where basedir has a value of "-", in which
// nothing is stored on disk and the only output, codelab formatted content,
// is printed to stdout.
//
// An alternate http.Transport may be specified if desired. Leave null for default.
func ExportCodelab(src string, txp *http.Transport, opts CmdExportOptions) (*types.Meta, error) {
	f, err := fetch.NewFetcher(opts.AuthToken, opts.PassMetadata, txp)
	if err != nil {
		return nil, err
	}
	clab, err := f.SlurpCodelab(src)
	if err != nil {
		return nil, err
	}

	// codelab export context
	lastmod := types.ContextTime(clab.Mod)
	clab.Meta.Source = src
	meta := &clab.Meta
	ctx := &types.Context{
		Env:     opts.Expenv,
		Format:  opts.Tmplout,
		Prefix:  opts.Prefix,
		MainGA:  opts.GlobalGA,
		Updated: &lastmod,
	}

	dir := opts.Output // output dir or stdout
	if !isStdout(dir) {
		dir = codelabDir(dir, meta)
		// download or copy codelab assets to disk, and rewrite image URLs
		mdir := filepath.Join(dir, util.ImgDirname)
		if _, err := f.SlurpImages(src, mdir, clab.Steps); err != nil {
			return nil, err
		}
	}
	// write codelab and its metadata to disk
	return meta, writeCodelab(dir, clab.Codelab, opts.ExtraVars, ctx)
}

// writeCodelab stores codelab main content in ctx.Format and its metadata
// in JSON format on disk.
// extraVars is extra variables to pass into the template context.
func writeCodelab(dir string, clab *types.Codelab, extraVars map[string]string, ctx *types.Context) error {
	// output to stdout does not include metadata
	if !isStdout(dir) {
		// make sure codelab dir exists
		if err := os.MkdirAll(dir, 0755); err != nil {
			return err
		}
		// codelab metadata
		cm := &types.ContextMeta{Context: *ctx, Meta: clab.Meta}
		f := filepath.Join(dir, metaFilename)
		if err := writeMeta(f, cm); err != nil {
			return err
		}
	}

	// main content file(s)
	data := &struct {
		render.Context
		Current *types.Step
		StepNum int
		Prev    bool
		Next    bool
	}{Context: render.Context{
		Env:      ctx.Env,
		Prefix:   ctx.Prefix,
		GlobalGA: ctx.MainGA,
		Updated:  time.Time(*ctx.Updated).Format(time.RFC3339),
		Meta:     &clab.Meta,
		Steps:    clab.Steps,
		Extra:    extraVars,
	}}
	if ctx.Format != "offline" {
		w := os.Stdout
		if !isStdout(dir) {
			ext := ctx.Format
			if ext != "md" {
				ext = "html"
			}
			f, err := os.Create(filepath.Join(dir, "index."+ext))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		return render.Execute(w, ctx.Format, data)
	}
	for i, step := range clab.Steps {
		data.Current = step
		data.StepNum = i + 1
		data.Prev = i > 0
		data.Next = i < len(clab.Steps)-1
		w := os.Stdout
		if !isStdout(dir) {
			name := "index.html"
			if i > 0 {
				name = fmt.Sprintf("step-%d.html", i+1)
			}
			f, err := os.Create(filepath.Join(dir, name))
			if err != nil {
				return err
			}
			w = f
			defer f.Close()
		}
		if err := render.Execute(w, ctx.Format, data); err != nil {
			return err
		}
	}
	return nil
}

// writeMeta writes codelab metadata to a local disk location
// specified by path.
func writeMeta(path string, cm *types.ContextMeta) error {
	if cm.Context.Format == "htmlElements" {
		cm.Context.Format = "html"
	}
	b, err := json.MarshalIndent(cm, "", "  ")
	if err != nil {
		return err
	}
	b = append(b, '\n')
	return ioutil.WriteFile(path, b, 0644)
}

// codelabDir returns codelab root directory.
// The base argument is codelab parent directory.
func codelabDir(base string, m *types.Meta) string {
	return filepath.Join(base, m.ID)
}
<|endoftext|>


<commit_before>package cache

import (
	"net"
	"sync"
	"time"

	"github.com/chinaboard/coral/utils"
	log "github.com/sirupsen/logrus"

	"github.com/juju/errors"
)

type Cache struct {
	data sync.Map
	ttl  time.Duration
}
type kv struct {
	value bool
	ttl   time.Time
}

func NewCache(ttl time.Duration) *Cache {
	cache := &Cache{ttl: ttl}
	cache.init()
	return cache
}

func (c *Cache) init() {
	go func(ch <-chan time.Time) {
		for range ch {
			c.data.Range(func(key, value interface{}) bool {
				if time.Since(value.(kv).ttl) > c.ttl {
					c.data.Delete(key)
				}
				return true
			})
		}
	}(time.Tick(time.Second))
}

func (c *Cache) Set(key string, value bool) {
	c.data.Store(key, kv{value: value, ttl: time.Now()})
}

func (c *Cache) Exist(key string) (bool, error) {
	v, ok := c.data.Load(key)
	if ok {
		c.Set(key, v.(kv).value)
		return v.(kv).value, nil
	}
	return false, errors.New("not found")
}

func (c *Cache) ShouldDirect(key string) bool {
	d, notFound := c.Exist(key)
	if notFound != nil {
		host, _, _ := net.SplitHostPort(key)
		ips, err := net.LookupIP(host)
		if err != nil {
			log.Warningln(err, "force use Proxy")
			d = false
		} else {
			ip := ips[0].String()
			d = utils.ShouldDirect(ip)
		}
		c.Set(key, d)
	}
	return d
}
<commit_msg>update err info<commit_after>package cache

import (
	"net"
	"sync"
	"time"

	"github.com/chinaboard/coral/utils"
	log "github.com/sirupsen/logrus"

	"github.com/juju/errors"
)

type Cache struct {
	data sync.Map
	ttl  time.Duration
}
type kv struct {
	value bool
	ttl   time.Time
}

func NewCache(ttl time.Duration) *Cache {
	cache := &Cache{ttl: ttl}
	cache.init()
	return cache
}

func (c *Cache) init() {
	go func(ch <-chan time.Time) {
		for range ch {
			c.data.Range(func(key, value interface{}) bool {
				if time.Since(value.(kv).ttl) > c.ttl {
					c.data.Delete(key)
				}
				return true
			})
		}
	}(time.Tick(time.Second))
}

func (c *Cache) Set(key string, value bool) {
	c.data.Store(key, kv{value: value, ttl: time.Now()})
}

func (c *Cache) Exist(key string) (bool, error) {
	v, ok := c.data.Load(key)
	if ok {
		c.Set(key, v.(kv).value)
		return v.(kv).value, nil
	}
	return false, errors.New("not found")
}

func (c *Cache) ShouldDirect(key string) bool {
	d, notFound := c.Exist(key)
	if notFound != nil {
		host, _, _ := net.SplitHostPort(key)
		ips, err := net.LookupIP(host)
		if err != nil {
			log.Warningln(err, host, "force use Proxy")
			d = false
		} else {
			ip := ips[0].String()
			d = utils.ShouldDirect(ip)
		}
		c.Set(key, d)
	}
	return d
}
<|endoftext|>


<commit_before>// Copyright 2017 Corey Scott http://www.sage42.org/
//
// 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 cache

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

// Client defines a cache instance.
//
// This can represent the cache for the entire system or for a particular use-case/type.
//
// If a cache is used for multiple purposes, then care must be taken to ensure uniqueness of cache keys.
//
// It is not recommended to change this struct's member data after creation as a data race will likely ensue.
type Client struct {
	// Storage is the cache storage scheme. (Required)
	Storage Storage

	// Logger defines a logger to used for errors during async cache writes (optional)
	Logger Logger

	// Metrics allow for tracking cache events (hit/miss/etc) (optional)
	Metrics Metrics

	// WriteTimeout is the max time spent waiting for cache writes to complete (optional - default 3 seconds)
	WriteTimeout time.Duration

	// track pending cache writes
	pendingWrites int64
}

// Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.
//
// It will asynchronously update/save the value in the cache on after a successful builder run
func (c *Client) Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	bytes, err := c.Storage.Get(ctx, key)
	if err != nil {
		if err == ErrCacheMiss {
			c.getMetrics().Track(CacheMiss)
			return c.onCacheMiss(ctx, key, dest, builder)
		}

		c.getLogger().Log("cache get error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheGetError)
		return err
	}

	return c.onCacheHit(ctx, key, dest, bytes)
}

func (c *Client) onCacheMiss(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	err := builder.Build(ctx, key, dest)
	if err != nil {
		c.getLogger().Log("cache miss build error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheLambdaError)
		return err
	}

	atomic.AddInt64(&c.pendingWrites, 1)
	go c.updateCache(key, dest)

	return err
}

func (c *Client) onCacheHit(ctx context.Context, key string, dest encoding.BinaryUnmarshaler, bytes []byte) error {
	err := dest.UnmarshalBinary(bytes)
	if err != nil {
		c.getLogger().Log("cache hit unmarshal error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheUnmarshalError)

		// invalidate to remove "bad" data
		_ = c.Invalidate(ctx, key)

		return err
	}

	c.getMetrics().Track(CacheHit)
	return nil
}

// update the cache with the supplied key/value pair
func (c *Client) updateCache(key string, val encoding.BinaryMarshaler) {
	defer func() {
		// update tracking
		atomic.AddInt64(&c.pendingWrites, -1)
	}()

	// use independent context so we don't miss cache updated
	ctx, cancelFn := context.WithTimeout(context.Background(), c.getWriteTimeout())
	defer cancelFn()

	bytes, err := val.MarshalBinary()
	if err != nil {
		c.getLogger().Log("cache update marshal error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheMarshalError)
		return
	}

	err = c.Storage.Set(ctx, key, bytes)
	if err != nil {
		c.getLogger().Log("cache update set error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheSetError)
	}
}

// Invalidate will force invalidate any matching key in the cache
func (c *Client) Invalidate(ctx context.Context, key string) error {
	err := c.Storage.Invalidate(ctx, key)
	if err != nil {
		c.getLogger().Log("cache invalidate error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheInvalidateError)
		return err
	}

	return nil
}

// return the supplied logger or a no-op implementation
func (c *Client) getLogger() Logger {
	if c.Logger != nil {
		return c.Logger
	}

	return noopLogger
}

// return the supplied metric tracker or a no-op implementation
func (c *Client) getMetrics() Metrics {
	if c.Metrics != nil {
		return c.Metrics
	}

	return noopMetrics
}

// return the timeout on cache writes
func (c *Client) getWriteTimeout() time.Duration {
	if int64(c.WriteTimeout) > 0 {
		return c.WriteTimeout
	}

	return 3 * time.Second
}

// Builder builds the data for a key
type Builder interface {
	// Build returns the data for the supplied key by populating dest
	Build(ctx context.Context, key string, dest BinaryEncoder) error
}

// BuilderFunc implements Builder as a function
type BuilderFunc func(ctx context.Context, key string, dest BinaryEncoder) error

// Build implements Builder
func (b BuilderFunc) Build(ctx context.Context, key string, dest BinaryEncoder) error {
	return b(ctx, key, dest)
}

// BinaryEncoder encodes/decodes the receiver to and from binary form
type BinaryEncoder interface {
	encoding.BinaryMarshaler
	encoding.BinaryUnmarshaler
}
<commit_msg>Make cache set public so that we can force set some values<commit_after>// Copyright 2017 Corey Scott http://www.sage42.org/
//
// 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 cache

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

// Client defines a cache instance.
//
// This can represent the cache for the entire system or for a particular use-case/type.
//
// If a cache is used for multiple purposes, then care must be taken to ensure uniqueness of cache keys.
//
// It is not recommended to change this struct's member data after creation as a data race will likely ensue.
type Client struct {
	// Storage is the cache storage scheme. (Required)
	Storage Storage

	// Logger defines a logger to used for errors during async cache writes (optional)
	Logger Logger

	// Metrics allow for tracking cache events (hit/miss/etc) (optional)
	Metrics Metrics

	// WriteTimeout is the max time spent waiting for cache writes to complete (optional - default 3 seconds)
	WriteTimeout time.Duration

	// track pending cache writes
	pendingWrites int64
}

// Get attempts to retrieve the value from cache and when it misses will run the builder func to create the value.
//
// It will asynchronously update/save the value in the cache on after a successful builder run
func (c *Client) Get(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	bytes, err := c.Storage.Get(ctx, key)
	if err != nil {
		if err == ErrCacheMiss {
			c.getMetrics().Track(CacheMiss)
			return c.onCacheMiss(ctx, key, dest, builder)
		}

		c.getLogger().Log("cache get error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheGetError)
		return err
	}

	return c.onCacheHit(ctx, key, dest, bytes)
}

func (c *Client) onCacheMiss(ctx context.Context, key string, dest BinaryEncoder, builder Builder) error {
	err := builder.Build(ctx, key, dest)
	if err != nil {
		c.getLogger().Log("cache miss build error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheLambdaError)
		return err
	}

	atomic.AddInt64(&c.pendingWrites, 1)
	go c.Set(context.Background(), key, dest)

	return err
}

func (c *Client) onCacheHit(ctx context.Context, key string, dest encoding.BinaryUnmarshaler, bytes []byte) error {
	err := dest.UnmarshalBinary(bytes)
	if err != nil {
		c.getLogger().Log("cache hit unmarshal error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheUnmarshalError)

		// invalidate to remove "bad" data
		_ = c.Invalidate(ctx, key)

		return err
	}

	c.getMetrics().Track(CacheHit)
	return nil
}

// Set will update the cache with the supplied key/value pair
// NOTE: generally this need not be called is it is called implicitly by Get
func (c *Client) Set(ctx context.Context, key string, val encoding.BinaryMarshaler) {
	defer func() {
		// update tracking
		atomic.AddInt64(&c.pendingWrites, -1)
	}()

	// use independent context so we don't miss cache updated
	ctx, cancelFn := context.WithTimeout(ctx, c.getWriteTimeout())
	defer cancelFn()

	bytes, err := val.MarshalBinary()
	if err != nil {
		c.getLogger().Log("cache update marshal error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheMarshalError)
		return
	}

	err = c.Storage.Set(ctx, key, bytes)
	if err != nil {
		c.getLogger().Log("cache update set error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheSetError)
	}
}

// Invalidate will force invalidate any matching key in the cache
func (c *Client) Invalidate(ctx context.Context, key string) error {
	err := c.Storage.Invalidate(ctx, key)
	if err != nil {
		c.getLogger().Log("cache invalidate error. key: '%s' error: %s", key, err)
		c.getMetrics().Track(CacheInvalidateError)
		return err
	}

	return nil
}

// return the supplied logger or a no-op implementation
func (c *Client) getLogger() Logger {
	if c.Logger != nil {
		return c.Logger
	}

	return noopLogger
}

// return the supplied metric tracker or a no-op implementation
func (c *Client) getMetrics() Metrics {
	if c.Metrics != nil {
		return c.Metrics
	}

	return noopMetrics
}

// return the timeout on cache writes
func (c *Client) getWriteTimeout() time.Duration {
	if int64(c.WriteTimeout) > 0 {
		return c.WriteTimeout
	}

	return 3 * time.Second
}

// Builder builds the data for a key
type Builder interface {
	// Build returns the data for the supplied key by populating dest
	Build(ctx context.Context, key string, dest BinaryEncoder) error
}

// BuilderFunc implements Builder as a function
type BuilderFunc func(ctx context.Context, key string, dest BinaryEncoder) error

// Build implements Builder
func (b BuilderFunc) Build(ctx context.Context, key string, dest BinaryEncoder) error {
	return b(ctx, key, dest)
}

// BinaryEncoder encodes/decodes the receiver to and from binary form
type BinaryEncoder interface {
	encoding.BinaryMarshaler
	encoding.BinaryUnmarshaler
}
<|endoftext|>


<commit_before>package cache

import (
	"code.google.com/p/goprotobuf/proto"
	"container/list"
	bin "encoding/binary"
	"goposm/binary"
	"goposm/element"
	"sort"
	"sync"
)

type Nodes []element.Node

func (s Nodes) Len() int           { return len(s) }
func (s Nodes) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s Nodes) Less(i, j int) bool { return s[i].Id < s[j].Id }

func packNodes(nodes []element.Node) *DeltaCoords {
	var lastLon, lastLat int64
	var lon, lat int64
	var lastId int64
	ids := make([]int64, len(nodes))
	lons := make([]int64, len(nodes))
	lats := make([]int64, len(nodes))

	i := 0
	for _, nd := range nodes {
		lon = int64(binary.CoordToInt(nd.Long))
		lat = int64(binary.CoordToInt(nd.Lat))
		ids[i] = nd.Id - lastId
		lons[i] = lon - lastLon
		lats[i] = lat - lastLat

		lastId = nd.Id
		lastLon = lon
		lastLat = lat
		i++
	}
	return &DeltaCoords{Ids: ids, Lats: lats, Lons: lons}
}

func unpackNodes(deltaCoords *DeltaCoords, nodes []element.Node) []element.Node {
	if len(deltaCoords.Ids) > cap(nodes) {
		nodes = make([]element.Node, len(deltaCoords.Ids))
	} else {
		nodes = nodes[:len(deltaCoords.Ids)]
	}

	var lastLon, lastLat int64
	var lon, lat int64
	var lastId, id int64

	for i := 0; i < len(deltaCoords.Ids); i++ {
		id = lastId + deltaCoords.Ids[i]
		lon = lastLon + deltaCoords.Lats[i]
		lat = lastLat + deltaCoords.Lons[i]
		nodes[i] = element.Node{
			OSMElem: element.OSMElem{Id: int64(id)},
			Long:    binary.IntToCoord(uint32(lon)),
			Lat:     binary.IntToCoord(uint32(lat)),
		}

		lastId = id
		lastLon = lon
		lastLat = lat
	}
	return nodes
}

type CoordsBunch struct {
	sync.Mutex
	id         int64
	coords     []element.Node
	elem       *list.Element
	needsWrite bool
}

type DeltaCoordsCache struct {
	Cache
	lruList   *list.List
	table     map[int64]*CoordsBunch
	freeNodes [][]element.Node
	capacity  int64
	mu        sync.Mutex
}

func NewDeltaCoordsCache(path string) (*DeltaCoordsCache, error) {
	coordsCache := DeltaCoordsCache{}
	err := coordsCache.open(path)
	if err != nil {
		return nil, err
	}
	coordsCache.lruList = list.New()
	coordsCache.table = make(map[int64]*CoordsBunch)
	coordsCache.capacity = 1024
	coordsCache.freeNodes = make([][]element.Node, 0)
	return &coordsCache, nil
}

func (self *DeltaCoordsCache) Close() {
	for bunchId, bunch := range self.table {
		if bunch.needsWrite {
			self.putCoordsPacked(bunchId, bunch.coords)
		}
	}
	self.Cache.Close()
}

func (self *DeltaCoordsCache) GetCoord(id int64) (element.Node, bool) {
	bunchId := getBunchId(id)
	bunch := self.getBunch(bunchId)
	defer bunch.Unlock()
	idx := sort.Search(len(bunch.coords), func(i int) bool {
		return bunch.coords[i].Id >= id
	})
	if idx < len(bunch.coords) && bunch.coords[idx].Id == id {
		return bunch.coords[idx], true
	}
	return element.Node{}, false
}

func (self *DeltaCoordsCache) FillWay(way *element.Way) bool {
	if way == nil {
		return false
	}
	way.Nodes = make([]element.Node, len(way.Refs))
	var ok bool
	for i, id := range way.Refs {
		way.Nodes[i], ok = self.GetCoord(id)
		if !ok {
			return false
		}
	}
	return true
}

func (self *DeltaCoordsCache) PutCoords(nodes []element.Node) {
	var start, currentBunchId int64
	currentBunchId = getBunchId(nodes[0].Id)
	start = 0
	for i, node := range nodes {
		bunchId := getBunchId(node.Id)
		if bunchId != currentBunchId {
			bunch := self.getBunch(currentBunchId)
			bunch.coords = append(bunch.coords, nodes[start:i-1]...)
			sort.Sort(Nodes(bunch.coords))
			currentBunchId = bunchId
			start = int64(i)
			bunch.needsWrite = true
			bunch.Unlock()
		}
	}
	bunch := self.getBunch(currentBunchId)
	bunch.coords = append(bunch.coords, nodes[start:]...)
	sort.Sort(Nodes(bunch.coords))
	bunch.needsWrite = true
	bunch.Unlock()
}

func (p *DeltaCoordsCache) putCoordsPacked(bunchId int64, nodes []element.Node) {
	if len(nodes) == 0 {
		return
	}
	keyBuf := make([]byte, 8)
	bin.PutVarint(keyBuf, bunchId)

	deltaCoords := packNodes(nodes)
	data, err := proto.Marshal(deltaCoords)
	if err != nil {
		panic(err)
	}
	p.db.Put(p.wo, keyBuf, data)
}

func (p *DeltaCoordsCache) getCoordsPacked(bunchId int64, nodes []element.Node) []element.Node {
	keyBuf := make([]byte, 8)
	bin.PutVarint(keyBuf, bunchId)

	data, err := p.db.Get(p.ro, keyBuf)
	if err != nil {
		panic(err)
	}
	if data == nil {
		// clear before returning
		return nodes[:0]
	}
	deltaCoords := &DeltaCoords{}
	err = proto.Unmarshal(data, deltaCoords)
	if err != nil {
		panic(err)
	}

	nodes = unpackNodes(deltaCoords, nodes)
	return nodes
}

func getBunchId(nodeId int64) int64 {
	return nodeId / (1024 * 8)
}

func (self *DeltaCoordsCache) getBunch(bunchId int64) *CoordsBunch {
	self.mu.Lock()
	defer self.mu.Unlock()
	bunch, ok := self.table[bunchId]
	var nodes []element.Node
	if !ok {
		elem := self.lruList.PushFront(bunchId)
		if len(self.freeNodes) > 0 {
			nodes = self.freeNodes[len(self.freeNodes)-1]
			self.freeNodes = self.freeNodes[:len(self.freeNodes)-1]
		} else {
			nodes = make([]element.Node, 0)
		}
		nodes = self.getCoordsPacked(bunchId, nodes)
		bunch = &CoordsBunch{id: bunchId, coords: nodes, elem: elem}
		self.table[bunchId] = bunch
	} else {
		self.lruList.MoveToFront(bunch.elem)
	}
	bunch.Lock()
	self.CheckCapacity()
	return bunch
}

func (self *DeltaCoordsCache) CheckCapacity() {
	for int64(len(self.table)) > self.capacity {
		elem := self.lruList.Back()
		bunchId := self.lruList.Remove(elem).(int64)
		bunch := self.table[bunchId]
		bunch.elem = nil
		if bunch.needsWrite {
			self.putCoordsPacked(bunchId, bunch.coords)
		}
		self.freeNodes = append(self.freeNodes, bunch.coords)
		delete(self.table, bunchId)
	}
}
<commit_msg>use smaller delta node bunch size<commit_after>package cache

import (
	"code.google.com/p/goprotobuf/proto"
	"container/list"
	bin "encoding/binary"
	"goposm/binary"
	"goposm/element"
	"sort"
	"sync"
)

type Nodes []element.Node

func (s Nodes) Len() int           { return len(s) }
func (s Nodes) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }
func (s Nodes) Less(i, j int) bool { return s[i].Id < s[j].Id }

func packNodes(nodes []element.Node) *DeltaCoords {
	var lastLon, lastLat int64
	var lon, lat int64
	var lastId int64
	ids := make([]int64, len(nodes))
	lons := make([]int64, len(nodes))
	lats := make([]int64, len(nodes))

	i := 0
	for _, nd := range nodes {
		lon = int64(binary.CoordToInt(nd.Long))
		lat = int64(binary.CoordToInt(nd.Lat))
		ids[i] = nd.Id - lastId
		lons[i] = lon - lastLon
		lats[i] = lat - lastLat

		lastId = nd.Id
		lastLon = lon
		lastLat = lat
		i++
	}
	return &DeltaCoords{Ids: ids, Lats: lats, Lons: lons}
}

func unpackNodes(deltaCoords *DeltaCoords, nodes []element.Node) []element.Node {
	if len(deltaCoords.Ids) > cap(nodes) {
		nodes = make([]element.Node, len(deltaCoords.Ids))
	} else {
		nodes = nodes[:len(deltaCoords.Ids)]
	}

	var lastLon, lastLat int64
	var lon, lat int64
	var lastId, id int64

	for i := 0; i < len(deltaCoords.Ids); i++ {
		id = lastId + deltaCoords.Ids[i]
		lon = lastLon + deltaCoords.Lats[i]
		lat = lastLat + deltaCoords.Lons[i]
		nodes[i] = element.Node{
			OSMElem: element.OSMElem{Id: int64(id)},
			Long:    binary.IntToCoord(uint32(lon)),
			Lat:     binary.IntToCoord(uint32(lat)),
		}

		lastId = id
		lastLon = lon
		lastLat = lat
	}
	return nodes
}

type CoordsBunch struct {
	sync.Mutex
	id         int64
	coords     []element.Node
	elem       *list.Element
	needsWrite bool
}

type DeltaCoordsCache struct {
	Cache
	lruList   *list.List
	table     map[int64]*CoordsBunch
	freeNodes [][]element.Node
	capacity  int64
	mu        sync.Mutex
}

func NewDeltaCoordsCache(path string) (*DeltaCoordsCache, error) {
	coordsCache := DeltaCoordsCache{}
	err := coordsCache.open(path)
	if err != nil {
		return nil, err
	}
	coordsCache.lruList = list.New()
	coordsCache.table = make(map[int64]*CoordsBunch)
	coordsCache.capacity = 1024 * 8
	coordsCache.freeNodes = make([][]element.Node, 0)
	return &coordsCache, nil
}

func (self *DeltaCoordsCache) Close() {
	for bunchId, bunch := range self.table {
		if bunch.needsWrite {
			self.putCoordsPacked(bunchId, bunch.coords)
		}
	}
	self.Cache.Close()
}

func (self *DeltaCoordsCache) GetCoord(id int64) (element.Node, bool) {
	bunchId := getBunchId(id)
	bunch := self.getBunch(bunchId)
	defer bunch.Unlock()
	idx := sort.Search(len(bunch.coords), func(i int) bool {
		return bunch.coords[i].Id >= id
	})
	if idx < len(bunch.coords) && bunch.coords[idx].Id == id {
		return bunch.coords[idx], true
	}
	return element.Node{}, false
}

func (self *DeltaCoordsCache) FillWay(way *element.Way) bool {
	if way == nil {
		return false
	}
	way.Nodes = make([]element.Node, len(way.Refs))
	var ok bool
	for i, id := range way.Refs {
		way.Nodes[i], ok = self.GetCoord(id)
		if !ok {
			return false
		}
	}
	return true
}

func (self *DeltaCoordsCache) PutCoords(nodes []element.Node) {
	var start, currentBunchId int64
	currentBunchId = getBunchId(nodes[0].Id)
	start = 0
	for i, node := range nodes {
		bunchId := getBunchId(node.Id)
		if bunchId != currentBunchId {
			bunch := self.getBunch(currentBunchId)
			bunch.coords = append(bunch.coords, nodes[start:i-1]...)
			sort.Sort(Nodes(bunch.coords))
			currentBunchId = bunchId
			start = int64(i)
			bunch.needsWrite = true
			bunch.Unlock()
		}
	}
	bunch := self.getBunch(currentBunchId)
	bunch.coords = append(bunch.coords, nodes[start:]...)
	sort.Sort(Nodes(bunch.coords))
	bunch.needsWrite = true
	bunch.Unlock()
}

func (p *DeltaCoordsCache) putCoordsPacked(bunchId int64, nodes []element.Node) {
	if len(nodes) == 0 {
		return
	}
	keyBuf := make([]byte, 8)
	bin.PutVarint(keyBuf, bunchId)

	deltaCoords := packNodes(nodes)
	data, err := proto.Marshal(deltaCoords)
	if err != nil {
		panic(err)
	}
	p.db.Put(p.wo, keyBuf, data)
}

func (p *DeltaCoordsCache) getCoordsPacked(bunchId int64, nodes []element.Node) []element.Node {
	keyBuf := make([]byte, 8)
	bin.PutVarint(keyBuf, bunchId)

	data, err := p.db.Get(p.ro, keyBuf)
	if err != nil {
		panic(err)
	}
	if data == nil {
		// clear before returning
		return nodes[:0]
	}
	deltaCoords := &DeltaCoords{}
	err = proto.Unmarshal(data, deltaCoords)
	if err != nil {
		panic(err)
	}

	nodes = unpackNodes(deltaCoords, nodes)
	return nodes
}

func getBunchId(nodeId int64) int64 {
	return nodeId / (64)
}

func (self *DeltaCoordsCache) getBunch(bunchId int64) *CoordsBunch {
	self.mu.Lock()
	defer self.mu.Unlock()
	bunch, ok := self.table[bunchId]
	var nodes []element.Node
	if !ok {
		elem := self.lruList.PushFront(bunchId)
		if len(self.freeNodes) > 0 {
			nodes = self.freeNodes[len(self.freeNodes)-1]
			self.freeNodes = self.freeNodes[:len(self.freeNodes)-1]
		} else {
			nodes = make([]element.Node, 0)
		}
		nodes = self.getCoordsPacked(bunchId, nodes)
		bunch = &CoordsBunch{id: bunchId, coords: nodes, elem: elem}
		self.table[bunchId] = bunch
	} else {
		self.lruList.MoveToFront(bunch.elem)
	}
	bunch.Lock()
	self.CheckCapacity()
	return bunch
}

func (self *DeltaCoordsCache) CheckCapacity() {
	for int64(len(self.table)) > self.capacity {
		elem := self.lruList.Back()
		bunchId := self.lruList.Remove(elem).(int64)
		bunch := self.table[bunchId]
		bunch.elem = nil
		if bunch.needsWrite {
			self.putCoordsPacked(bunchId, bunch.coords)
		}
		self.freeNodes = append(self.freeNodes, bunch.coords)
		delete(self.table, bunchId)
	}
}
<|endoftext|>


<commit_before>package cacher_test

import (
	"testing"

	"github.com/jelmersnoeck/cacher"
)

func acceptCacher(c cacher.Cacher) bool {
	return true
}

func TestMemoryCache(t *testing.T) {
	cache := cacher.NewMemoryCache(0)

	if !acceptCacher(cache) {
		t.Errorf("Expected MemoryCache to be accepted")
		t.FailNow()
	}
}
<commit_msg>CacherTest: redundant test.<commit_after><|endoftext|>


<commit_before>package caddy

import (
	"flag"
	"fmt"
	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/tarent/lib-compose/logging"
	_ "github.com/tarent/loginsrv/htpasswd"
	"github.com/tarent/loginsrv/login"
	_ "github.com/tarent/loginsrv/oauth2"
	_ "github.com/tarent/loginsrv/osiam"
	"os"
)

func init() {
	caddy.RegisterPlugin("login", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
	httpserver.RegisterDevDirective("login", "jwt")
}

// setup configures a new loginsrv instance.
func setup(c *caddy.Controller) error {
	logging.Set("info", true)

	for c.Next() {
		args := c.RemainingArgs()

		if len(args) < 1 {
			return fmt.Errorf("Missing path argument for loginsrv directive (%v:%v)", c.File(), c.Line())
		}

		if len(args) > 1 {
			return fmt.Errorf("To many arguments for loginsrv directive %q (%v:%v)", args, c.File(), c.Line())
		}

		config, err := parseConfig(c)
		if err != nil {
			return err
		}

		if e, isset := os.LookupEnv("JWT_SECRET"); isset {
			config.JwtSecret = e
		} else {
			os.Setenv("JWT_SECRET", config.JwtSecret)
		}
		fmt.Printf("config %+v\n", config)
		loginHandler, err := login.NewHandler(config)
		if err != nil {
			return err
		}

		httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
			return NewCaddyHandler(next, args[0], loginHandler, config)
		})
	}

	return nil
}

func parseConfig(c *caddy.Controller) (*login.Config, error) {
	cfg := login.DefaultConfig()
	cfg.Host = ""
	cfg.Port = ""
	cfg.LogLevel = ""

	fs := flag.NewFlagSet("loginsrv-config", flag.ContinueOnError)
	cfg.ConfigureFlagSet(fs)

	for c.NextBlock() {
		name := c.Val()
		args := c.RemainingArgs()
		if len(args) != 1 {
			return cfg, fmt.Errorf("Wrong number of arguments for %v: %v (%v:%v)", name, args, c.File(), c.Line())
		}
		value := args[0]

		f := fs.Lookup(name)
		if f == nil {
			c.ArgErr()
			continue
		}
		f.Value.Set(value)
	}

	return cfg, nil
}
<commit_msg>removed RegisterDevDirective, because this is in upstream<commit_after>package caddy

import (
	"flag"
	"fmt"
	"github.com/mholt/caddy"
	"github.com/mholt/caddy/caddyhttp/httpserver"
	"github.com/tarent/lib-compose/logging"
	_ "github.com/tarent/loginsrv/htpasswd"
	"github.com/tarent/loginsrv/login"
	_ "github.com/tarent/loginsrv/oauth2"
	_ "github.com/tarent/loginsrv/osiam"
	"os"
)

func init() {
	caddy.RegisterPlugin("login", caddy.Plugin{
		ServerType: "http",
		Action:     setup,
	})
}

// setup configures a new loginsrv instance.
func setup(c *caddy.Controller) error {
	logging.Set("info", true)

	for c.Next() {
		args := c.RemainingArgs()

		if len(args) < 1 {
			return fmt.Errorf("Missing path argument for loginsrv directive (%v:%v)", c.File(), c.Line())
		}

		if len(args) > 1 {
			return fmt.Errorf("To many arguments for loginsrv directive %q (%v:%v)", args, c.File(), c.Line())
		}

		config, err := parseConfig(c)
		if err != nil {
			return err
		}

		if e, isset := os.LookupEnv("JWT_SECRET"); isset {
			config.JwtSecret = e
		} else {
			os.Setenv("JWT_SECRET", config.JwtSecret)
		}
		fmt.Printf("config %+v\n", config)
		loginHandler, err := login.NewHandler(config)
		if err != nil {
			return err
		}

		httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
			return NewCaddyHandler(next, args[0], loginHandler, config)
		})
	}

	return nil
}

func parseConfig(c *caddy.Controller) (*login.Config, error) {
	cfg := login.DefaultConfig()
	cfg.Host = ""
	cfg.Port = ""
	cfg.LogLevel = ""

	fs := flag.NewFlagSet("loginsrv-config", flag.ContinueOnError)
	cfg.ConfigureFlagSet(fs)

	for c.NextBlock() {
		name := c.Val()
		args := c.RemainingArgs()
		if len(args) != 1 {
			return cfg, fmt.Errorf("Wrong number of arguments for %v: %v (%v:%v)", name, args, c.File(), c.Line())
		}
		value := args[0]

		f := fs.Lookup(name)
		if f == nil {
			c.ArgErr()
			continue
		}
		f.Value.Set(value)
	}

	return cfg, nil
}
<|endoftext|>


<commit_before>package canal

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"

	"github.com/siddontang/go-mysql/client"
	"github.com/siddontang/go-mysql/dump"
	"github.com/siddontang/go-mysql/mysql"
	"github.com/siddontang/go-mysql/replication"
	"github.com/siddontang/go-mysql/schema"
	"github.com/siddontang/go/log"
	"github.com/siddontang/go/sync2"
)

var errCanalClosed = errors.New("canal was closed")

// Canal can sync your MySQL data into everywhere, like Elasticsearch, Redis, etc...
// MySQL must open row format for binlog
type Canal struct {
	m sync.Mutex

	cfg *Config

	master     *masterInfo
	dumper     *dump.Dumper
	dumpDoneCh chan struct{}
	syncer     *replication.BinlogSyncer

	rsLock     sync.Mutex
	rsHandlers []RowsEventHandler

	connLock sync.Mutex
	conn     *client.Conn

	wg sync.WaitGroup

	tableLock sync.Mutex
	tables    map[string]*schema.Table

	quit   chan struct{}
	closed sync2.AtomicBool
}

func NewCanal(cfg *Config) (*Canal, error) {
	c := new(Canal)
	c.cfg = cfg
	c.closed.Set(false)
	c.quit = make(chan struct{})

	os.MkdirAll(cfg.DataDir, 0755)

	c.dumpDoneCh = make(chan struct{})
	c.rsHandlers = make([]RowsEventHandler, 0, 4)
	c.tables = make(map[string]*schema.Table)

	var err error
	if c.master, err = loadMasterInfo(c.masterInfoPath()); err != nil {
		return nil, err
	} else if len(c.master.Addr) != 0 && c.master.Addr != c.cfg.Addr {
		log.Infof("MySQL addr %s in old master.info, but new %s, reset", c.master.Addr, c.cfg.Addr)
		// may use another MySQL, reset
		c.master = &masterInfo{}
	}

	c.master.Addr = c.cfg.Addr

	if err := c.prepareDumper(); err != nil {
		return nil, err
	}

	if err = c.prepareSyncer(); err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Canal) prepareDumper() error {
	var err error
	if c.dumper, err = dump.NewDumper(c.cfg.Dump.ExecutionPath,
		c.cfg.Addr, c.cfg.User, c.cfg.Password); err != nil {
		if err != exec.ErrNotFound {
			return err
		}
		//no mysqldump, use binlog only
		c.dumper = nil
		return nil
	}

	dbs := c.cfg.Dump.Databases
	tables := c.cfg.Dump.Tables
	tableDB := c.cfg.Dump.TableDB

	if len(tables) == 0 {
		c.dumper.AddDatabases(dbs...)
	} else {
		c.dumper.AddTables(tableDB, tables...)
	}

	for _, ignoreTable := range c.cfg.Dump.IgnoreTables {
		if seps := strings.Split(ignoreTable, ","); len(seps) == 2 {
			c.dumper.AddIgnoreTables(seps[0], seps[1])
		}
	}

	c.dumper.SetErrOut(ioutil.Discard)
	return nil
}

func (c *Canal) Start() error {
	if err := c.checkBinlogFormat(); err != nil {
		return err
	}

	c.wg.Add(1)
	go c.run()

	return nil
}

func (c *Canal) run() error {
	defer c.wg.Done()

	if err := c.tryDump(); err != nil {
		log.Errorf("canal dump mysql err: %v", err)
		return err
	}

	close(c.dumpDoneCh)

	if err := c.startSyncBinlog(); err != nil {
		if !c.isClosed() {
			log.Errorf("canal start sync binlog err: %v", err)
		}
		return err
	}

	return nil
}

func (c *Canal) isClosed() bool {
	return c.closed.Get()
}

func (c *Canal) Close() {
	log.Infof("close canal")

	c.m.Lock()
	defer c.m.Unlock()

	if c.isClosed() {
		return
	}

	c.closed.Set(true)

	close(c.quit)

	c.connLock.Lock()
	c.conn.Close()
	c.conn = nil
	c.connLock.Unlock()

	if c.syncer != nil {
		c.syncer.Close()
		c.syncer = nil
	}

	c.master.Close()

	c.wg.Wait()
}

func (c *Canal) WaitDumpDone() <-chan struct{} {
	return c.dumpDoneCh
}

func (c *Canal) GetTable(db string, table string) (*schema.Table, error) {
	key := fmt.Sprintf("%s.%s", db, table)
	c.tableLock.Lock()
	t, ok := c.tables[key]
	c.tableLock.Unlock()

	if ok {
		return t, nil
	}

	t, err := schema.NewTable(c, db, table)
	if err != nil {
		return nil, err
	}

	c.tableLock.Lock()
	c.tables[key] = t
	c.tableLock.Unlock()

	return t, nil
}

func (c *Canal) checkBinlogFormat() error {
	res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_format";`)
	if err != nil {
		return err
	} else if f, _ := res.GetString(0, 1); f != "ROW" {
		return fmt.Errorf("binlog must ROW format, but %s now", f)
	}

	// need to check MySQL binlog row image? full, minimal or noblob?
	// now only log
	if c.cfg.Flavor == mysql.MySQLFlavor {
		if res, err = c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_row_image"`); err != nil {
			return err
		}

		rowImage, _ := res.GetString(0, 1)
		log.Infof("MySQL use binlog row %s image", rowImage)
	}

	return nil
}

func (c *Canal) prepareSyncer() error {
	c.syncer = replication.NewBinlogSyncer(c.cfg.ServerID, c.cfg.Flavor)

	seps := strings.Split(c.cfg.Addr, ":")
	if len(seps) != 2 {
		return fmt.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
	}

	port, err := strconv.ParseUint(seps[1], 10, 16)
	if err != nil {
		return err
	}

	if err = c.syncer.RegisterSlave(seps[0], uint16(port), c.cfg.User, c.cfg.Password); err != nil {
		return err
	}
	return nil
}

func (c *Canal) masterInfoPath() string {
	return path.Join(c.cfg.DataDir, "master.info")
}

// Execute a SQL
func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err error) {
	c.connLock.Lock()
	defer c.connLock.Unlock()

	retryNum := 3
	for i := 0; i < retryNum; i++ {
		if c.conn == nil {
			c.conn, err = client.Connect(c.cfg.Addr, c.cfg.User, c.cfg.Password, "")
			if err != nil {
				return nil, err
			}
		}

		rr, err = c.conn.Execute(cmd, args...)
		if err != nil && err != mysql.ErrBadConn {
			return
		} else if err == mysql.ErrBadConn {
			c.conn.Close()
			c.conn = nil
			continue
		} else {
			return
		}
	}
	return
}
<commit_msg>add CheckBinlogRowImage function<commit_after>package canal

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"sync"

	"github.com/siddontang/go-mysql/client"
	"github.com/siddontang/go-mysql/dump"
	"github.com/siddontang/go-mysql/mysql"
	"github.com/siddontang/go-mysql/replication"
	"github.com/siddontang/go-mysql/schema"
	"github.com/siddontang/go/log"
	"github.com/siddontang/go/sync2"
)

var errCanalClosed = errors.New("canal was closed")

// Canal can sync your MySQL data into everywhere, like Elasticsearch, Redis, etc...
// MySQL must open row format for binlog
type Canal struct {
	m sync.Mutex

	cfg *Config

	master     *masterInfo
	dumper     *dump.Dumper
	dumpDoneCh chan struct{}
	syncer     *replication.BinlogSyncer

	rsLock     sync.Mutex
	rsHandlers []RowsEventHandler

	connLock sync.Mutex
	conn     *client.Conn

	wg sync.WaitGroup

	tableLock sync.Mutex
	tables    map[string]*schema.Table

	quit   chan struct{}
	closed sync2.AtomicBool
}

func NewCanal(cfg *Config) (*Canal, error) {
	c := new(Canal)
	c.cfg = cfg
	c.closed.Set(false)
	c.quit = make(chan struct{})

	os.MkdirAll(cfg.DataDir, 0755)

	c.dumpDoneCh = make(chan struct{})
	c.rsHandlers = make([]RowsEventHandler, 0, 4)
	c.tables = make(map[string]*schema.Table)

	var err error
	if c.master, err = loadMasterInfo(c.masterInfoPath()); err != nil {
		return nil, err
	} else if len(c.master.Addr) != 0 && c.master.Addr != c.cfg.Addr {
		log.Infof("MySQL addr %s in old master.info, but new %s, reset", c.master.Addr, c.cfg.Addr)
		// may use another MySQL, reset
		c.master = &masterInfo{}
	}

	c.master.Addr = c.cfg.Addr

	if err := c.prepareDumper(); err != nil {
		return nil, err
	}

	if err = c.prepareSyncer(); err != nil {
		return nil, err
	}

	if err := c.checkBinlogRowFormat(); err != nil {
		return nil, err
	}

	return c, nil
}

func (c *Canal) prepareDumper() error {
	var err error
	if c.dumper, err = dump.NewDumper(c.cfg.Dump.ExecutionPath,
		c.cfg.Addr, c.cfg.User, c.cfg.Password); err != nil {
		if err != exec.ErrNotFound {
			return err
		}
		//no mysqldump, use binlog only
		c.dumper = nil
		return nil
	}

	dbs := c.cfg.Dump.Databases
	tables := c.cfg.Dump.Tables
	tableDB := c.cfg.Dump.TableDB

	if len(tables) == 0 {
		c.dumper.AddDatabases(dbs...)
	} else {
		c.dumper.AddTables(tableDB, tables...)
	}

	for _, ignoreTable := range c.cfg.Dump.IgnoreTables {
		if seps := strings.Split(ignoreTable, ","); len(seps) == 2 {
			c.dumper.AddIgnoreTables(seps[0], seps[1])
		}
	}

	c.dumper.SetErrOut(ioutil.Discard)
	return nil
}

func (c *Canal) Start() error {
	c.wg.Add(1)
	go c.run()

	return nil
}

func (c *Canal) run() error {
	defer c.wg.Done()

	if err := c.tryDump(); err != nil {
		log.Errorf("canal dump mysql err: %v", err)
		return err
	}

	close(c.dumpDoneCh)

	if err := c.startSyncBinlog(); err != nil {
		if !c.isClosed() {
			log.Errorf("canal start sync binlog err: %v", err)
		}
		return err
	}

	return nil
}

func (c *Canal) isClosed() bool {
	return c.closed.Get()
}

func (c *Canal) Close() {
	log.Infof("close canal")

	c.m.Lock()
	defer c.m.Unlock()

	if c.isClosed() {
		return
	}

	c.closed.Set(true)

	close(c.quit)

	c.connLock.Lock()
	c.conn.Close()
	c.conn = nil
	c.connLock.Unlock()

	if c.syncer != nil {
		c.syncer.Close()
		c.syncer = nil
	}

	c.master.Close()

	c.wg.Wait()
}

func (c *Canal) WaitDumpDone() <-chan struct{} {
	return c.dumpDoneCh
}

func (c *Canal) GetTable(db string, table string) (*schema.Table, error) {
	key := fmt.Sprintf("%s.%s", db, table)
	c.tableLock.Lock()
	t, ok := c.tables[key]
	c.tableLock.Unlock()

	if ok {
		return t, nil
	}

	t, err := schema.NewTable(c, db, table)
	if err != nil {
		return nil, err
	}

	c.tableLock.Lock()
	c.tables[key] = t
	c.tableLock.Unlock()

	return t, nil
}

// Check MySQL binlog row image, must be in FULL, MINIMAL, NOBLOB
func (c *Canal) CheckBinlogRowImage(image string) error {
	// need to check MySQL binlog row image? full, minimal or noblob?
	// now only log
	if c.cfg.Flavor == mysql.MySQLFlavor {
		if res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_row_image"`); err != nil {
			return err
		} else {
			rowImage, _ := res.GetString(0, 1)
			if !strings.EqualFold(rowImage, image) {
				return fmt.Errorf("MySQL uses %s binlog row image, but we want %s", rowImage, image)
			}
		}
	}

	return nil
}

func (c *Canal) checkBinlogRowFormat() error {
	res, err := c.Execute(`SHOW GLOBAL VARIABLES LIKE "binlog_format";`)
	if err != nil {
		return err
	} else if f, _ := res.GetString(0, 1); f != "ROW" {
		return fmt.Errorf("binlog must ROW format, but %s now", f)
	}

	return nil
}

func (c *Canal) prepareSyncer() error {
	c.syncer = replication.NewBinlogSyncer(c.cfg.ServerID, c.cfg.Flavor)

	seps := strings.Split(c.cfg.Addr, ":")
	if len(seps) != 2 {
		return fmt.Errorf("invalid mysql addr format %s, must host:port", c.cfg.Addr)
	}

	port, err := strconv.ParseUint(seps[1], 10, 16)
	if err != nil {
		return err
	}

	if err = c.syncer.RegisterSlave(seps[0], uint16(port), c.cfg.User, c.cfg.Password); err != nil {
		return err
	}
	return nil
}

func (c *Canal) masterInfoPath() string {
	return path.Join(c.cfg.DataDir, "master.info")
}

// Execute a SQL
func (c *Canal) Execute(cmd string, args ...interface{}) (rr *mysql.Result, err error) {
	c.connLock.Lock()
	defer c.connLock.Unlock()

	retryNum := 3
	for i := 0; i < retryNum; i++ {
		if c.conn == nil {
			c.conn, err = client.Connect(c.cfg.Addr, c.cfg.User, c.cfg.Password, "")
			if err != nil {
				return nil, err
			}
		}

		rr, err = c.conn.Execute(cmd, args...)
		if err != nil && err != mysql.ErrBadConn {
			return
		} else if err == mysql.ErrBadConn {
			c.conn.Close()
			c.conn = nil
			continue
		} else {
			return
		}
	}
	return
}
<|endoftext|>