<commit_before>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 blob

import (
	"fmt"
	"sync"

	"github.com/jacobsa/comeback/kv"
	"github.com/jacobsa/gcloud/syncutil"
)

// Return a blob store that stores blobs in the supplied key/value store. Keys
// look like:
//
//     <prefix><score>
//
// where <score> is the result of calling Score.Hex.
//
// The blob store trusts that it has full ownership of this portion of the
// store's key space -- if a score key exists, then it points to the correct
// data.
//
// bufferSize controls the number of bytes that may be buffered by this class,
// used to avoid hogging RAM. It should be set to a few times the product of
// the desired bandwidth in bytes and the typical latency of a write to the KV
// store. This must be at least as large as the largest blob written.
//
// maxInFlight controls the maximum parallelism with which we will call the KV
// store, used to avoid hammering it too hard. It should be set to a few times
// the product of the desired request rate in Hz and the typical latency of a
// write.
func NewKVStoreBlobStore(
	kvStore kv.Store,
	prefix string,
	bufferSize int,
	maxInFlight int) Store {
	s := &kvBasedBlobStore{
		kvStore:             kvStore,
		keyPrefix:           prefix,
		maxBytesBuffered:    bufferSize,
		maxRequestsInFlight: maxInFlight,
		inFlight:            make(map[Score]int),
	}

	s.mu = syncutil.NewInvariantMutex(s.checkInvariants)
	s.inFlightChanged.L = &s.mu

	return s
}

type kvBasedBlobStore struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	kvStore kv.Store

	/////////////////////////
	// Constant data
	/////////////////////////

	keyPrefix           string
	maxBytesBuffered    int
	maxRequestsInFlight int

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// If any background write to the KV store has failed, an error that should
	// be returned for all future calls to Store or Flush.
	//
	// GUARDED_BY(mu)
	writeErr error

	// A map from scores that have been accepted by Store but not yet
	// successfully written out to the length of the corresponding blobs.
	//
	// INVARIANT: len(inFlight) <= maxRequestsInFlight
	//
	// GUARDED_BY(mu)
	inFlight map[Score]int

	// A cached sum of the lengths of in-flight scores.
	//
	// INVARIANT: bytesBuffered is the sum of all values of inFlight
	// INVARIANT: 0 <= bytesBuffered <= maxBytesBuffered
	//
	// GUARDED_BY(mu)
	bytesBuffered int

	// Signalled when the contents of inFlight change.
	inFlightChanged sync.Cond
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (s *kvBasedBlobStore) checkInvariants() {
	// INVARIANT: len(inFlight) <= maxRequestsInFlight
	if !(len(s.inFlight) <= s.maxRequestsInFlight) {
		panic(fmt.Sprintf("Too many in flight: %v", len(s.inFlight)))
	}

	// INVARIANT: bytesBuffered is the sum of all values of inFlight
	sum := 0
	for _, v := range s.inFlight {
		sum += v
	}

	if sum != s.bytesBuffered {
		panic(fmt.Sprintf("Differing sum: %v vs. %v", sum, s.bytesBuffered))
	}

	// INVARIANT: 0 <= bytesBuffered <= maxBytesBuffered
	if !(0 <= s.bytesBuffered && s.bytesBuffered <= s.maxBytesBuffered) {
		panic(fmt.Sprintf(
			"bytesBuffered of %v not in range [0, %v]",
			s.bytesBuffered,
			s.maxBytesBuffered))
	}
}

func (s *kvBasedBlobStore) makeKey(score Score) (key string) {
	key = s.keyPrefix + score.Hex()
	return
}

// LOCKS_REQURED(s.mu)
func (s *kvBasedBlobStore) hasRoom(blobLen int) bool {
	bytesLeft := s.maxBytesBuffered - s.bytesBuffered
	return len(s.inFlight) < s.maxRequestsInFlight && blobLen <= bytesLeft
}

// LOCKS_EXCLUDED(s.mu)
func (s *kvBasedBlobStore) makeDurable(score Score, key string, blob []byte) {
	var err error

	// When we exit, set a write error (if appropriate) and update the in-flight
	// map.
	defer func() {
		s.mu.Lock()
		defer s.mu.Unlock()

		// Is this the first write error?
		if err != nil && s.writeErr == nil {
			s.writeErr = err
		}

		delete(s.inFlight, score)
		s.bytesBuffered -= len(blob)
		s.inFlightChanged.Broadcast()
	}()

	// Attempt to write out the blob.
	err = s.kvStore.Set(key, blob)
	if err != nil {
		err = fmt.Errorf("kvStore.Set: %v", err)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (s *kvBasedBlobStore) Store(blob []byte) (score Score, err error) {
	// Will this blob ever fit?
	if len(blob) > s.maxBytesBuffered {
		err = fmt.Errorf(
			"%v-byte blob is larger than buffer size of %v",
			len(blob),
			s.maxBytesBuffered)

		return
	}

	// Compute a score and a key for use under the lock below.
	score = ComputeScore(blob)
	key := s.makeKey(score)

	s.mu.Lock()
	defer s.mu.Unlock()

	// Wait until there is room for this request.
	for !s.hasRoom(len(blob)) {
		s.inFlightChanged.Wait()
	}

	// Have we already decided on a final error?
	if s.writeErr != nil {
		err = fmt.Errorf("Error from previous write: %v", s.writeErr)
		return
	}

	// If we already have a request for this score in flight, we can avoid
	// spawning another one. (If the existing one fails, the user will see an
	// error from Flush.)
	if _, ok := s.inFlight[score]; ok {
		return
	}

	// If the KV store already contains the key, we need not do anything further.
	if s.kvStore.Contains(key) {
		return
	}

	// Mark this blob as in flight.
	s.inFlight[score] = len(blob)
	s.bytesBuffered += len(blob)
	s.inFlightChanged.Broadcast()

	// Spawn a goroutine that will make the blob durable in the background.
	go s.makeDurable(score, key, blob)

	return
}

func (s *kvBasedBlobStore) Flush() (err error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Wait until there are no requests in flight.
	for len(s.inFlight) != 0 {
		s.inFlightChanged.Wait()
	}

	err = s.writeErr
	return
}

func (s *kvBasedBlobStore) Contains(score Score) (b bool) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Do we have an in-flight request?
	if _, ok := s.inFlight[score]; ok {
		b = true
		return
	}

	// Does the key exist in the KV store?
	key := s.makeKey(score)
	if s.kvStore.Contains(key) {
		b = true
		return
	}

	return
}

func (s *kvBasedBlobStore) Load(score Score) (blob []byte, err error) {
	// Choose the appropriate key.
	key := s.makeKey(score)

	// Call the key/value store.
	if blob, err = s.kvStore.Get(key); err != nil {
		err = fmt.Errorf("Get: %v", err)
		return
	}

	return
}
<commit_msg>Updated public interface.<commit_after>// Copyright 2015 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 blob

import (
	"fmt"
	"sync"

	"github.com/jacobsa/comeback/kv"
	"github.com/jacobsa/gcloud/syncutil"
)

// Return a blob store whose Store method buffers around a wrapped store,
// allowing the caller to proceed concurrently while wrapped.Store runs, even
// if it takes awhile.
//
// bufferSize controls the number of bytes that may be buffered by this store,
// used to avoid hogging RAM. It should be set to a few times the product of
// the desired bandwidth in bytes and the typical latency of a write to the
// wrapped store. This must be at least as large as the largest blob written.
//
// maxInFlight controls the maximum parallelism with which we will call the KV
// store, used to avoid hammering it too hard. It should be set to a few times
// the product of the desired request rate in Hz and the typical latency of a
// write.
//
// It is assumed that when wrapped.Store returns successfully, the blob is
// durable.
func NewBufferingStore(
	bufferSize int,
	maxInFlight int,
	wrapped Store) Store {
	s := &bufferingStore{
		wrapped:             wrapped,
		maxBytesBuffered:    bufferSize,
		maxRequestsInFlight: maxInFlight,
		inFlight:            make(map[Score]int),
	}

	s.mu = syncutil.NewInvariantMutex(s.checkInvariants)
	s.inFlightChanged.L = &s.mu

	return s
}

type kvBasedBlobStore struct {
	/////////////////////////
	// Dependencies
	/////////////////////////

	kvStore kv.Store

	/////////////////////////
	// Constant data
	/////////////////////////

	keyPrefix           string
	maxBytesBuffered    int
	maxRequestsInFlight int

	/////////////////////////
	// Mutable state
	/////////////////////////

	mu syncutil.InvariantMutex

	// If any background write to the KV store has failed, an error that should
	// be returned for all future calls to Store or Flush.
	//
	// GUARDED_BY(mu)
	writeErr error

	// A map from scores that have been accepted by Store but not yet
	// successfully written out to the length of the corresponding blobs.
	//
	// INVARIANT: len(inFlight) <= maxRequestsInFlight
	//
	// GUARDED_BY(mu)
	inFlight map[Score]int

	// A cached sum of the lengths of in-flight scores.
	//
	// INVARIANT: bytesBuffered is the sum of all values of inFlight
	// INVARIANT: 0 <= bytesBuffered <= maxBytesBuffered
	//
	// GUARDED_BY(mu)
	bytesBuffered int

	// Signalled when the contents of inFlight change.
	inFlightChanged sync.Cond
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func (s *kvBasedBlobStore) checkInvariants() {
	// INVARIANT: len(inFlight) <= maxRequestsInFlight
	if !(len(s.inFlight) <= s.maxRequestsInFlight) {
		panic(fmt.Sprintf("Too many in flight: %v", len(s.inFlight)))
	}

	// INVARIANT: bytesBuffered is the sum of all values of inFlight
	sum := 0
	for _, v := range s.inFlight {
		sum += v
	}

	if sum != s.bytesBuffered {
		panic(fmt.Sprintf("Differing sum: %v vs. %v", sum, s.bytesBuffered))
	}

	// INVARIANT: 0 <= bytesBuffered <= maxBytesBuffered
	if !(0 <= s.bytesBuffered && s.bytesBuffered <= s.maxBytesBuffered) {
		panic(fmt.Sprintf(
			"bytesBuffered of %v not in range [0, %v]",
			s.bytesBuffered,
			s.maxBytesBuffered))
	}
}

func (s *kvBasedBlobStore) makeKey(score Score) (key string) {
	key = s.keyPrefix + score.Hex()
	return
}

// LOCKS_REQURED(s.mu)
func (s *kvBasedBlobStore) hasRoom(blobLen int) bool {
	bytesLeft := s.maxBytesBuffered - s.bytesBuffered
	return len(s.inFlight) < s.maxRequestsInFlight && blobLen <= bytesLeft
}

// LOCKS_EXCLUDED(s.mu)
func (s *kvBasedBlobStore) makeDurable(score Score, key string, blob []byte) {
	var err error

	// When we exit, set a write error (if appropriate) and update the in-flight
	// map.
	defer func() {
		s.mu.Lock()
		defer s.mu.Unlock()

		// Is this the first write error?
		if err != nil && s.writeErr == nil {
			s.writeErr = err
		}

		delete(s.inFlight, score)
		s.bytesBuffered -= len(blob)
		s.inFlightChanged.Broadcast()
	}()

	// Attempt to write out the blob.
	err = s.kvStore.Set(key, blob)
	if err != nil {
		err = fmt.Errorf("kvStore.Set: %v", err)
		return
	}

	return
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (s *kvBasedBlobStore) Store(blob []byte) (score Score, err error) {
	// Will this blob ever fit?
	if len(blob) > s.maxBytesBuffered {
		err = fmt.Errorf(
			"%v-byte blob is larger than buffer size of %v",
			len(blob),
			s.maxBytesBuffered)

		return
	}

	// Compute a score and a key for use under the lock below.
	score = ComputeScore(blob)
	key := s.makeKey(score)

	s.mu.Lock()
	defer s.mu.Unlock()

	// Wait until there is room for this request.
	for !s.hasRoom(len(blob)) {
		s.inFlightChanged.Wait()
	}

	// Have we already decided on a final error?
	if s.writeErr != nil {
		err = fmt.Errorf("Error from previous write: %v", s.writeErr)
		return
	}

	// If we already have a request for this score in flight, we can avoid
	// spawning another one. (If the existing one fails, the user will see an
	// error from Flush.)
	if _, ok := s.inFlight[score]; ok {
		return
	}

	// If the KV store already contains the key, we need not do anything further.
	if s.kvStore.Contains(key) {
		return
	}

	// Mark this blob as in flight.
	s.inFlight[score] = len(blob)
	s.bytesBuffered += len(blob)
	s.inFlightChanged.Broadcast()

	// Spawn a goroutine that will make the blob durable in the background.
	go s.makeDurable(score, key, blob)

	return
}

func (s *kvBasedBlobStore) Flush() (err error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Wait until there are no requests in flight.
	for len(s.inFlight) != 0 {
		s.inFlightChanged.Wait()
	}

	err = s.writeErr
	return
}

func (s *kvBasedBlobStore) Contains(score Score) (b bool) {
	s.mu.Lock()
	defer s.mu.Unlock()

	// Do we have an in-flight request?
	if _, ok := s.inFlight[score]; ok {
		b = true
		return
	}

	// Does the key exist in the KV store?
	key := s.makeKey(score)
	if s.kvStore.Contains(key) {
		b = true
		return
	}

	return
}

func (s *kvBasedBlobStore) Load(score Score) (blob []byte, err error) {
	// Choose the appropriate key.
	key := s.makeKey(score)

	// Call the key/value store.
	if blob, err = s.kvStore.Get(key); err != nil {
		err = fmt.Errorf("Get: %v", err)
		return
	}

	return
}
<|endoftext|>


<commit_before>package locator

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/maxbrunsfeld/counterfeiter/model"
)

func GetInterfaceFromFilePath(interfaceName, filePath string) (*model.InterfaceToFake, error) {
	dirPath, err := getDir(filePath)
	if err != nil {
		return nil, err
	}

	importPath, err := importPathForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths, err := vendorPathsForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	return GetInterfaceFromImportPath(interfaceName, importPath, vendorPaths...)
}

func GetInterfaceFromImportPath(interfaceName, importPath string, vendorPaths ...string) (*model.InterfaceToFake, error) {
	dirPath, err := dirPathForImportPath(importPath, vendorPaths)
	if err != nil {
		return nil, err
	}

	packages, err := packagesForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	for _, pkg := range packages {
		iface, file, isFunction, err := findInterface(pkg, interfaceName)
		if err != nil {
			return nil, err
		}

		if iface != nil {
			typesFound := getTypeNames(pkg)
			importSpecs := getImports(file)

			pkgImport := pkg.Name
			if strings.HasSuffix(importPath, pkg.Name) {
				pkgImport = "xyz123"
			}

			var methods []model.Method
			var err error
			switch iface.(type) {
			case *ast.InterfaceType:
				interfaceNode := iface.(*ast.InterfaceType)
				methods, err = methodsForInterface(interfaceNode, importPath, pkgImport, importSpecs, typesFound, vendorPaths)
			case *ast.FuncType:
				funcNode := iface.(*ast.FuncType)
				methods, err = methodsForFunction(funcNode, interfaceName, pkgImport, importSpecs, typesFound)
			default:
				err = fmt.Errorf("cannot generate a counterfeit for a '%T'", iface)
			}

			if err != nil {
				return nil, err
			}

			importSpecs[pkgImport] = &ast.ImportSpec{
				Name: &ast.Ident{Name: pkgImport},
				Path: &ast.BasicLit{
					Kind:  token.STRING,
					Value: strconv.Quote(importPath),
				},
			}

			return &model.InterfaceToFake{
				Name:                   interfaceName,
				Methods:                methods,
				ImportPath:             importPath,
				PackageName:            pkg.Name,
				RepresentedByInterface: !isFunction,
			}, nil
		}
	}

	return nil, fmt.Errorf("Could not find interface '%s'", interfaceName)
}

func getDir(path string) (string, error) {
	stat, err := os.Stat(path)
	if err != nil {
		return "", err
	}

	if !stat.IsDir() {
		path = filepath.Dir(path)
	}

	return path, nil
}

func findImportPath(importSpecs map[string]*ast.ImportSpec, alias string) string {
	if importSpec, ok := importSpecs[alias]; ok {
		return strings.Trim(importSpec.Path.Value, `"`)
	}
	return ""
}

func dirPathForImportPath(importPath string, vendorPaths []string) (string, error) {
	for _, srcPath := range append(vendorPaths, goSourcePaths()...) {
		dirPath := filepath.Join(srcPath, filepath.Clean(importPath))
		stat, err := os.Stat(dirPath)
		if err == nil && stat.IsDir() {
			return dirPath, nil
		}
	}

	return "", fmt.Errorf("Package '%s' not found on GOPATH", importPath)
}

func importPathForDirPath(sourcePath string) (string, error) {
	sourcePath, err := filepath.Abs(sourcePath)
	if err != nil {
		return "", err
	}

	for _, goSrcPath := range goSourcePaths() {
		if strings.HasPrefix(sourcePath, goSrcPath) {
			return sourcePath[len(goSrcPath)+1:], nil
		}
	}

	return "", fmt.Errorf("Path '%s' is not on GOPATH", sourcePath)
}

func vendorPathsForDirPath(dirPath string) ([]string, error) {
	dirPath, err := filepath.Abs(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths := []string{}
	for _, goSrcPath := range goSourcePaths() {
		for strings.HasPrefix(dirPath, goSrcPath) {
			vendorPath := filepath.Join(dirPath, "vendor")
			stat, err := os.Stat(vendorPath)
			if err == nil && stat.IsDir() {
				vendorPaths = append(vendorPaths, vendorPath)
			}
			dirPath = filepath.Dir(dirPath)
		}
	}

	return vendorPaths, nil
}

func goSourcePaths() []string {
	result := []string{}
	separator := string(os.PathListSeparator)
	for _, path := range strings.Split(os.Getenv("GOPATH"), separator) {
		result = append(result, filepath.Join(path, "src"))
	}
	result = append(result, filepath.Join(runtime.GOROOT(), "src"))
	return result
}

func packagesForDirPath(path string) (map[string]*ast.Package, error) {
	return parser.ParseDir(token.NewFileSet(), path, nil, parser.AllErrors)
}

func findInterface(pkg *ast.Package, interfaceName string) (ast.Node, *ast.File, bool, error) {
	var file *ast.File
	var iface ast.Node
	var err error
	var isFunction bool

	for _, f := range pkg.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			typeSpec, ok := node.(*ast.TypeSpec)
			if !ok || typeSpec.Name.Name != interfaceName {
				return true
			}

			switch typeSpec.Type.(type) {
			case *ast.InterfaceType:
				file = f
				iface = typeSpec.Type
			case *ast.FuncType:
				file = f
				isFunction = true
				iface = typeSpec.Type
			default:
				err = fmt.Errorf("Name '%s' does not refer to an interface", interfaceName)
			}
			return false
		})

		if iface != nil {
			break
		}
	}

	return iface, file, isFunction, err
}

func getImports(file *ast.File) map[string]*ast.ImportSpec {
	result := map[string]*ast.ImportSpec{}
	ast.Inspect(file, func(node ast.Node) bool {
		if importSpec, ok := node.(*ast.ImportSpec); ok {
			var alias string
			if importSpec.Name == nil {
				alias = path.Base(strings.Trim(importSpec.Path.Value, `"`))
			} else {
				alias = importSpec.Name.Name
			}
			result[alias] = importSpec
		}
		return true
	})
	return result
}

func getTypeNames(pkg *ast.Package) map[string]bool {
	result := make(map[string]bool)
	ast.Inspect(pkg, func(node ast.Node) bool {
		if typeSpec, ok := node.(*ast.TypeSpec); ok {
			if typeSpec.Name != nil {
				result[typeSpec.Name.Name] = true
			}
		}
		return true
	})
	return result
}
<commit_msg>Use prop unix slashes in package paths<commit_after>package locator

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"path"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"

	"github.com/maxbrunsfeld/counterfeiter/model"
)

func GetInterfaceFromFilePath(interfaceName, filePath string) (*model.InterfaceToFake, error) {
	dirPath, err := getDir(filePath)
	if err != nil {
		return nil, err
	}

	importPath, err := importPathForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths, err := vendorPathsForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	return GetInterfaceFromImportPath(interfaceName, importPath, vendorPaths...)
}

func GetInterfaceFromImportPath(interfaceName, importPath string, vendorPaths ...string) (*model.InterfaceToFake, error) {
	dirPath, err := dirPathForImportPath(importPath, vendorPaths)
	if err != nil {
		return nil, err
	}

	packages, err := packagesForDirPath(dirPath)
	if err != nil {
		return nil, err
	}

	for _, pkg := range packages {
		iface, file, isFunction, err := findInterface(pkg, interfaceName)
		if err != nil {
			return nil, err
		}

		if iface != nil {
			typesFound := getTypeNames(pkg)
			importSpecs := getImports(file)

			pkgImport := pkg.Name
			if strings.HasSuffix(importPath, pkg.Name) {
				pkgImport = "xyz123"
			}

			var methods []model.Method
			var err error
			switch iface.(type) {
			case *ast.InterfaceType:
				interfaceNode := iface.(*ast.InterfaceType)
				methods, err = methodsForInterface(interfaceNode, importPath, pkgImport, importSpecs, typesFound, vendorPaths)
			case *ast.FuncType:
				funcNode := iface.(*ast.FuncType)
				methods, err = methodsForFunction(funcNode, interfaceName, pkgImport, importSpecs, typesFound)
			default:
				err = fmt.Errorf("cannot generate a counterfeit for a '%T'", iface)
			}

			if err != nil {
				return nil, err
			}

			importSpecs[pkgImport] = &ast.ImportSpec{
				Name: &ast.Ident{Name: pkgImport},
				Path: &ast.BasicLit{
					Kind:  token.STRING,
					Value: strconv.Quote(importPath),
				},
			}

			return &model.InterfaceToFake{
				Name:                   interfaceName,
				Methods:                methods,
				ImportPath:             importPath,
				PackageName:            pkg.Name,
				RepresentedByInterface: !isFunction,
			}, nil
		}
	}

	return nil, fmt.Errorf("Could not find interface '%s'", interfaceName)
}

func getDir(path string) (string, error) {
	stat, err := os.Stat(path)
	if err != nil {
		return "", err
	}

	if !stat.IsDir() {
		path = filepath.Dir(path)
	}

	return path, nil
}

func findImportPath(importSpecs map[string]*ast.ImportSpec, alias string) string {
	if importSpec, ok := importSpecs[alias]; ok {
		return strings.Trim(importSpec.Path.Value, `"`)
	}
	return ""
}

func dirPathForImportPath(importPath string, vendorPaths []string) (string, error) {
	for _, srcPath := range append(vendorPaths, goSourcePaths()...) {
		dirPath := filepath.Join(srcPath, filepath.Clean(importPath))
		stat, err := os.Stat(dirPath)
		if err == nil && stat.IsDir() {
			return dirPath, nil
		}
	}

	return "", fmt.Errorf("Package '%s' not found on GOPATH", importPath)
}

func importPathForDirPath(sourcePath string) (string, error) {
	sourcePath, err := filepath.Abs(sourcePath)
	if err != nil {
		return "", err
	}

	for _, goSrcPath := range goSourcePaths() {
		if strings.HasPrefix(sourcePath, goSrcPath) {
			return filepath.ToSlash(sourcePath[len(goSrcPath)+1:]), nil
		}
	}

	return "", fmt.Errorf("Path '%s' is not on GOPATH", sourcePath)
}

func vendorPathsForDirPath(dirPath string) ([]string, error) {
	dirPath, err := filepath.Abs(dirPath)
	if err != nil {
		return nil, err
	}

	vendorPaths := []string{}
	for _, goSrcPath := range goSourcePaths() {
		for strings.HasPrefix(dirPath, goSrcPath) {
			vendorPath := filepath.Join(dirPath, "vendor")
			stat, err := os.Stat(vendorPath)
			if err == nil && stat.IsDir() {
				vendorPaths = append(vendorPaths, vendorPath)
			}
			dirPath = filepath.Dir(dirPath)
		}
	}

	return vendorPaths, nil
}

func goSourcePaths() []string {
	result := []string{}
	separator := string(os.PathListSeparator)
	for _, path := range strings.Split(os.Getenv("GOPATH"), separator) {
		result = append(result, filepath.Join(path, "src"))
	}
	result = append(result, filepath.Join(runtime.GOROOT(), "src"))
	return result
}

func packagesForDirPath(path string) (map[string]*ast.Package, error) {
	return parser.ParseDir(token.NewFileSet(), path, nil, parser.AllErrors)
}

func findInterface(pkg *ast.Package, interfaceName string) (ast.Node, *ast.File, bool, error) {
	var file *ast.File
	var iface ast.Node
	var err error
	var isFunction bool

	for _, f := range pkg.Files {
		ast.Inspect(f, func(node ast.Node) bool {
			typeSpec, ok := node.(*ast.TypeSpec)
			if !ok || typeSpec.Name.Name != interfaceName {
				return true
			}

			switch typeSpec.Type.(type) {
			case *ast.InterfaceType:
				file = f
				iface = typeSpec.Type
			case *ast.FuncType:
				file = f
				isFunction = true
				iface = typeSpec.Type
			default:
				err = fmt.Errorf("Name '%s' does not refer to an interface", interfaceName)
			}
			return false
		})

		if iface != nil {
			break
		}
	}

	return iface, file, isFunction, err
}

func getImports(file *ast.File) map[string]*ast.ImportSpec {
	result := map[string]*ast.ImportSpec{}
	ast.Inspect(file, func(node ast.Node) bool {
		if importSpec, ok := node.(*ast.ImportSpec); ok {
			var alias string
			if importSpec.Name == nil {
				alias = path.Base(strings.Trim(importSpec.Path.Value, `"`))
			} else {
				alias = importSpec.Name.Name
			}
			result[alias] = importSpec
		}
		return true
	})
	return result
}

func getTypeNames(pkg *ast.Package) map[string]bool {
	result := make(map[string]bool)
	ast.Inspect(pkg, func(node ast.Node) bool {
		if typeSpec, ok := node.(*ast.TypeSpec); ok {
			if typeSpec.Name != nil {
				result[typeSpec.Name.Name] = true
			}
		}
		return true
	})
	return result
}
<|endoftext|>


<commit_before>package log

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"testing"
)

type DummyLogger struct {
	DebugFunc  func(args ...interface{})
	DebugfFunc func(format string, args ...interface{})

	InfoFunc  func(args ...interface{})
	InfofFunc func(format string, args ...interface{})

	WarnFunc  func(args ...interface{})
	WarnfFunc func(format string, args ...interface{})

	ErrorFunc  func(args ...interface{})
	ErrorfFunc func(format string, args ...interface{})
}

func (l *DummyLogger) Debug(args ...interface{}) {
	l.DebugFunc(args...)
}

func (l *DummyLogger) Debugf(format string, args ...interface{}) {
	l.DebugfFunc(format, args...)
}

func (l *DummyLogger) Info(args ...interface{}) {
	l.InfoFunc(args...)
}

func (l *DummyLogger) Infof(format string, args ...interface{}) {
	l.InfofFunc(format, args...)
}

func (l *DummyLogger) Warn(args ...interface{}) {
	l.WarnFunc(args...)
}

func (l *DummyLogger) Warnf(format string, args ...interface{}) {
	l.WarnfFunc(format, args...)
}

func (l *DummyLogger) Error(args ...interface{}) {
	l.ErrorFunc(args...)
}

func (l *DummyLogger) Errorf(format string, args ...interface{}) {
	l.ErrorfFunc(format, args...)
}

func TestLevel_String(t *testing.T) {
	testSets := []struct {
		level Level
		str   string
	}{
		{
			DebugLevel,
			"DEBUG",
		},
		{
			InfoLevel,
			"INFO",
		},
		{
			WarnLevel,
			"WARN",
		},
		{
			ErrorLevel,
			"ERROR",
		},
	}

	for i, set := range testSets {
		if set.level.String() != set.str {
			t.Errorf("Expected string value is not returned on test #%d: %s.", i, set.level.String())
		}
	}
}

func Test_newDefaultLogger(t *testing.T) {
	l := newDefaultLogger()

	if l == nil {
		t.Fatal("Instance of defaultLogger is not returned.")
	}

	if _, ok := l.(*defaultLogger); !ok {
		t.Fatalf("Returned instance is not defaultLogger type: %#v.", l)
	}
}

func TestNewWithStandardLogger(t *testing.T) {
	standardLogger := log.New(bytes.NewBuffer([]byte{}), "", 0)
	l := NewWithStandardLogger(standardLogger)

	if l == nil {
		t.Fatal("Instance of defaultLogger is not returned.")
	}

	if l.(*defaultLogger).logger != standardLogger {
		t.Fatal("Given standard logger is not set.")
	}
}

func TestEachLevel(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	impl := logger.(*defaultLogger)
	old := impl.logger
	impl.logger = log.New(b, "", 0)
	defer func() {
		impl.logger = old
	}()

	testSets := []struct {
		level   Level
		logFunc func(args ...interface{})
	}{
		{
			level:   DebugLevel,
			logFunc: logger.Debug,
		},
		{
			level:   InfoLevel,
			logFunc: logger.Info,
		},
		{
			level:   WarnLevel,
			logFunc: logger.Warn,
		},
		{
			level:   ErrorLevel,
			logFunc: logger.Error,
		},
	}

	for i, test := range testSets {
		_, _ = io.Copy(ioutil.Discard, b) // make sure the buffer is reset before each test
		input := "test"
		test.logFunc(input, i)
		expected := fmt.Sprintf("[%s] %s %d\n", test.level.String(), input, i)
		if expected != b.String() {
			t.Errorf("expected logging output is not given: %s", b.String())
		}
	}
}

func TestEachLevelWithFormat(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	impl := logger.(*defaultLogger)
	old := impl.logger
	impl.logger = log.New(b, "", 0)
	defer func() {
		impl.logger = old
	}()

	testSets := []struct {
		level   Level
		logFunc func(string, ...interface{})
	}{
		{
			level:   DebugLevel,
			logFunc: logger.Debugf,
		},
		{
			level:   InfoLevel,
			logFunc: logger.Infof,
		},
		{
			level:   WarnLevel,
			logFunc: logger.Warnf,
		},
		{
			level:   ErrorLevel,
			logFunc: logger.Errorf,
		},
	}

	for i, test := range testSets {
		_, _ = io.Copy(ioutil.Discard, b) // make sure the buffer is reset before each test
		input := "test"
		format := "%d : %s"
		test.logFunc(format, i, input)
		expected := fmt.Sprintf("[%s] %s\n", test.level, fmt.Sprintf(format, i, input))
		if expected != b.String() {
			t.Errorf("expected logging output is not given: %s", b.String())
		}
	}
}

func TestSetLogger(t *testing.T) {
	impl := logger.(*defaultLogger)
	old := impl.logger
	defer func() {
		impl.logger = old
	}()

	newLogger := &DummyLogger{}
	SetLogger(newLogger)

	if logger != newLogger {
		t.Errorf("assigned logger is not set: %#v.", logger)
	}
}
<commit_msg>Add missing tests<commit_after>package log

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"testing"
)

type DummyLogger struct {
	DebugFunc  func(args ...interface{})
	DebugfFunc func(format string, args ...interface{})

	InfoFunc  func(args ...interface{})
	InfofFunc func(format string, args ...interface{})

	WarnFunc  func(args ...interface{})
	WarnfFunc func(format string, args ...interface{})

	ErrorFunc  func(args ...interface{})
	ErrorfFunc func(format string, args ...interface{})
}

func (l *DummyLogger) Debug(args ...interface{}) {
	l.DebugFunc(args...)
}

func (l *DummyLogger) Debugf(format string, args ...interface{}) {
	l.DebugfFunc(format, args...)
}

func (l *DummyLogger) Info(args ...interface{}) {
	l.InfoFunc(args...)
}

func (l *DummyLogger) Infof(format string, args ...interface{}) {
	l.InfofFunc(format, args...)
}

func (l *DummyLogger) Warn(args ...interface{}) {
	l.WarnFunc(args...)
}

func (l *DummyLogger) Warnf(format string, args ...interface{}) {
	l.WarnfFunc(format, args...)
}

func (l *DummyLogger) Error(args ...interface{}) {
	l.ErrorFunc(args...)
}

func (l *DummyLogger) Errorf(format string, args ...interface{}) {
	l.ErrorfFunc(format, args...)
}

func TestLevel_String(t *testing.T) {
	testSets := []struct {
		level Level
		str   string
	}{
		{
			DebugLevel,
			"DEBUG",
		},
		{
			InfoLevel,
			"INFO",
		},
		{
			WarnLevel,
			"WARN",
		},
		{
			ErrorLevel,
			"ERROR",
		},
	}

	for i, set := range testSets {
		if set.level.String() != set.str {
			t.Errorf("Expected string value is not returned on test #%d: %s.", i, set.level.String())
		}
	}
}

func Test_newDefaultLogger(t *testing.T) {
	l := newDefaultLogger()

	if l == nil {
		t.Fatal("Instance of defaultLogger is not returned.")
	}

	if _, ok := l.(*defaultLogger); !ok {
		t.Fatalf("Returned instance is not defaultLogger type: %#v.", l)
	}
}

func TestNewWithStandardLogger(t *testing.T) {
	standardLogger := log.New(bytes.NewBuffer([]byte{}), "", 0)
	l := NewWithStandardLogger(standardLogger)

	if l == nil {
		t.Fatal("Instance of defaultLogger is not returned.")
	}

	if l.(*defaultLogger).logger != standardLogger {
		t.Fatal("Given standard logger is not set.")
	}
}

func TestEachLevel(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	impl := logger.(*defaultLogger)
	old := impl.logger
	impl.logger = log.New(b, "", 0)
	defer func() {
		impl.logger = old
	}()

	testSets := []struct {
		level   Level
		logFunc func(args ...interface{})
	}{
		// Access via logger instance
		{
			level:   DebugLevel,
			logFunc: logger.Debug,
		},
		{
			level:   InfoLevel,
			logFunc: logger.Info,
		},
		{
			level:   WarnLevel,
			logFunc: logger.Warn,
		},
		{
			level:   ErrorLevel,
			logFunc: logger.Error,
		},

		// Access to pre-set logger statically
		{
			level:   DebugLevel,
			logFunc: Debug,
		},
		{
			level:   InfoLevel,
			logFunc: Info,
		},
		{
			level:   WarnLevel,
			logFunc: Warn,
		},
		{
			level:   ErrorLevel,
			logFunc: Error,
		},
	}

	for i, test := range testSets {
		_, _ = io.Copy(ioutil.Discard, b) // make sure the buffer is reset before each test
		input := "test"
		test.logFunc(input, i)
		expected := fmt.Sprintf("[%s] %s %d\n", test.level.String(), input, i)
		if expected != b.String() {
			t.Errorf("Expected logging output is not given: %s", b.String())
		}
	}
}

func TestEachLevelWithFormat(t *testing.T) {
	b := bytes.NewBuffer([]byte{})
	impl := logger.(*defaultLogger)
	old := impl.logger
	impl.logger = log.New(b, "", 0)
	defer func() {
		impl.logger = old
	}()

	testSets := []struct {
		level   Level
		logFunc func(string, ...interface{})
	}{
		// Access via logger instance
		{
			level:   DebugLevel,
			logFunc: logger.Debugf,
		},
		{
			level:   InfoLevel,
			logFunc: logger.Infof,
		},
		{
			level:   WarnLevel,
			logFunc: logger.Warnf,
		},
		{
			level:   ErrorLevel,
			logFunc: logger.Errorf,
		},

		// Access to pre-set logger statically
		{
			level:   DebugLevel,
			logFunc: Debugf,
		},
		{
			level:   InfoLevel,
			logFunc: Infof,
		},
		{
			level:   WarnLevel,
			logFunc: Warnf,
		},
		{
			level:   ErrorLevel,
			logFunc: Errorf,
		},
	}

	for i, test := range testSets {
		_, _ = io.Copy(ioutil.Discard, b) // make sure the buffer is reset before each test
		input := "test"
		format := "%d : %s"
		test.logFunc(format, i, input)
		expected := fmt.Sprintf("[%s] %s\n", test.level, fmt.Sprintf(format, i, input))
		if expected != b.String() {
			t.Errorf("Expected logging output is not given: %s", b.String())
		}
	}
}

func TestSetLogger(t *testing.T) {
	impl := logger.(*defaultLogger)
	old := impl.logger
	defer func() {
		impl.logger = old
	}()

	newLogger := &DummyLogger{}
	SetLogger(newLogger)

	if logger != newLogger {
		t.Errorf("Assigned logger is not set: %#v.", logger)
	}
}
<|endoftext|>


<commit_before>package decor

import (
	"fmt"
	"math"
	"time"

	"github.com/VividCortex/ewma"
	"github.com/vbauerster/mpb/internal"
)

type TimeNormalizer func(time.Duration) time.Duration

// EwmaETA exponential-weighted-moving-average based ETA decorator.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`age` is the previous N samples to average over.
//
//	`wcc` optional WC config
func EwmaETA(style int, age float64, wcc ...WC) Decorator {
	return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...)
}

// MovingAverageETA decorator relies on MovingAverage implementation to calculate its average.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`average` available implementations of MovingAverage [ewma.MovingAverage|NewMedian|NewMedianEwma]
//
//	`normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer]
//
//	`wcc` optional WC config
func MovingAverageETA(style int, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator {
	var wc WC
	for _, widthConf := range wcc {
		wc = widthConf
	}
	wc.Init()
	d := &movingAverageETA{
		WC:         wc,
		style:      style,
		average:    average,
		normalizer: normalizer,
	}
	return d
}

type movingAverageETA struct {
	WC
	style       int
	average     ewma.MovingAverage
	completeMsg *string
	normalizer  TimeNormalizer
}

func (d *movingAverageETA) Decor(st *Statistics) string {
	if st.Completed && d.completeMsg != nil {
		return d.FormatMsg(*d.completeMsg)
	}

	v := internal.Round(d.average.Value())
	remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v)))
	hours := int64((remaining / time.Hour) % 60)
	minutes := int64((remaining / time.Minute) % 60)
	seconds := int64((remaining / time.Second) % 60)

	var str string
	switch d.style {
	case ET_STYLE_GO:
		str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second)
	case ET_STYLE_HHMMSS:
		str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
	case ET_STYLE_HHMM:
		str = fmt.Sprintf("%02d:%02d", hours, minutes)
	case ET_STYLE_MMSS:
		str = fmt.Sprintf("%02d:%02d", minutes, seconds)
	}

	return d.FormatMsg(str)
}

func (d *movingAverageETA) NextAmount(n int, wdd ...time.Duration) {
	var workDuration time.Duration
	for _, wd := range wdd {
		workDuration = wd
	}
	lastItemEstimate := float64(workDuration) / float64(n)
	if math.IsInf(lastItemEstimate, 0) || math.IsNaN(lastItemEstimate) {
		return
	}
	d.average.Add(lastItemEstimate)
}

func (d *movingAverageETA) OnCompleteMessage(msg string) {
	d.completeMsg = &msg
}

// AverageETA decorator.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`wcc` optional WC config
func AverageETA(style int, wcc ...WC) Decorator {
	var wc WC
	for _, widthConf := range wcc {
		wc = widthConf
	}
	wc.Init()
	d := &averageETA{
		WC:        wc,
		style:     style,
		startTime: time.Now(),
	}
	return d
}

type averageETA struct {
	WC
	style       int
	startTime   time.Time
	completeMsg *string
}

func (d *averageETA) Decor(st *Statistics) string {
	if st.Completed && d.completeMsg != nil {
		return d.FormatMsg(*d.completeMsg)
	}

	var str string
	timeElapsed := time.Since(d.startTime)
	v := internal.Round(float64(timeElapsed) / float64(st.Current))
	if math.IsInf(v, 0) || math.IsNaN(v) {
		v = 0
	}
	remaining := time.Duration((st.Total - st.Current) * int64(v))
	hours := int64((remaining / time.Hour) % 60)
	minutes := int64((remaining / time.Minute) % 60)
	seconds := int64((remaining / time.Second) % 60)

	switch d.style {
	case ET_STYLE_GO:
		str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second)
	case ET_STYLE_HHMMSS:
		str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
	case ET_STYLE_HHMM:
		str = fmt.Sprintf("%02d:%02d", hours, minutes)
	case ET_STYLE_MMSS:
		str = fmt.Sprintf("%02d:%02d", minutes, seconds)
	}

	return d.FormatMsg(str)
}

func (d *averageETA) OnCompleteMessage(msg string) {
	d.completeMsg = &msg
}

func MaxTolerateTimeNormalizer(maxTolerate time.Duration) TimeNormalizer {
	var normalized time.Duration
	var lastCall time.Time
	return func(remaining time.Duration) time.Duration {
		if diff := normalized - remaining; diff <= 0 || diff > maxTolerate || remaining < maxTolerate/2 {
			normalized = remaining
			lastCall = time.Now()
			return remaining
		}
		normalized -= time.Since(lastCall)
		lastCall = time.Now()
		return normalized
	}
}

func FixedIntervalTimeNormalizer(updInterval int) TimeNormalizer {
	var normalized time.Duration
	var lastCall time.Time
	var count int
	return func(remaining time.Duration) time.Duration {
		if count == 0 || remaining <= time.Duration(15*time.Second) {
			count = updInterval
			normalized = remaining
			lastCall = time.Now()
			return remaining
		}
		count--
		normalized -= time.Since(lastCall)
		lastCall = time.Now()
		if normalized > 0 {
			return normalized
		}
		return remaining
	}
}

func NopNormalizer() TimeNormalizer {
	return func(remaining time.Duration) time.Duration {
		return remaining
	}
}
<commit_msg>adaptive ET_STYLE_MMSS<commit_after>package decor

import (
	"fmt"
	"math"
	"time"

	"github.com/VividCortex/ewma"
	"github.com/vbauerster/mpb/internal"
)

type TimeNormalizer func(time.Duration) time.Duration

// EwmaETA exponential-weighted-moving-average based ETA decorator.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`age` is the previous N samples to average over.
//
//	`wcc` optional WC config
func EwmaETA(style int, age float64, wcc ...WC) Decorator {
	return MovingAverageETA(style, ewma.NewMovingAverage(age), NopNormalizer(), wcc...)
}

// MovingAverageETA decorator relies on MovingAverage implementation to calculate its average.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`average` available implementations of MovingAverage [ewma.MovingAverage|NewMedian|NewMedianEwma]
//
//	`normalizer` available implementations are [NopNormalizer|FixedIntervalTimeNormalizer|MaxTolerateTimeNormalizer]
//
//	`wcc` optional WC config
func MovingAverageETA(style int, average MovingAverage, normalizer TimeNormalizer, wcc ...WC) Decorator {
	var wc WC
	for _, widthConf := range wcc {
		wc = widthConf
	}
	wc.Init()
	d := &movingAverageETA{
		WC:         wc,
		style:      style,
		average:    average,
		normalizer: normalizer,
	}
	return d
}

type movingAverageETA struct {
	WC
	style       int
	average     ewma.MovingAverage
	completeMsg *string
	normalizer  TimeNormalizer
}

func (d *movingAverageETA) Decor(st *Statistics) string {
	if st.Completed && d.completeMsg != nil {
		return d.FormatMsg(*d.completeMsg)
	}

	v := internal.Round(d.average.Value())
	remaining := d.normalizer(time.Duration((st.Total - st.Current) * int64(v)))
	hours := int64((remaining / time.Hour) % 60)
	minutes := int64((remaining / time.Minute) % 60)
	seconds := int64((remaining / time.Second) % 60)

	var str string
	switch d.style {
	case ET_STYLE_GO:
		str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second)
	case ET_STYLE_HHMMSS:
		str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
	case ET_STYLE_HHMM:
		str = fmt.Sprintf("%02d:%02d", hours, minutes)
	case ET_STYLE_MMSS:
		if hours > 0 {
			str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
		} else {
			str = fmt.Sprintf("%02d:%02d", minutes, seconds)
		}
	}

	return d.FormatMsg(str)
}

func (d *movingAverageETA) NextAmount(n int, wdd ...time.Duration) {
	var workDuration time.Duration
	for _, wd := range wdd {
		workDuration = wd
	}
	lastItemEstimate := float64(workDuration) / float64(n)
	if math.IsInf(lastItemEstimate, 0) || math.IsNaN(lastItemEstimate) {
		return
	}
	d.average.Add(lastItemEstimate)
}

func (d *movingAverageETA) OnCompleteMessage(msg string) {
	d.completeMsg = &msg
}

// AverageETA decorator.
//
//	`style` one of [ET_STYLE_GO|ET_STYLE_HHMMSS|ET_STYLE_HHMM|ET_STYLE_MMSS]
//
//	`wcc` optional WC config
func AverageETA(style int, wcc ...WC) Decorator {
	var wc WC
	for _, widthConf := range wcc {
		wc = widthConf
	}
	wc.Init()
	d := &averageETA{
		WC:        wc,
		style:     style,
		startTime: time.Now(),
	}
	return d
}

type averageETA struct {
	WC
	style       int
	startTime   time.Time
	completeMsg *string
}

func (d *averageETA) Decor(st *Statistics) string {
	if st.Completed && d.completeMsg != nil {
		return d.FormatMsg(*d.completeMsg)
	}

	var str string
	timeElapsed := time.Since(d.startTime)
	v := internal.Round(float64(timeElapsed) / float64(st.Current))
	if math.IsInf(v, 0) || math.IsNaN(v) {
		v = 0
	}
	remaining := time.Duration((st.Total - st.Current) * int64(v))
	hours := int64((remaining / time.Hour) % 60)
	minutes := int64((remaining / time.Minute) % 60)
	seconds := int64((remaining / time.Second) % 60)

	switch d.style {
	case ET_STYLE_GO:
		str = fmt.Sprint(time.Duration(remaining.Seconds()) * time.Second)
	case ET_STYLE_HHMMSS:
		str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
	case ET_STYLE_HHMM:
		str = fmt.Sprintf("%02d:%02d", hours, minutes)
	case ET_STYLE_MMSS:
		if hours > 0 {
			str = fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
		} else {
			str = fmt.Sprintf("%02d:%02d", minutes, seconds)
		}
	}

	return d.FormatMsg(str)
}

func (d *averageETA) OnCompleteMessage(msg string) {
	d.completeMsg = &msg
}

func MaxTolerateTimeNormalizer(maxTolerate time.Duration) TimeNormalizer {
	var normalized time.Duration
	var lastCall time.Time
	return func(remaining time.Duration) time.Duration {
		if diff := normalized - remaining; diff <= 0 || diff > maxTolerate || remaining < maxTolerate/2 {
			normalized = remaining
			lastCall = time.Now()
			return remaining
		}
		normalized -= time.Since(lastCall)
		lastCall = time.Now()
		return normalized
	}
}

func FixedIntervalTimeNormalizer(updInterval int) TimeNormalizer {
	var normalized time.Duration
	var lastCall time.Time
	var count int
	return func(remaining time.Duration) time.Duration {
		if count == 0 || remaining <= time.Duration(15*time.Second) {
			count = updInterval
			normalized = remaining
			lastCall = time.Now()
			return remaining
		}
		count--
		normalized -= time.Since(lastCall)
		lastCall = time.Now()
		if normalized > 0 {
			return normalized
		}
		return remaining
	}
}

func NopNormalizer() TimeNormalizer {
	return func(remaining time.Duration) time.Duration {
		return remaining
	}
}
<|endoftext|>


<commit_before>package service

import (
	"strconv"

	log "github.com/sirupsen/logrus"
	"github.com/godbus/dbus"
	"github.com/godbus/dbus/introspect"
	"github.com/godbus/dbus/prop"
	"github.com/muka/go-bluetooth/bluez"
	"github.com/muka/go-bluetooth/bluez/profile"
)

// NewGattCharacteristic1 create a new GattCharacteristic1 client
func NewGattCharacteristic1(config *GattCharacteristic1Config, props *profile.GattCharacteristic1Properties) (*GattCharacteristic1, error) {

	propInterface, err := NewProperties(config.conn)
	if err != nil {
		return nil, err
	}

	s := &GattCharacteristic1{
		config:              config,
		properties:          props,
		PropertiesInterface: propInterface,
		descriptors:         make(map[dbus.ObjectPath]*GattDescriptor1),
	}

	err = propInterface.AddProperties(s.Interface(), props)
	if err != nil {
		return nil, err
	}

	return s, nil
}

//GattCharacteristic1Config GattCharacteristic1 configuration
type GattCharacteristic1Config struct {
	objectPath dbus.ObjectPath
	service    *GattService1
	ID         int
	conn       *dbus.Conn
}

// GattCharacteristic1 client
type GattCharacteristic1 struct {
	config              *GattCharacteristic1Config
	properties          *profile.GattCharacteristic1Properties
	PropertiesInterface *Properties
	descriptors         map[dbus.ObjectPath]*GattDescriptor1
	descIndex           int
	notifying           bool
}

//Interface return the dbus interface name
func (s *GattCharacteristic1) Interface() string {
	return bluez.GattCharacteristic1Interface
}

//Path return the object path
func (s *GattCharacteristic1) Path() dbus.ObjectPath {
	return s.config.objectPath
}

//Properties return the properties of the service
func (s *GattCharacteristic1) Properties() map[string]bluez.Properties {
	p := make(map[string]bluez.Properties)
	s.properties.Descriptors = s.GetDescriptorPaths()
	p[s.Interface()] = s.properties
	return p
}

//GetDescriptors return the characteristics of the service
func (s *GattCharacteristic1) GetDescriptors() map[dbus.ObjectPath]*GattDescriptor1 {
	return s.descriptors
}

//GetDescriptorPaths return the characteristics object paths
func (s *GattCharacteristic1) GetDescriptorPaths() []dbus.ObjectPath {
	paths := make([]dbus.ObjectPath, 0)
	for path := range s.descriptors {
		paths = append(paths, path)
	}
	return paths
}

//CreateDescriptor create a new characteristic
func (s *GattCharacteristic1) CreateDescriptor(props *profile.GattDescriptor1Properties) (*GattDescriptor1, error) {
	s.descIndex++
	path := string(s.config.objectPath) + "/desc" + strconv.Itoa(s.descIndex)
	config := &GattDescriptor1Config{
		ID:             s.descIndex,
		objectPath:     dbus.ObjectPath(path),
		conn:           s.config.conn,
		characteristic: s,
	}

	props.Characteristic = config.objectPath

	desc, err := NewGattDescriptor1(config, props)
	return desc, err
}

//AddDescriptor add a characteristic
func (s *GattCharacteristic1) AddDescriptor(desc *GattDescriptor1) error {

	s.descriptors[desc.Path()] = desc

	err := desc.Expose()
	if err != nil {
		return err
	}

	err = s.config.service.GetApp().exportTree()
	if err != nil {
		return err
	}

	om := s.config.service.GetApp().GetObjectManager()
	return om.AddObject(desc.Path(), desc.Properties())
}

//RemoveDescriptor remove a characteristic
func (s *GattCharacteristic1) RemoveDescriptor(char *GattDescriptor1) error {
	if _, ok := s.descriptors[char.Path()]; ok {
		delete(s.descriptors, char.Path())
		om := s.config.service.GetApp().GetObjectManager()
		return om.RemoveObject(char.Path())
	}
	return nil
}

//ReadValue read a value
func (s *GattCharacteristic1) ReadValue(options map[string]interface{}) ([]byte, *dbus.Error) {
	log.Debug("Characteristic.ReadValue")

	b, err := s.config.service.config.app.HandleRead(s.config.service.properties.UUID, s.properties.UUID)

	var dberr *dbus.Error
	if err != nil {
		if err.code == -1 {
			// No registered callback, so we'll just use our stored value
			b = s.properties.Value
		} else {
			dberr = dbus.NewError(err.Error(), nil)
		}
	}

	return b, dberr
}

//WriteValue write a value
func (s *GattCharacteristic1) WriteValue(value []byte, options map[string]interface{}) *dbus.Error {
	log.Debug("Characteristic.WriteValue")

	err := s.config.service.config.app.HandleWrite(s.config.service.properties.UUID, s.properties.UUID, value)

	if err != nil {
		if err.code == -1 {
			// No registered callback, so we'll just store this value
			s.UpdateValue(value)
			return nil
		}
		dberr := dbus.NewError(err.Error(), nil)
		return dberr
	}

	return nil
}

//UpdateValue update a value
func (s *GattCharacteristic1) UpdateValue(value []byte) {
	s.properties.Value = value
	s.PropertiesInterface.Instance().Set(s.Interface(), "Value", dbus.MakeVariant(value))
}

//StartNotify start notification
func (s *GattCharacteristic1) StartNotify() *dbus.Error {
	log.Debug("Characteristic.StartNotify")
	s.notifying = true
	return nil
}

//StopNotify stop notification
func (s *GattCharacteristic1) StopNotify() *dbus.Error {
	log.Debug("Characteristic.StopNotify")
	s.notifying = false
	return nil
}

//Expose the char to dbus
func (s *GattCharacteristic1) Expose() error {

	conn := s.config.conn

	err := conn.Export(s, s.Path(), s.Interface())
	if err != nil {
		return err
	}

	for iface, props := range s.Properties() {
		s.PropertiesInterface.AddProperties(iface, props)
	}

	s.PropertiesInterface.Expose(s.Path())

	node := &introspect.Node{
		Interfaces: []introspect.Interface{
			//Introspect
			introspect.IntrospectData,
			//Properties
			prop.IntrospectData,
			//GattCharacteristic1
			{
				Name:       s.Interface(),
				Methods:    introspect.Methods(s),
				Properties: s.PropertiesInterface.Introspection(s.Interface()),
			},
		},
	}

	err = conn.Export(
		introspect.NewIntrospectable(node),
		s.Path(),
		"org.freedesktop.DBus.Introspectable")
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>Use CallbackNotRegistered instead of -1<commit_after>package service

import (
	"strconv"

	log "github.com/sirupsen/logrus"
	"github.com/godbus/dbus"
	"github.com/godbus/dbus/introspect"
	"github.com/godbus/dbus/prop"
	"github.com/muka/go-bluetooth/bluez"
	"github.com/muka/go-bluetooth/bluez/profile"
)

// NewGattCharacteristic1 create a new GattCharacteristic1 client
func NewGattCharacteristic1(config *GattCharacteristic1Config, props *profile.GattCharacteristic1Properties) (*GattCharacteristic1, error) {

	propInterface, err := NewProperties(config.conn)
	if err != nil {
		return nil, err
	}

	s := &GattCharacteristic1{
		config:              config,
		properties:          props,
		PropertiesInterface: propInterface,
		descriptors:         make(map[dbus.ObjectPath]*GattDescriptor1),
	}

	err = propInterface.AddProperties(s.Interface(), props)
	if err != nil {
		return nil, err
	}

	return s, nil
}

//GattCharacteristic1Config GattCharacteristic1 configuration
type GattCharacteristic1Config struct {
	objectPath dbus.ObjectPath
	service    *GattService1
	ID         int
	conn       *dbus.Conn
}

// GattCharacteristic1 client
type GattCharacteristic1 struct {
	config              *GattCharacteristic1Config
	properties          *profile.GattCharacteristic1Properties
	PropertiesInterface *Properties
	descriptors         map[dbus.ObjectPath]*GattDescriptor1
	descIndex           int
	notifying           bool
}

//Interface return the dbus interface name
func (s *GattCharacteristic1) Interface() string {
	return bluez.GattCharacteristic1Interface
}

//Path return the object path
func (s *GattCharacteristic1) Path() dbus.ObjectPath {
	return s.config.objectPath
}

//Properties return the properties of the service
func (s *GattCharacteristic1) Properties() map[string]bluez.Properties {
	p := make(map[string]bluez.Properties)
	s.properties.Descriptors = s.GetDescriptorPaths()
	p[s.Interface()] = s.properties
	return p
}

//GetDescriptors return the characteristics of the service
func (s *GattCharacteristic1) GetDescriptors() map[dbus.ObjectPath]*GattDescriptor1 {
	return s.descriptors
}

//GetDescriptorPaths return the characteristics object paths
func (s *GattCharacteristic1) GetDescriptorPaths() []dbus.ObjectPath {
	paths := make([]dbus.ObjectPath, 0)
	for path := range s.descriptors {
		paths = append(paths, path)
	}
	return paths
}

//CreateDescriptor create a new characteristic
func (s *GattCharacteristic1) CreateDescriptor(props *profile.GattDescriptor1Properties) (*GattDescriptor1, error) {
	s.descIndex++
	path := string(s.config.objectPath) + "/desc" + strconv.Itoa(s.descIndex)
	config := &GattDescriptor1Config{
		ID:             s.descIndex,
		objectPath:     dbus.ObjectPath(path),
		conn:           s.config.conn,
		characteristic: s,
	}

	props.Characteristic = config.objectPath

	desc, err := NewGattDescriptor1(config, props)
	return desc, err
}

//AddDescriptor add a characteristic
func (s *GattCharacteristic1) AddDescriptor(desc *GattDescriptor1) error {

	s.descriptors[desc.Path()] = desc

	err := desc.Expose()
	if err != nil {
		return err
	}

	err = s.config.service.GetApp().exportTree()
	if err != nil {
		return err
	}

	om := s.config.service.GetApp().GetObjectManager()
	return om.AddObject(desc.Path(), desc.Properties())
}

//RemoveDescriptor remove a characteristic
func (s *GattCharacteristic1) RemoveDescriptor(char *GattDescriptor1) error {
	if _, ok := s.descriptors[char.Path()]; ok {
		delete(s.descriptors, char.Path())
		om := s.config.service.GetApp().GetObjectManager()
		return om.RemoveObject(char.Path())
	}
	return nil
}

//ReadValue read a value
func (s *GattCharacteristic1) ReadValue(options map[string]interface{}) ([]byte, *dbus.Error) {
	log.Debug("Characteristic.ReadValue")

	b, err := s.config.service.config.app.HandleRead(s.config.service.properties.UUID, s.properties.UUID)

	var dberr *dbus.Error
	if err != nil {
		if err.code == CallbackNotRegistered {
			// No registered callback, so we'll just use our stored value
			b = s.properties.Value
		} else {
			dberr = dbus.NewError(err.Error(), nil)
		}
	}

	return b, dberr
}

//WriteValue write a value
func (s *GattCharacteristic1) WriteValue(value []byte, options map[string]interface{}) *dbus.Error {
	log.Debug("Characteristic.WriteValue")

	err := s.config.service.config.app.HandleWrite(s.config.service.properties.UUID, s.properties.UUID, value)

	if err != nil {
		if err.code == CallbackNotRegistered {
			// No registered callback, so we'll just store this value
			s.UpdateValue(value)
			return nil
		}
		dberr := dbus.NewError(err.Error(), nil)
		return dberr
	}

	return nil
}

//UpdateValue update a value
func (s *GattCharacteristic1) UpdateValue(value []byte) {
	s.properties.Value = value
	s.PropertiesInterface.Instance().Set(s.Interface(), "Value", dbus.MakeVariant(value))
}

//StartNotify start notification
func (s *GattCharacteristic1) StartNotify() *dbus.Error {
	log.Debug("Characteristic.StartNotify")
	s.notifying = true
	return nil
}

//StopNotify stop notification
func (s *GattCharacteristic1) StopNotify() *dbus.Error {
	log.Debug("Characteristic.StopNotify")
	s.notifying = false
	return nil
}

//Expose the char to dbus
func (s *GattCharacteristic1) Expose() error {

	conn := s.config.conn

	err := conn.Export(s, s.Path(), s.Interface())
	if err != nil {
		return err
	}

	for iface, props := range s.Properties() {
		s.PropertiesInterface.AddProperties(iface, props)
	}

	s.PropertiesInterface.Expose(s.Path())

	node := &introspect.Node{
		Interfaces: []introspect.Interface{
			//Introspect
			introspect.IntrospectData,
			//Properties
			prop.IntrospectData,
			//GattCharacteristic1
			{
				Name:       s.Interface(),
				Methods:    introspect.Methods(s),
				Properties: s.PropertiesInterface.Introspection(s.Interface()),
			},
		},
	}

	err = conn.Export(
		introspect.NewIntrospectable(node),
		s.Path(),
		"org.freedesktop.DBus.Introspectable")
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

const (
	defaultPort  = 7171
	dataDir      = "notes/"
	templatesDir = "templates/"
	fileExt      = ".txt"
)

var (
	port = flag.Int("port", defaultPort, "Specify the listening port.")
)
var (
	templates = template.Must(template.ParseFiles(
		templatesDir+"index.html",
		templatesDir+"view.html",
		templatesDir+"edit.html",
		templatesDir+"error.html"))
	validPath = regexp.MustCompile(`^/((view|edit|save|styles|error)/([a-zA-Z0-9\.\-_]*))?$`)
)

type Model map[string]interface{}

type Page struct {
	Title string
	Body  string
}

func main() {
	flag.Parse()
	registerHandlers()
	fmt.Println("Running... ( port", *port, ")")
	http.ListenAndServe(":"+strconv.Itoa(*port), nil)
}

func registerHandlers() {
	http.HandleFunc("/", makeHandler(indexHandler))
	http.HandleFunc("/view/", makeHandler(viewHandler))
	http.HandleFunc("/edit/", makeHandler(editHandler))
	http.HandleFunc("/save/", makeHandler(saveHandler))
	http.HandleFunc("/styles/", makeHandler(styleHandler))
	http.HandleFunc("/error/", makeHandler(errorHandler))
}

func (p *Page) save() error {
	filename := p.Title + fileExt
	return ioutil.WriteFile(dataDir+filename, []byte(p.Body), 0660)
}

func (p *Page) load(title string) (*Page, error) {
	filename := title + fileExt
	filename = strings.Replace(filename, "%20", " ", -1)
	body, err := ioutil.ReadFile(dataDir + filename)
	if err != nil {
		return nil, err
	}
	p.Title = title
	p.Body = string(body)
	return p, nil
}

func buildModel(p *Page, asHtml bool) Model {
	b := p.Body
	if asHtml {
		b = strings.Replace(p.Body, "\n", "
", -1)
		link := regexp.MustCompile(`(^| )+([a-zA-Z0-9\-\._]+)\.txt`)
		b = link.ReplaceAllString(b, `$1<a href="view/$2">$2</a>`)
	}
	m := Model{
		"Title": p.Title,
		"Body":  template.HTML(b),
	}
	return m
}

func styleHandler(w http.ResponseWriter, r *http.Request, title string) {
	filename := "styles/" + title
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println("style handler err", err)
		return
	}
	w.Header().Set("content-type", "text/css")
	w.Write(body)
}

func indexHandler(w http.ResponseWriter, req *http.Request, title string) {
	title = "index"
	p, err := new(Page).load(title)
	if err != nil {
		http.Redirect(w, req, "/edit/"+title, http.StatusFound)
		return
	}
	model := buildModel(p, true)
	renderTemplate(w, "index", model)
}

func errorHandler(w http.ResponseWriter, req *http.Request, title string) {
	//title = "error"
	//p := &Page{Title: title}
	p := Page{}
	model := buildModel(p, true)
	renderTemplate(w, "error", model)
}

func viewHandler(w http.ResponseWriter, req *http.Request, title string) {
	if title == "index" {
		http.Redirect(w, req, "/", http.StatusFound)
		return
	}
	p, err := new(Page).load(title)
	if err != nil {
		http.Redirect(w, req, "/edit/"+title, http.StatusFound)
		return
	}
	model := buildModel(p, true)
	renderTemplate(w, "view", model)
}

func editHandler(w http.ResponseWriter, req *http.Request, title string) {
	p, err := new(Page).load(title)
	if err != nil {
		p = &Page{Title: title}
	}
	model := buildModel(p, false)
	renderTemplate(w, "edit", model)
}

func saveHandler(w http.ResponseWriter, req *http.Request, title string) {
	body := req.FormValue("body")
	p := &Page{Title: title, Body: body}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, req, "/view/"+title, http.StatusFound)
}

func renderTemplate(w http.ResponseWriter, tmpl string, model Model) {
	err := templates.ExecuteTemplate(w, tmpl+".html", model)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		pathParts := validPath.FindStringSubmatch(req.URL.Path)
		if pathParts == nil {
			//u := &url.URL{Path: req.URL.Path}
			//encodedPath := u.String()
			http.Redirect(w, req, "/error/", http.StatusFound)
			return
		}
		fn(w, req, pathParts[3])
	}
}
<commit_msg>Ensure notes directory exists at start.<commit_after>package main

import (
	"flag"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
)

const (
	defaultPort  = 7171
	dataDir      = "notes/"
	templatesDir = "templates/"
	fileExt      = ".txt"
)

var (
	port = flag.Int("port", defaultPort, "Specify the listening port.")
)
var (
	templates = template.Must(template.ParseFiles(
		templatesDir+"index.html",
		templatesDir+"view.html",
		templatesDir+"edit.html",
		templatesDir+"error.html"))
	linkPattern  = regexp.MustCompile(`(^| )+([a-zA-Z0-9\-\._]+)\.txt`)
	linkTemplate = `$1<a href="view/$2">$2</a>`
	validPath    = regexp.MustCompile(`^/((view|edit|save|styles|error)/([a-zA-Z0-9\.\-_]*))?$`)
)

type Model map[string]interface{}

type Page struct {
	Title string
	Body  string
}

func main() {
	flag.Parse()
	checkEnvironment()
	registerHandlers()
	fmt.Println("Running... ( port", *port, ")")
	http.ListenAndServe(":"+strconv.Itoa(*port), nil)
}

func checkEnvironment() {
	_, err := os.Stat(dataDir)
	if err != nil {
		if os.IsNotExist(err) {
			err = os.Mkdir(dataDir, 0644)
			if err != nil {
				fmt.Println(`Unable to create "notes" directory:`, err)
				os.Exit(0)
			}
		} else {
			fmt.Println(`Unable to check existence of "notes" directory:`, err)
			os.Exit(0)
		}
	}
}

func registerHandlers() {
	http.HandleFunc("/", makeHandler(indexHandler))
	http.HandleFunc("/view/", makeHandler(viewHandler))
	http.HandleFunc("/edit/", makeHandler(editHandler))
	http.HandleFunc("/save/", makeHandler(saveHandler))
	http.HandleFunc("/styles/", makeHandler(styleHandler))
	http.HandleFunc("/error/", makeHandler(errorHandler))
}

func (p *Page) save() error {
	filename := p.Title + fileExt
	return ioutil.WriteFile(dataDir+filename, []byte(p.Body), 0644)
}

func (p *Page) load(title string) (*Page, error) {
	filename := title + fileExt
	filename = strings.Replace(filename, "%20", " ", -1)
	body, err := ioutil.ReadFile(dataDir + filename)
	if err != nil {
		return nil, err
	}
	p.Title = title
	p.Body = string(body)
	return p, nil
}

func buildModel(p *Page, asHtml bool) Model {
	b := p.Body
	if asHtml {
		b = parseText(b)
	}
	m := Model{
		"Title": p.Title,
		"Body":  template.HTML(b),
	}
	return m
}

func parseText(body string) string {
	b := strings.Replace(body, "\n", "
", -1)
	b = linkPattern.ReplaceAllString(b, linkTemplate)
	return b
}

func styleHandler(w http.ResponseWriter, r *http.Request, title string) {
	filename := "styles/" + title
	body, err := ioutil.ReadFile(filename)
	if err != nil {
		fmt.Println("style handler err", err)
		return
	}
	w.Header().Set("content-type", "text/css")
	w.Write(body)
}

func indexHandler(w http.ResponseWriter, req *http.Request, title string) {
	title = "index"
	p, err := new(Page).load(title)
	if err != nil {
		http.Redirect(w, req, "/edit/"+title, http.StatusFound)
		return
	}
	model := buildModel(p, true)
	renderTemplate(w, "index", model)
}

func errorHandler(w http.ResponseWriter, req *http.Request, title string) {
	//title = "error"
	//p := &Page{Title: title}
	p := &Page{}
	model := buildModel(p, true)
	renderTemplate(w, "error", model)
}

func viewHandler(w http.ResponseWriter, req *http.Request, title string) {
	if title == "index" {
		http.Redirect(w, req, "/", http.StatusFound)
		return
	}
	p, err := new(Page).load(title)
	if err != nil {
		http.Redirect(w, req, "/edit/"+title, http.StatusFound)
		return
	}
	model := buildModel(p, true)
	renderTemplate(w, "view", model)
}

func editHandler(w http.ResponseWriter, req *http.Request, title string) {
	p, err := new(Page).load(title)
	if err != nil {
		p = &Page{Title: title}
	}
	model := buildModel(p, false)
	renderTemplate(w, "edit", model)
}

func saveHandler(w http.ResponseWriter, req *http.Request, title string) {
	body := req.FormValue("body")
	p := &Page{Title: title, Body: body}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, req, "/view/"+title, http.StatusFound)
}

func renderTemplate(w http.ResponseWriter, tmpl string, model Model) {
	err := templates.ExecuteTemplate(w, tmpl+".html", model)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		pathParts := validPath.FindStringSubmatch(req.URL.Path)
		if pathParts == nil {
			//u := &url.URL{Path: req.URL.Path}
			//encodedPath := u.String()
			http.Redirect(w, req, "/error/", http.StatusFound)
			return
		}
		fn(w, req, pathParts[3])
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Google LLC
//
// 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 datastore

import (
	"errors"

	"cloud.google.com/go/internal/trace"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	pb "google.golang.org/genproto/googleapis/datastore/v1"
)

// ErrConcurrentTransaction is returned when a transaction is rolled back due
// to a conflict with a concurrent transaction.
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")

var errExpiredTransaction = errors.New("datastore: transaction expired")

type transactionSettings struct {
	attempts int
	readOnly bool
	prevID   []byte // ID of the transaction to retry
}

// newTransactionSettings creates a transactionSettings with a given TransactionOption slice.
// Unconfigured options will be set to default values.
func newTransactionSettings(opts []TransactionOption) *transactionSettings {
	s := &transactionSettings{attempts: 3}
	for _, o := range opts {
		o.apply(s)
	}
	return s
}

// TransactionOption configures the way a transaction is executed.
type TransactionOption interface {
	apply(*transactionSettings)
}

// MaxAttempts returns a TransactionOption that overrides the default 3 attempt times.
func MaxAttempts(attempts int) TransactionOption {
	return maxAttempts(attempts)
}

type maxAttempts int

func (w maxAttempts) apply(s *transactionSettings) {
	if w > 0 {
		s.attempts = int(w)
	}
}

// ReadOnly is a TransactionOption that marks the transaction as read-only.
var ReadOnly TransactionOption

func init() {
	ReadOnly = readOnly{}
}

type readOnly struct{}

func (readOnly) apply(s *transactionSettings) {
	s.readOnly = true
}

// Transaction represents a set of datastore operations to be committed atomically.
//
// Operations are enqueued by calling the Put and Delete methods on Transaction
// (or their Multi-equivalents).  These operations are only committed when the
// Commit method is invoked. To ensure consistency, reads must be performed by
// using Transaction's Get method or by using the Transaction method when
// building a query.
//
// A Transaction must be committed or rolled back exactly once.
type Transaction struct {
	id        []byte
	client    *Client
	ctx       context.Context
	mutations []*pb.Mutation      // The mutations to apply.
	pending   map[int]*PendingKey // Map from mutation index to incomplete keys pending transaction completion.
}

// NewTransaction starts a new transaction.
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (t *Transaction, err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.NewTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	for _, o := range opts {
		if _, ok := o.(maxAttempts); ok {
			return nil, errors.New("datastore: NewTransaction does not accept MaxAttempts option")
		}
	}
	return c.newTransaction(ctx, newTransactionSettings(opts))
}

func (c *Client) newTransaction(ctx context.Context, s *transactionSettings) (*Transaction, error) {
	req := &pb.BeginTransactionRequest{ProjectId: c.dataset}
	if s.readOnly {
		req.TransactionOptions = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
		}
	} else if s.prevID != nil {
		req.TransactionOptions = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
				PreviousTransaction: s.prevID,
			}},
		}
	}
	resp, err := c.client.BeginTransaction(ctx, req)
	if err != nil {
		return nil, err
	}
	return &Transaction{
		id:        resp.Transaction,
		ctx:       ctx,
		client:    c,
		mutations: nil,
		pending:   make(map[int]*PendingKey),
	}, nil
}

// RunInTransaction runs f in a transaction. f is invoked with a Transaction
// that f should use for all the transaction's datastore operations.
//
// f must not call Commit or Rollback on the provided Transaction.
//
// If f returns nil, RunInTransaction commits the transaction,
// returning the Commit and a nil error if it succeeds. If the commit fails due
// to a conflicting transaction, RunInTransaction retries f with a new
// Transaction. It gives up and returns ErrConcurrentTransaction after three
// failed attempts (or as configured with MaxAttempts).
//
// If f returns non-nil, then the transaction will be rolled back and
// RunInTransaction will return the same error. The function f is not retried.
//
// Note that when f returns, the transaction is not committed. Calling code
// must not assume that any of f's changes have been committed until
// RunInTransaction returns nil.
//
// Since f may be called multiple times, f should usually be idempotent – that
// is, it should have the same result when called multiple times. Note that
// Transaction.Get will append when unmarshalling slice fields, so it is not
// necessarily idempotent.
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (cmt *Commit, err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.RunInTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	settings := newTransactionSettings(opts)
	for n := 0; n < settings.attempts; n++ {
		tx, err := c.newTransaction(ctx, settings)
		if err != nil {
			return nil, err
		}
		if err := f(tx); err != nil {
			_ = tx.Rollback()
			return nil, err
		}
		if cmt, err := tx.Commit(); err != ErrConcurrentTransaction {
			return cmt, err
		}
		// Pass this transaction's ID to the retry transaction to preserve
		// transaction priority.
		if !settings.readOnly {
			settings.prevID = tx.id
		}
	}
	return nil, ErrConcurrentTransaction
}

// Commit applies the enqueued operations atomically.
func (t *Transaction) Commit() (c *Commit, err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Commit")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return nil, errExpiredTransaction
	}
	req := &pb.CommitRequest{
		ProjectId:           t.client.dataset,
		TransactionSelector: &pb.CommitRequest_Transaction{Transaction: t.id},
		Mutations:           t.mutations,
		Mode:                pb.CommitRequest_TRANSACTIONAL,
	}
	t.id = nil
	resp, err := t.client.client.Commit(t.ctx, req)
	if err != nil {
		if grpc.Code(err) == codes.Aborted {
			return nil, ErrConcurrentTransaction
		}
		return nil, err
	}

	// Copy any newly minted keys into the returned keys.
	for i, p := range t.pending {
		if i >= len(resp.MutationResults) || resp.MutationResults[i].Key == nil {
			return nil, errors.New("datastore: internal error: server returned the wrong mutation results")
		}
		key, err := protoToKey(resp.MutationResults[i].Key)
		if err != nil {
			return nil, errors.New("datastore: internal error: server returned an invalid key")
		}
		p.key = key
		p.commit = c
	}

	return c, nil
}

// Rollback abandons a pending transaction.
func (t *Transaction) Rollback() (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Rollback")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	id := t.id
	t.id = nil
	_, err = t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
		ProjectId:   t.client.dataset,
		Transaction: id,
	})
	return err
}

// Get is the transaction-specific version of the package function Get.
// All reads performed during the transaction will come from a single consistent
// snapshot. Furthermore, if the transaction is set to a serializable isolation
// level, another transaction cannot concurrently modify the data that is read
// or modified by this transaction.
func (t *Transaction) Get(key *Key, dst interface{}) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Get")
	defer func() { trace.EndSpan(t.ctx, err) }()

	opts := &pb.ReadOptions{
		ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
	}
	err = t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
	if me, ok := err.(MultiError); ok {
		return me[0]
	}
	return err
}

// GetMulti is a batch version of Get.
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.GetMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	opts := &pb.ReadOptions{
		ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
	}
	return t.client.get(t.ctx, keys, dst, opts)
}

// Put is the transaction-specific version of the package function Put.
//
// Put returns a PendingKey which can be resolved into a Key using the
// return value from a successful Commit. If key is an incomplete key, the
// returned pending key will resolve to a unique key generated by the
// datastore.
func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) {
	h, err := t.PutMulti([]*Key{key}, []interface{}{src})
	if err != nil {
		if me, ok := err.(MultiError); ok {
			return nil, me[0]
		}
		return nil, err
	}
	return h[0], nil
}

// PutMulti is a batch version of Put. One PendingKey is returned for each
// element of src in the same order.
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) (ret []*PendingKey, err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.PutMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return nil, errExpiredTransaction
	}
	mutations, err := putMutations(keys, src)
	if err != nil {
		return nil, err
	}
	origin := len(t.mutations)
	t.mutations = append(t.mutations, mutations...)

	// Prepare the returned handles, pre-populating where possible.
	ret = make([]*PendingKey, len(keys))
	for i, key := range keys {
		p := &PendingKey{}
		if key.Incomplete() {
			// This key will be in the final commit result.
			t.pending[origin+i] = p
		} else {
			p.key = key
		}
		ret[i] = p
	}

	return ret, nil
}

// Delete is the transaction-specific version of the package function Delete.
// Delete enqueues the deletion of the entity for the given key, to be
// committed atomically upon calling Commit.
func (t *Transaction) Delete(key *Key) error {
	err := t.DeleteMulti([]*Key{key})
	if me, ok := err.(MultiError); ok {
		return me[0]
	}
	return err
}

// DeleteMulti is a batch version of Delete.
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) DeleteMulti(keys []*Key) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.DeleteMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	mutations, err := deleteMutations(keys)
	if err != nil {
		return err
	}
	t.mutations = append(t.mutations, mutations...)
	return nil
}

// Mutate adds the mutations to the transaction. They will all be applied atomically
// upon calling Commit. Mutate returns a PendingKey for each Mutation in the argument
// list, in the same order. PendingKeys for Delete mutations are always nil.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
//
// For an example, see Client.Mutate.
func (t *Transaction) Mutate(muts ...*Mutation) ([]*PendingKey, error) {
	if t.id == nil {
		return nil, errExpiredTransaction
	}
	pmuts, err := mutationProtos(muts)
	if err != nil {
		return nil, err
	}
	origin := len(t.mutations)
	t.mutations = append(t.mutations, pmuts...)
	// Prepare the returned handles, pre-populating where possible.
	ret := make([]*PendingKey, len(muts))
	for i, mut := range muts {
		if mut.isDelete() {
			continue
		}
		p := &PendingKey{}
		if mut.key.Incomplete() {
			// This key will be in the final commit result.
			t.pending[origin+i] = p
		} else {
			p.key = mut.key
		}
		ret[i] = p
	}
	return ret, nil
}

// Commit represents the result of a committed transaction.
type Commit struct{}

// Key resolves a pending key handle into a final key.
func (c *Commit) Key(p *PendingKey) *Key {
	if p == nil { // if called on a *PendingKey from a Delete mutation
		return nil
	}
	// If p.commit is nil, the PendingKey did not come from an incomplete key,
	// so p.key is valid.
	if p.commit != nil && c != p.commit {
		panic("PendingKey was not created by corresponding transaction")
	}
	return p.key
}

// PendingKey represents the key for newly-inserted entity. It can be
// resolved into a Key by calling the Key method of Commit.
type PendingKey struct {
	key    *Key
	commit *Commit
}
<commit_msg>datastore: don't nil out transaction ID on retry<commit_after>// Copyright 2014 Google LLC
//
// 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 datastore

import (
	"errors"

	"cloud.google.com/go/internal/trace"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	pb "google.golang.org/genproto/googleapis/datastore/v1"
)

// ErrConcurrentTransaction is returned when a transaction is rolled back due
// to a conflict with a concurrent transaction.
var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")

var errExpiredTransaction = errors.New("datastore: transaction expired")

type transactionSettings struct {
	attempts int
	readOnly bool
	prevID   []byte // ID of the transaction to retry
}

// newTransactionSettings creates a transactionSettings with a given TransactionOption slice.
// Unconfigured options will be set to default values.
func newTransactionSettings(opts []TransactionOption) *transactionSettings {
	s := &transactionSettings{attempts: 3}
	for _, o := range opts {
		o.apply(s)
	}
	return s
}

// TransactionOption configures the way a transaction is executed.
type TransactionOption interface {
	apply(*transactionSettings)
}

// MaxAttempts returns a TransactionOption that overrides the default 3 attempt times.
func MaxAttempts(attempts int) TransactionOption {
	return maxAttempts(attempts)
}

type maxAttempts int

func (w maxAttempts) apply(s *transactionSettings) {
	if w > 0 {
		s.attempts = int(w)
	}
}

// ReadOnly is a TransactionOption that marks the transaction as read-only.
var ReadOnly TransactionOption

func init() {
	ReadOnly = readOnly{}
}

type readOnly struct{}

func (readOnly) apply(s *transactionSettings) {
	s.readOnly = true
}

// Transaction represents a set of datastore operations to be committed atomically.
//
// Operations are enqueued by calling the Put and Delete methods on Transaction
// (or their Multi-equivalents).  These operations are only committed when the
// Commit method is invoked. To ensure consistency, reads must be performed by
// using Transaction's Get method or by using the Transaction method when
// building a query.
//
// A Transaction must be committed or rolled back exactly once.
type Transaction struct {
	id        []byte
	client    *Client
	ctx       context.Context
	mutations []*pb.Mutation      // The mutations to apply.
	pending   map[int]*PendingKey // Map from mutation index to incomplete keys pending transaction completion.
}

// NewTransaction starts a new transaction.
func (c *Client) NewTransaction(ctx context.Context, opts ...TransactionOption) (t *Transaction, err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.NewTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	for _, o := range opts {
		if _, ok := o.(maxAttempts); ok {
			return nil, errors.New("datastore: NewTransaction does not accept MaxAttempts option")
		}
	}
	return c.newTransaction(ctx, newTransactionSettings(opts))
}

func (c *Client) newTransaction(ctx context.Context, s *transactionSettings) (*Transaction, error) {
	req := &pb.BeginTransactionRequest{ProjectId: c.dataset}
	if s.readOnly {
		req.TransactionOptions = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadOnly_{ReadOnly: &pb.TransactionOptions_ReadOnly{}},
		}
	} else if s.prevID != nil {
		req.TransactionOptions = &pb.TransactionOptions{
			Mode: &pb.TransactionOptions_ReadWrite_{ReadWrite: &pb.TransactionOptions_ReadWrite{
				PreviousTransaction: s.prevID,
			}},
		}
	}
	resp, err := c.client.BeginTransaction(ctx, req)
	if err != nil {
		return nil, err
	}
	return &Transaction{
		id:        resp.Transaction,
		ctx:       ctx,
		client:    c,
		mutations: nil,
		pending:   make(map[int]*PendingKey),
	}, nil
}

// RunInTransaction runs f in a transaction. f is invoked with a Transaction
// that f should use for all the transaction's datastore operations.
//
// f must not call Commit or Rollback on the provided Transaction.
//
// If f returns nil, RunInTransaction commits the transaction,
// returning the Commit and a nil error if it succeeds. If the commit fails due
// to a conflicting transaction, RunInTransaction retries f with a new
// Transaction. It gives up and returns ErrConcurrentTransaction after three
// failed attempts (or as configured with MaxAttempts).
//
// If f returns non-nil, then the transaction will be rolled back and
// RunInTransaction will return the same error. The function f is not retried.
//
// Note that when f returns, the transaction is not committed. Calling code
// must not assume that any of f's changes have been committed until
// RunInTransaction returns nil.
//
// Since f may be called multiple times, f should usually be idempotent – that
// is, it should have the same result when called multiple times. Note that
// Transaction.Get will append when unmarshalling slice fields, so it is not
// necessarily idempotent.
func (c *Client) RunInTransaction(ctx context.Context, f func(tx *Transaction) error, opts ...TransactionOption) (cmt *Commit, err error) {
	ctx = trace.StartSpan(ctx, "cloud.google.com/go/datastore.RunInTransaction")
	defer func() { trace.EndSpan(ctx, err) }()

	settings := newTransactionSettings(opts)
	for n := 0; n < settings.attempts; n++ {
		tx, err := c.newTransaction(ctx, settings)
		if err != nil {
			return nil, err
		}
		if err := f(tx); err != nil {
			_ = tx.Rollback()
			return nil, err
		}
		if cmt, err := tx.Commit(); err != ErrConcurrentTransaction {
			return cmt, err
		}
		// Pass this transaction's ID to the retry transaction to preserve
		// transaction priority.
		if !settings.readOnly {
			settings.prevID = tx.id
		}
	}
	return nil, ErrConcurrentTransaction
}

// Commit applies the enqueued operations atomically.
func (t *Transaction) Commit() (c *Commit, err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Commit")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return nil, errExpiredTransaction
	}
	req := &pb.CommitRequest{
		ProjectId:           t.client.dataset,
		TransactionSelector: &pb.CommitRequest_Transaction{Transaction: t.id},
		Mutations:           t.mutations,
		Mode:                pb.CommitRequest_TRANSACTIONAL,
	}
	resp, err := t.client.client.Commit(t.ctx, req)
	if grpc.Code(err) == codes.Aborted {
		return nil, ErrConcurrentTransaction
	}
	t.id = nil // mark the transaction as expired
	if err != nil {
		return nil, err
	}

	// Copy any newly minted keys into the returned keys.
	for i, p := range t.pending {
		if i >= len(resp.MutationResults) || resp.MutationResults[i].Key == nil {
			return nil, errors.New("datastore: internal error: server returned the wrong mutation results")
		}
		key, err := protoToKey(resp.MutationResults[i].Key)
		if err != nil {
			return nil, errors.New("datastore: internal error: server returned an invalid key")
		}
		p.key = key
		p.commit = c
	}

	return c, nil
}

// Rollback abandons a pending transaction.
func (t *Transaction) Rollback() (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Rollback")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	id := t.id
	t.id = nil
	_, err = t.client.client.Rollback(t.ctx, &pb.RollbackRequest{
		ProjectId:   t.client.dataset,
		Transaction: id,
	})
	return err
}

// Get is the transaction-specific version of the package function Get.
// All reads performed during the transaction will come from a single consistent
// snapshot. Furthermore, if the transaction is set to a serializable isolation
// level, another transaction cannot concurrently modify the data that is read
// or modified by this transaction.
func (t *Transaction) Get(key *Key, dst interface{}) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.Get")
	defer func() { trace.EndSpan(t.ctx, err) }()

	opts := &pb.ReadOptions{
		ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
	}
	err = t.client.get(t.ctx, []*Key{key}, []interface{}{dst}, opts)
	if me, ok := err.(MultiError); ok {
		return me[0]
	}
	return err
}

// GetMulti is a batch version of Get.
func (t *Transaction) GetMulti(keys []*Key, dst interface{}) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.GetMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	opts := &pb.ReadOptions{
		ConsistencyType: &pb.ReadOptions_Transaction{Transaction: t.id},
	}
	return t.client.get(t.ctx, keys, dst, opts)
}

// Put is the transaction-specific version of the package function Put.
//
// Put returns a PendingKey which can be resolved into a Key using the
// return value from a successful Commit. If key is an incomplete key, the
// returned pending key will resolve to a unique key generated by the
// datastore.
func (t *Transaction) Put(key *Key, src interface{}) (*PendingKey, error) {
	h, err := t.PutMulti([]*Key{key}, []interface{}{src})
	if err != nil {
		if me, ok := err.(MultiError); ok {
			return nil, me[0]
		}
		return nil, err
	}
	return h[0], nil
}

// PutMulti is a batch version of Put. One PendingKey is returned for each
// element of src in the same order.
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) PutMulti(keys []*Key, src interface{}) (ret []*PendingKey, err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.PutMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return nil, errExpiredTransaction
	}
	mutations, err := putMutations(keys, src)
	if err != nil {
		return nil, err
	}
	origin := len(t.mutations)
	t.mutations = append(t.mutations, mutations...)

	// Prepare the returned handles, pre-populating where possible.
	ret = make([]*PendingKey, len(keys))
	for i, key := range keys {
		p := &PendingKey{}
		if key.Incomplete() {
			// This key will be in the final commit result.
			t.pending[origin+i] = p
		} else {
			p.key = key
		}
		ret[i] = p
	}

	return ret, nil
}

// Delete is the transaction-specific version of the package function Delete.
// Delete enqueues the deletion of the entity for the given key, to be
// committed atomically upon calling Commit.
func (t *Transaction) Delete(key *Key) error {
	err := t.DeleteMulti([]*Key{key})
	if me, ok := err.(MultiError); ok {
		return me[0]
	}
	return err
}

// DeleteMulti is a batch version of Delete.
// TODO(jba): rewrite in terms of Mutate.
func (t *Transaction) DeleteMulti(keys []*Key) (err error) {
	t.ctx = trace.StartSpan(t.ctx, "cloud.google.com/go/datastore.Transaction.DeleteMulti")
	defer func() { trace.EndSpan(t.ctx, err) }()

	if t.id == nil {
		return errExpiredTransaction
	}
	mutations, err := deleteMutations(keys)
	if err != nil {
		return err
	}
	t.mutations = append(t.mutations, mutations...)
	return nil
}

// Mutate adds the mutations to the transaction. They will all be applied atomically
// upon calling Commit. Mutate returns a PendingKey for each Mutation in the argument
// list, in the same order. PendingKeys for Delete mutations are always nil.
//
// If any of the mutations are invalid, Mutate returns a MultiError with the errors.
// Mutate returns a MultiError in this case even if there is only one Mutation.
//
// For an example, see Client.Mutate.
func (t *Transaction) Mutate(muts ...*Mutation) ([]*PendingKey, error) {
	if t.id == nil {
		return nil, errExpiredTransaction
	}
	pmuts, err := mutationProtos(muts)
	if err != nil {
		return nil, err
	}
	origin := len(t.mutations)
	t.mutations = append(t.mutations, pmuts...)
	// Prepare the returned handles, pre-populating where possible.
	ret := make([]*PendingKey, len(muts))
	for i, mut := range muts {
		if mut.isDelete() {
			continue
		}
		p := &PendingKey{}
		if mut.key.Incomplete() {
			// This key will be in the final commit result.
			t.pending[origin+i] = p
		} else {
			p.key = mut.key
		}
		ret[i] = p
	}
	return ret, nil
}

// Commit represents the result of a committed transaction.
type Commit struct{}

// Key resolves a pending key handle into a final key.
func (c *Commit) Key(p *PendingKey) *Key {
	if p == nil { // if called on a *PendingKey from a Delete mutation
		return nil
	}
	// If p.commit is nil, the PendingKey did not come from an incomplete key,
	// so p.key is valid.
	if p.commit != nil && c != p.commit {
		panic("PendingKey was not created by corresponding transaction")
	}
	return p.key
}

// PendingKey represents the key for newly-inserted entity. It can be
// resolved into a Key by calling the Key method of Commit.
type PendingKey struct {
	key    *Key
	commit *Commit
}
<|endoftext|>