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

// Package types declares the data types and implements
// the algorithms for type-checking of Go packages. Use
// Config.Check to invoke the type checker for a package.
// Alternatively, create a new type checker with NewChecker
// and invoke it incrementally by calling Checker.Files.
//
// Type-checking consists of several interdependent phases:
//
// Name resolution maps each identifier (ast.Ident) in the program to the
// language object (Object) it denotes.
// Use Info.{Defs,Uses,Implicits} for the results of name resolution.
//
// Constant folding computes the exact constant value (constant.Value)
// for every expression (ast.Expr) that is a compile-time constant.
// Use Info.Types[expr].Value for the results of constant folding.
//
// Type inference computes the type (Type) of every expression (ast.Expr)
// and checks for compliance with the language specification.
// Use Info.Types[expr].Type for the results of type inference.
//
// For a tutorial, see https://golang.org/s/types-tutorial.
//
package types

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/constant"
	"go/token"
)

// An Error describes a type-checking error; it implements the error interface.
// A "soft" error is an error that still permits a valid interpretation of a
// package (such as "unused variable"); "hard" errors may lead to unpredictable
// behavior if ignored.
type Error struct {
	Fset *token.FileSet // file set for interpretation of Pos
	Pos  token.Pos      // error position
	Msg  string         // error message
	Soft bool           // if set, error is "soft"
}

// Error returns an error string formatted as follows:
// filename:line:column: message
func (err Error) Error() string {
	return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg)
}

// An Importer resolves import paths to Packages.
//
// CAUTION: This interface does not support the import of locally
// vendored packages. See https://golang.org/s/go15vendor.
// If possible, external implementations should implement ImporterFrom.
type Importer interface {
	// Import returns the imported package for the given import path.
	// The semantics is like for ImporterFrom.ImportFrom except that
	// dir and mode are ignored (since they are not present).
	Import(path string) (*Package, error)
}

// ImportMode is reserved for future use.
type ImportMode int

// An ImporterFrom resolves import paths to packages; it
// supports vendoring per https://golang.org/s/go15vendor.
// Use go/importer to obtain an ImporterFrom implementation.
type ImporterFrom interface {
	// Importer is present for backward-compatibility. Calling
	// Import(path) is the same as calling ImportFrom(path, "", 0);
	// i.e., locally vendored packages may not be found.
	// The types package does not call Import if an ImporterFrom
	// is present.
	Importer

	// ImportFrom returns the imported package for the given import
	// path when imported by a package file located in dir.
	// If the import failed, besides returning an error, ImportFrom
	// is encouraged to cache and return a package anyway, if one
	// was created. This will reduce package inconsistencies and
	// follow-on type checker errors due to the missing package.
	// The mode value must be 0; it is reserved for future use.
	// Two calls to ImportFrom with the same path and dir must
	// return the same package.
	ImportFrom(path, dir string, mode ImportMode) (*Package, error)
}

// A Config specifies the configuration for type checking.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
	// If IgnoreFuncBodies is set, function bodies are not
	// type-checked.
	IgnoreFuncBodies bool

	// If FakeImportC is set, `import "C"` (for packages requiring Cgo)
	// declares an empty "C" package and errors are omitted for qualified
	// identifiers referring to package C (which won't find an object).
	// This feature is intended for the standard library cmd/api tool.
	//
	// Caution: Effects may be unpredictable due to follow-on errors.
	//          Do not use casually!
	FakeImportC bool

	// If Error != nil, it is called with each error found
	// during type checking; err has dynamic type Error.
	// Secondary errors (for instance, to enumerate all types
	// involved in an invalid recursive type declaration) have
	// error strings that start with a '\t' character.
	// If Error == nil, type-checking stops with the first
	// error found.
	Error func(err error)

	// An importer is used to import packages referred to from
	// import declarations.
	// If the installed importer implements ImporterFrom, the type
	// checker calls ImportFrom instead of Import.
	// The type checker reports an error if an importer is needed
	// but none was installed.
	Importer Importer

	// If Sizes != nil, it provides the sizing functions for package unsafe.
	// Otherwise SizesFor("gc", "amd64") is used instead.
	Sizes Sizes

	// If DisableUnusedImportCheck is set, packages are not checked
	// for unused imports.
	DisableUnusedImportCheck bool
}

// Info holds result type information for a type-checked package.
// Only the information for which a map is provided is collected.
// If the package has type errors, the collected information may
// be incomplete.
type Info struct {
	// Types maps expressions to their types, and for constant
	// expressions, also their values. Invalid expressions are
	// omitted.
	//
	// For (possibly parenthesized) identifiers denoting built-in
	// functions, the recorded signatures are call-site specific:
	// if the call result is not a constant, the recorded type is
	// an argument-specific signature. Otherwise, the recorded type
	// is invalid.
	//
	// The Types map does not record the type of every identifier,
	// only those that appear where an arbitrary expression is
	// permitted. For instance, the identifier f in a selector
	// expression x.f is found only in the Selections map, the
	// identifier z in a variable declaration 'var z int' is found
	// only in the Defs map, and identifiers denoting packages in
	// qualified identifiers are collected in the Uses map.
	Types map[ast.Expr]TypeAndValue

	// Defs maps identifiers to the objects they define (including
	// package names, dots "." of dot-imports, and blank "_" identifiers).
	// For identifiers that do not denote objects (e.g., the package name
	// in package clauses, or symbolic variables t in t := x.(type) of
	// type switch headers), the corresponding objects are nil.
	//
	// For an embedded field, Defs returns the field *Var it defines.
	//
	// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
	Defs map[*ast.Ident]Object

	// Uses maps identifiers to the objects they denote.
	//
	// For an embedded field, Uses returns the *TypeName it denotes.
	//
	// Invariant: Uses[id].Pos() != id.Pos()
	Uses map[*ast.Ident]Object

	// Implicits maps nodes to their implicitly declared objects, if any.
	// The following node and object types may appear:
	//
	//     node               declared object
	//
	//     *ast.ImportSpec    *PkgName for imports without renames
	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
	//     *ast.Field         anonymous parameter *Var
	//
	Implicits map[ast.Node]Object

	// Selections maps selector expressions (excluding qualified identifiers)
	// to their corresponding selections.
	Selections map[*ast.SelectorExpr]*Selection

	// Scopes maps ast.Nodes to the scopes they define. Package scopes are not
	// associated with a specific node but with all files belonging to a package.
	// Thus, the package scope can be found in the type-checked Package object.
	// Scopes nest, with the Universe scope being the outermost scope, enclosing
	// the package scope, which contains (one or more) files scopes, which enclose
	// function scopes which in turn enclose statement and function literal scopes.
	// Note that even though package-level functions are declared in the package
	// scope, the function scopes are embedded in the file scope of the file
	// containing the function declaration.
	//
	// The following node types may appear in Scopes:
	//
	//     *ast.File
	//     *ast.FuncType
	//     *ast.BlockStmt
	//     *ast.IfStmt
	//     *ast.SwitchStmt
	//     *ast.TypeSwitchStmt
	//     *ast.CaseClause
	//     *ast.CommClause
	//     *ast.ForStmt
	//     *ast.RangeStmt
	//
	Scopes map[ast.Node]*Scope

	// InitOrder is the list of package-level initializers in the order in which
	// they must be executed. Initializers referring to variables related by an
	// initialization dependency appear in topological order, the others appear
	// in source order. Variables without an initialization expression do not
	// appear in this list.
	InitOrder []*Initializer
}

// TypeOf returns the type of expression e, or nil if not found.
// Precondition: the Types, Uses and Defs maps are populated.
//
func (info *Info) TypeOf(e ast.Expr) Type {
	if t, ok := info.Types[e]; ok {
		return t.Type
	}
	if id, _ := e.(*ast.Ident); id != nil {
		if obj := info.ObjectOf(id); obj != nil {
			return obj.Type()
		}
	}
	return nil
}

// ObjectOf returns the object denoted by the specified id,
// or nil if not found.
//
// If id is an embedded struct field, ObjectOf returns the field (*Var)
// it uses, not the type (*TypeName) it defines.
//
// Precondition: the Uses and Defs maps are populated.
//
func (info *Info) ObjectOf(id *ast.Ident) Object {
	if obj := info.Defs[id]; obj != nil {
		return obj
	}
	return info.Uses[id]
}

// TypeAndValue reports the type and value (for constants)
// of the corresponding expression.
type TypeAndValue struct {
	mode  operandMode
	Type  Type
	Value constant.Value
}

// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report
// "void" values as regular values but with the empty tuple type.

// IsVoid reports whether the corresponding expression
// is a function call without results.
func (tv TypeAndValue) IsVoid() bool {
	return tv.mode == novalue
}

// IsType reports whether the corresponding expression specifies a type.
func (tv TypeAndValue) IsType() bool {
	return tv.mode == typexpr
}

// IsBuiltin reports whether the corresponding expression denotes
// a (possibly parenthesized) built-in function.
func (tv TypeAndValue) IsBuiltin() bool {
	return tv.mode == builtin
}

// IsValue reports whether the corresponding expression is a value.
// Builtins are not considered values. Constant values have a non-
// nil Value.
func (tv TypeAndValue) IsValue() bool {
	switch tv.mode {
	case constant_, variable, mapindex, value, commaok:
		return true
	}
	return false
}

// IsNil reports whether the corresponding expression denotes the
// predeclared value nil.
func (tv TypeAndValue) IsNil() bool {
	return tv.mode == value && tv.Type == Typ[UntypedNil]
}

// Addressable reports whether the corresponding expression
// is addressable (https://golang.org/ref/spec#Address_operators).
func (tv TypeAndValue) Addressable() bool {
	return tv.mode == variable
}

// Assignable reports whether the corresponding expression
// is assignable to (provided a value of the right type).
func (tv TypeAndValue) Assignable() bool {
	return tv.mode == variable || tv.mode == mapindex
}

// HasOk reports whether the corresponding expression may be
// used on the rhs of a comma-ok assignment.
func (tv TypeAndValue) HasOk() bool {
	return tv.mode == commaok || tv.mode == mapindex
}

// An Initializer describes a package-level variable, or a list of variables in case
// of a multi-valued initialization expression, and the corresponding initialization
// expression.
type Initializer struct {
	Lhs []*Var // var Lhs = Rhs
	Rhs ast.Expr
}

func (init *Initializer) String() string {
	var buf bytes.Buffer
	for i, lhs := range init.Lhs {
		if i > 0 {
			buf.WriteString(", ")
		}
		buf.WriteString(lhs.Name())
	}
	buf.WriteString(" = ")
	WriteExpr(&buf, init.Rhs)
	return buf.String()
}

// Check type-checks a package and returns the resulting package object and
// the first error if any. Additionally, if info != nil, Check populates each
// of the non-nil maps in the Info struct.
//
// The package is marked as complete if no errors occurred, otherwise it is
// incomplete. See Config.Error for controlling behavior in the presence of
// errors.
//
// The package is specified by a list of *ast.Files and corresponding
// file set, and the package path the package is identified with.
// The clean path must not be empty or dot (".").
func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
	pkg := NewPackage(path, "")
	return pkg, NewChecker(conf, fset, pkg, info).Files(files)
}

// AssertableTo reports whether a value of type V can be asserted to have type T.
func AssertableTo(V *Interface, T Type) bool {
	m, _ := assertableTo(V, T)
	return m == nil
}

// AssignableTo reports whether a value of type V is assignable to a variable of type T.
func AssignableTo(V, T Type) bool {
	x := operand{mode: value, typ: V}
	return x.assignableTo(nil, T, nil) // config not needed for non-constant x
}

// ConvertibleTo reports whether a value of type V is convertible to a value of type T.
func ConvertibleTo(V, T Type) bool {
	x := operand{mode: value, typ: V}
	return x.convertibleTo(nil, T) // config not needed for non-constant x
}

// Implements reports whether type V implements interface T.
func Implements(V Type, T *Interface) bool {
	f, _ := MissingMethod(V, T, true)
	return f == nil
}
<commit_msg>go/types: fix swapped use of "uses" and "defines" in ObjectOf documentation<commit_after>// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package types declares the data types and implements
// the algorithms for type-checking of Go packages. Use
// Config.Check to invoke the type checker for a package.
// Alternatively, create a new type checker with NewChecker
// and invoke it incrementally by calling Checker.Files.
//
// Type-checking consists of several interdependent phases:
//
// Name resolution maps each identifier (ast.Ident) in the program to the
// language object (Object) it denotes.
// Use Info.{Defs,Uses,Implicits} for the results of name resolution.
//
// Constant folding computes the exact constant value (constant.Value)
// for every expression (ast.Expr) that is a compile-time constant.
// Use Info.Types[expr].Value for the results of constant folding.
//
// Type inference computes the type (Type) of every expression (ast.Expr)
// and checks for compliance with the language specification.
// Use Info.Types[expr].Type for the results of type inference.
//
// For a tutorial, see https://golang.org/s/types-tutorial.
//
package types

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/constant"
	"go/token"
)

// An Error describes a type-checking error; it implements the error interface.
// A "soft" error is an error that still permits a valid interpretation of a
// package (such as "unused variable"); "hard" errors may lead to unpredictable
// behavior if ignored.
type Error struct {
	Fset *token.FileSet // file set for interpretation of Pos
	Pos  token.Pos      // error position
	Msg  string         // error message
	Soft bool           // if set, error is "soft"
}

// Error returns an error string formatted as follows:
// filename:line:column: message
func (err Error) Error() string {
	return fmt.Sprintf("%s: %s", err.Fset.Position(err.Pos), err.Msg)
}

// An Importer resolves import paths to Packages.
//
// CAUTION: This interface does not support the import of locally
// vendored packages. See https://golang.org/s/go15vendor.
// If possible, external implementations should implement ImporterFrom.
type Importer interface {
	// Import returns the imported package for the given import path.
	// The semantics is like for ImporterFrom.ImportFrom except that
	// dir and mode are ignored (since they are not present).
	Import(path string) (*Package, error)
}

// ImportMode is reserved for future use.
type ImportMode int

// An ImporterFrom resolves import paths to packages; it
// supports vendoring per https://golang.org/s/go15vendor.
// Use go/importer to obtain an ImporterFrom implementation.
type ImporterFrom interface {
	// Importer is present for backward-compatibility. Calling
	// Import(path) is the same as calling ImportFrom(path, "", 0);
	// i.e., locally vendored packages may not be found.
	// The types package does not call Import if an ImporterFrom
	// is present.
	Importer

	// ImportFrom returns the imported package for the given import
	// path when imported by a package file located in dir.
	// If the import failed, besides returning an error, ImportFrom
	// is encouraged to cache and return a package anyway, if one
	// was created. This will reduce package inconsistencies and
	// follow-on type checker errors due to the missing package.
	// The mode value must be 0; it is reserved for future use.
	// Two calls to ImportFrom with the same path and dir must
	// return the same package.
	ImportFrom(path, dir string, mode ImportMode) (*Package, error)
}

// A Config specifies the configuration for type checking.
// The zero value for Config is a ready-to-use default configuration.
type Config struct {
	// If IgnoreFuncBodies is set, function bodies are not
	// type-checked.
	IgnoreFuncBodies bool

	// If FakeImportC is set, `import "C"` (for packages requiring Cgo)
	// declares an empty "C" package and errors are omitted for qualified
	// identifiers referring to package C (which won't find an object).
	// This feature is intended for the standard library cmd/api tool.
	//
	// Caution: Effects may be unpredictable due to follow-on errors.
	//          Do not use casually!
	FakeImportC bool

	// If Error != nil, it is called with each error found
	// during type checking; err has dynamic type Error.
	// Secondary errors (for instance, to enumerate all types
	// involved in an invalid recursive type declaration) have
	// error strings that start with a '\t' character.
	// If Error == nil, type-checking stops with the first
	// error found.
	Error func(err error)

	// An importer is used to import packages referred to from
	// import declarations.
	// If the installed importer implements ImporterFrom, the type
	// checker calls ImportFrom instead of Import.
	// The type checker reports an error if an importer is needed
	// but none was installed.
	Importer Importer

	// If Sizes != nil, it provides the sizing functions for package unsafe.
	// Otherwise SizesFor("gc", "amd64") is used instead.
	Sizes Sizes

	// If DisableUnusedImportCheck is set, packages are not checked
	// for unused imports.
	DisableUnusedImportCheck bool
}

// Info holds result type information for a type-checked package.
// Only the information for which a map is provided is collected.
// If the package has type errors, the collected information may
// be incomplete.
type Info struct {
	// Types maps expressions to their types, and for constant
	// expressions, also their values. Invalid expressions are
	// omitted.
	//
	// For (possibly parenthesized) identifiers denoting built-in
	// functions, the recorded signatures are call-site specific:
	// if the call result is not a constant, the recorded type is
	// an argument-specific signature. Otherwise, the recorded type
	// is invalid.
	//
	// The Types map does not record the type of every identifier,
	// only those that appear where an arbitrary expression is
	// permitted. For instance, the identifier f in a selector
	// expression x.f is found only in the Selections map, the
	// identifier z in a variable declaration 'var z int' is found
	// only in the Defs map, and identifiers denoting packages in
	// qualified identifiers are collected in the Uses map.
	Types map[ast.Expr]TypeAndValue

	// Defs maps identifiers to the objects they define (including
	// package names, dots "." of dot-imports, and blank "_" identifiers).
	// For identifiers that do not denote objects (e.g., the package name
	// in package clauses, or symbolic variables t in t := x.(type) of
	// type switch headers), the corresponding objects are nil.
	//
	// For an embedded field, Defs returns the field *Var it defines.
	//
	// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
	Defs map[*ast.Ident]Object

	// Uses maps identifiers to the objects they denote.
	//
	// For an embedded field, Uses returns the *TypeName it denotes.
	//
	// Invariant: Uses[id].Pos() != id.Pos()
	Uses map[*ast.Ident]Object

	// Implicits maps nodes to their implicitly declared objects, if any.
	// The following node and object types may appear:
	//
	//     node               declared object
	//
	//     *ast.ImportSpec    *PkgName for imports without renames
	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
	//     *ast.Field         anonymous parameter *Var
	//
	Implicits map[ast.Node]Object

	// Selections maps selector expressions (excluding qualified identifiers)
	// to their corresponding selections.
	Selections map[*ast.SelectorExpr]*Selection

	// Scopes maps ast.Nodes to the scopes they define. Package scopes are not
	// associated with a specific node but with all files belonging to a package.
	// Thus, the package scope can be found in the type-checked Package object.
	// Scopes nest, with the Universe scope being the outermost scope, enclosing
	// the package scope, which contains (one or more) files scopes, which enclose
	// function scopes which in turn enclose statement and function literal scopes.
	// Note that even though package-level functions are declared in the package
	// scope, the function scopes are embedded in the file scope of the file
	// containing the function declaration.
	//
	// The following node types may appear in Scopes:
	//
	//     *ast.File
	//     *ast.FuncType
	//     *ast.BlockStmt
	//     *ast.IfStmt
	//     *ast.SwitchStmt
	//     *ast.TypeSwitchStmt
	//     *ast.CaseClause
	//     *ast.CommClause
	//     *ast.ForStmt
	//     *ast.RangeStmt
	//
	Scopes map[ast.Node]*Scope

	// InitOrder is the list of package-level initializers in the order in which
	// they must be executed. Initializers referring to variables related by an
	// initialization dependency appear in topological order, the others appear
	// in source order. Variables without an initialization expression do not
	// appear in this list.
	InitOrder []*Initializer
}

// TypeOf returns the type of expression e, or nil if not found.
// Precondition: the Types, Uses and Defs maps are populated.
//
func (info *Info) TypeOf(e ast.Expr) Type {
	if t, ok := info.Types[e]; ok {
		return t.Type
	}
	if id, _ := e.(*ast.Ident); id != nil {
		if obj := info.ObjectOf(id); obj != nil {
			return obj.Type()
		}
	}
	return nil
}

// ObjectOf returns the object denoted by the specified id,
// or nil if not found.
//
// If id is an embedded struct field, ObjectOf returns the field (*Var)
// it defines, not the type (*TypeName) it uses.
//
// Precondition: the Uses and Defs maps are populated.
//
func (info *Info) ObjectOf(id *ast.Ident) Object {
	if obj := info.Defs[id]; obj != nil {
		return obj
	}
	return info.Uses[id]
}

// TypeAndValue reports the type and value (for constants)
// of the corresponding expression.
type TypeAndValue struct {
	mode  operandMode
	Type  Type
	Value constant.Value
}

// TODO(gri) Consider eliminating the IsVoid predicate. Instead, report
// "void" values as regular values but with the empty tuple type.

// IsVoid reports whether the corresponding expression
// is a function call without results.
func (tv TypeAndValue) IsVoid() bool {
	return tv.mode == novalue
}

// IsType reports whether the corresponding expression specifies a type.
func (tv TypeAndValue) IsType() bool {
	return tv.mode == typexpr
}

// IsBuiltin reports whether the corresponding expression denotes
// a (possibly parenthesized) built-in function.
func (tv TypeAndValue) IsBuiltin() bool {
	return tv.mode == builtin
}

// IsValue reports whether the corresponding expression is a value.
// Builtins are not considered values. Constant values have a non-
// nil Value.
func (tv TypeAndValue) IsValue() bool {
	switch tv.mode {
	case constant_, variable, mapindex, value, commaok:
		return true
	}
	return false
}

// IsNil reports whether the corresponding expression denotes the
// predeclared value nil.
func (tv TypeAndValue) IsNil() bool {
	return tv.mode == value && tv.Type == Typ[UntypedNil]
}

// Addressable reports whether the corresponding expression
// is addressable (https://golang.org/ref/spec#Address_operators).
func (tv TypeAndValue) Addressable() bool {
	return tv.mode == variable
}

// Assignable reports whether the corresponding expression
// is assignable to (provided a value of the right type).
func (tv TypeAndValue) Assignable() bool {
	return tv.mode == variable || tv.mode == mapindex
}

// HasOk reports whether the corresponding expression may be
// used on the rhs of a comma-ok assignment.
func (tv TypeAndValue) HasOk() bool {
	return tv.mode == commaok || tv.mode == mapindex
}

// An Initializer describes a package-level variable, or a list of variables in case
// of a multi-valued initialization expression, and the corresponding initialization
// expression.
type Initializer struct {
	Lhs []*Var // var Lhs = Rhs
	Rhs ast.Expr
}

func (init *Initializer) String() string {
	var buf bytes.Buffer
	for i, lhs := range init.Lhs {
		if i > 0 {
			buf.WriteString(", ")
		}
		buf.WriteString(lhs.Name())
	}
	buf.WriteString(" = ")
	WriteExpr(&buf, init.Rhs)
	return buf.String()
}

// Check type-checks a package and returns the resulting package object and
// the first error if any. Additionally, if info != nil, Check populates each
// of the non-nil maps in the Info struct.
//
// The package is marked as complete if no errors occurred, otherwise it is
// incomplete. See Config.Error for controlling behavior in the presence of
// errors.
//
// The package is specified by a list of *ast.Files and corresponding
// file set, and the package path the package is identified with.
// The clean path must not be empty or dot (".").
func (conf *Config) Check(path string, fset *token.FileSet, files []*ast.File, info *Info) (*Package, error) {
	pkg := NewPackage(path, "")
	return pkg, NewChecker(conf, fset, pkg, info).Files(files)
}

// AssertableTo reports whether a value of type V can be asserted to have type T.
func AssertableTo(V *Interface, T Type) bool {
	m, _ := assertableTo(V, T)
	return m == nil
}

// AssignableTo reports whether a value of type V is assignable to a variable of type T.
func AssignableTo(V, T Type) bool {
	x := operand{mode: value, typ: V}
	return x.assignableTo(nil, T, nil) // config not needed for non-constant x
}

// ConvertibleTo reports whether a value of type V is convertible to a value of type T.
func ConvertibleTo(V, T Type) bool {
	x := operand{mode: value, typ: V}
	return x.convertibleTo(nil, T) // config not needed for non-constant x
}

// Implements reports whether type V implements interface T.
func Implements(V Type, T *Interface) bool {
	f, _ := MissingMethod(V, T, true)
	return f == nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/fzzbt/radix/redis"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"syscall"
)

// The maximal size for an image is 5MB
const maxSize = 5 * (1 << 20)

// The directory for caching files
var directory string

// The connection to redis
var connection *redis.Client

func urlStatus(uri string) error {
	ok, err := connection.Hexists("img/"+uri, "created_at").Bool()
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("Invalid URL")
	}

	str, err := connection.Get("img/err/" + uri).Str()
	if err == nil {
		return errors.New(str)
	}

	return nil
}

// Generate a key for cache from a string
func generateKeyForCache(s string) string {
	h := sha1.New()
	io.WriteString(h, s)
	key := h.Sum(nil)

	// Use 3 levels of hasing to avoid having too many files in the same directory
	return fmt.Sprintf("%s/%x/%x/%x/%x", directory, key[0:1], key[1:2], key[2:3], key[3:])
}

// Fetch image from cache
func fetchImageFromCache(uri string) (contentType string, body []byte, ok bool) {
	ok = false

	contentType, err := connection.Hget("img/"+uri, "type").Str()
	if err != nil {
		return
	}

	filename := generateKeyForCache(uri)
	body, err = ioutil.ReadFile(filename)
	ok = err == nil

	return
}

// Save the body and the content-type header in cache
func saveImageInCache(uri string, contentType string, body []byte) {
	go func() {
		filename := generateKeyForCache(uri)
		dirname := path.Dir(filename)
		err := os.MkdirAll(dirname, 0755)
		if err != nil {
			return
		}

		// Save the body on disk
		err = ioutil.WriteFile(filename, body, 0644)
		if err != nil {
			log.Printf("Error while writing %s\n", filename)
			return
		}

		// And other infos in redis
		connection.Hset("img/"+uri, "type", contentType)
	}()
}

func saveErrorInCache(uri string, err error) {
	go func() {
		connection.Set("img/err/"+uri, err.Error())
		connection.Expire("img/err/"+uri, 600)
	}()
}

// Fetch the image from the distant server
func fetchImageFromServer(uri string) (contentType string, body []byte, err error) {
	res, err := http.Get(uri)
	if err != nil {
		return
	}
	if res.StatusCode != 200 {
		log.Printf("Status code of %s is: %d\n", uri, res.StatusCode)
		err = errors.New("Unexpected status code")
		saveErrorInCache(uri, err)
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if res.ContentLength > maxSize {
		log.Printf("Exceeded max size for %s: %d\n", uri, res.ContentLength)
		err = errors.New("Exceeded max size")
		saveErrorInCache(uri, err)
		return
	}
	contentType = res.Header.Get("Content-Type")
	if contentType[0:5] != "image" {
		log.Printf("%s has an invalid content-type: %s\n", uri, contentType)
		err = errors.New("Invalid content-type")
		saveErrorInCache(uri, err)
		return
	}
	log.Printf("Fetch %s (%s)\n", uri, contentType)

	if urlStatus(uri) == nil {
		saveImageInCache(uri, contentType, body)
	}
	return
}

// Fetch image from cache if available, or from the server
func fetchImage(uri string) (contentType string, body []byte, err error) {
	err = urlStatus(uri)
	if err != nil {
		return
	}

	contentType, body, ok := fetchImageFromCache(uri)
	if !ok {
		contentType, body, err = fetchImageFromServer(uri)
	}

	return
}

// Receive an HTTP request for an image and respond with it
func Img(w http.ResponseWriter, r *http.Request) {
	encoded_url := r.URL.Query().Get(":encoded_url")
	chars, err := hex.DecodeString(encoded_url)
	if err != nil {
		log.Printf("Invalid URL %s\n", encoded_url)
		http.Error(w, "Invalid parameters", 400)
		return
	}
	uri := string(chars)

	contentType, body, err := fetchImage(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Add("Content-Type", contentType)
	w.Write(body)
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var logs string
	var conn string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.StringVar(&conn, "r", "localhost:6379/0", "The redis database to use for caching meta")
	flag.StringVar(&directory, "d", "cache", "The directory for the caching files")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Redis
	parts := strings.Split(conn, "/")
	host := parts[0]
	db := 0
	if len(parts) >= 2 {
		db, _ = strconv.Atoi(parts[1])
	}
	cfg := redis.Config{Database: db, Address: host, PoolCapacity: 4}
	connection = redis.NewClient(cfg)
	defer connection.Close()

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/img/:encoded_url", http.HandlerFunc(Img))
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<commit_msg>Also accept request with the filename at the end of the URL<commit_after>package main

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"flag"
	"fmt"
	"github.com/bmizerany/pat"
	"github.com/fzzbt/radix/redis"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"strconv"
	"strings"
	"syscall"
)

// The maximal size for an image is 5MB
const maxSize = 5 * (1 << 20)

// The directory for caching files
var directory string

// The connection to redis
var connection *redis.Client

func urlStatus(uri string) error {
	ok, err := connection.Hexists("img/"+uri, "created_at").Bool()
	if err != nil {
		return err
	}
	if !ok {
		return errors.New("Invalid URL")
	}

	str, err := connection.Get("img/err/" + uri).Str()
	if err == nil {
		return errors.New(str)
	}

	return nil
}

// Generate a key for cache from a string
func generateKeyForCache(s string) string {
	h := sha1.New()
	io.WriteString(h, s)
	key := h.Sum(nil)

	// Use 3 levels of hasing to avoid having too many files in the same directory
	return fmt.Sprintf("%s/%x/%x/%x/%x", directory, key[0:1], key[1:2], key[2:3], key[3:])
}

// Fetch image from cache
func fetchImageFromCache(uri string) (contentType string, body []byte, ok bool) {
	ok = false

	contentType, err := connection.Hget("img/"+uri, "type").Str()
	if err != nil {
		return
	}

	filename := generateKeyForCache(uri)
	body, err = ioutil.ReadFile(filename)
	ok = err == nil

	return
}

// Save the body and the content-type header in cache
func saveImageInCache(uri string, contentType string, body []byte) {
	go func() {
		filename := generateKeyForCache(uri)
		dirname := path.Dir(filename)
		err := os.MkdirAll(dirname, 0755)
		if err != nil {
			return
		}

		// Save the body on disk
		err = ioutil.WriteFile(filename, body, 0644)
		if err != nil {
			log.Printf("Error while writing %s\n", filename)
			return
		}

		// And other infos in redis
		connection.Hset("img/"+uri, "type", contentType)
	}()
}

func saveErrorInCache(uri string, err error) {
	go func() {
		connection.Set("img/err/"+uri, err.Error())
		connection.Expire("img/err/"+uri, 600)
	}()
}

// Fetch the image from the distant server
func fetchImageFromServer(uri string) (contentType string, body []byte, err error) {
	res, err := http.Get(uri)
	if err != nil {
		return
	}
	if res.StatusCode != 200 {
		log.Printf("Status code of %s is: %d\n", uri, res.StatusCode)
		err = errors.New("Unexpected status code")
		saveErrorInCache(uri, err)
		return
	}

	defer res.Body.Close()
	body, err = ioutil.ReadAll(res.Body)
	if err != nil {
		return
	}
	if res.ContentLength > maxSize {
		log.Printf("Exceeded max size for %s: %d\n", uri, res.ContentLength)
		err = errors.New("Exceeded max size")
		saveErrorInCache(uri, err)
		return
	}
	contentType = res.Header.Get("Content-Type")
	if contentType[0:5] != "image" {
		log.Printf("%s has an invalid content-type: %s\n", uri, contentType)
		err = errors.New("Invalid content-type")
		saveErrorInCache(uri, err)
		return
	}
	log.Printf("Fetch %s (%s)\n", uri, contentType)

	if urlStatus(uri) == nil {
		saveImageInCache(uri, contentType, body)
	}
	return
}

// Fetch image from cache if available, or from the server
func fetchImage(uri string) (contentType string, body []byte, err error) {
	err = urlStatus(uri)
	if err != nil {
		return
	}

	contentType, body, ok := fetchImageFromCache(uri)
	if !ok {
		contentType, body, err = fetchImageFromServer(uri)
	}

	return
}

// Receive an HTTP request for an image and respond with it
func Img(w http.ResponseWriter, r *http.Request) {
	encoded_url := r.URL.Query().Get(":encoded_url")
	chars, err := hex.DecodeString(encoded_url)
	if err != nil {
		log.Printf("Invalid URL %s\n", encoded_url)
		http.Error(w, "Invalid parameters", 400)
		return
	}
	uri := string(chars)

	contentType, body, err := fetchImage(uri)
	if err != nil {
		http.NotFound(w, r)
		return
	}
	w.Header().Add("Content-Type", contentType)
	w.Write(body)
}

// Returns 200 OK if the server is running (for monitoring)
func Status(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "OK")
}

func main() {
	// Parse the command-line
	var addr string
	var logs string
	var conn string
	flag.StringVar(&addr, "a", "127.0.0.1:8000", "Bind to this address:port")
	flag.StringVar(&logs, "l", "-", "Use this file for logs")
	flag.StringVar(&conn, "r", "localhost:6379/0", "The redis database to use for caching meta")
	flag.StringVar(&directory, "d", "cache", "The directory for the caching files")
	flag.Parse()

	// Logging
	if logs != "-" {
		f, err := os.OpenFile(logs, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
		if err != nil {
			log.Fatal("OpenFile: ", err)
		}
		syscall.Dup2(int(f.Fd()), int(os.Stdout.Fd()))
		syscall.Dup2(int(f.Fd()), int(os.Stderr.Fd()))
	}

	// Redis
	parts := strings.Split(conn, "/")
	host := parts[0]
	db := 0
	if len(parts) >= 2 {
		db, _ = strconv.Atoi(parts[1])
	}
	cfg := redis.Config{Database: db, Address: host, PoolCapacity: 4}
	connection = redis.NewClient(cfg)
	defer connection.Close()

	// Routing
	m := pat.New()
	m.Get("/status", http.HandlerFunc(Status))
	m.Get("/img/:encoded_url/:filename", http.HandlerFunc(Img))
	m.Get("/img/:encoded_url", http.HandlerFunc(Img))
	http.Handle("/", m)

	// Start the HTTP server
	log.Printf("Listening on http://%s/\n", addr)
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
<|endoftext|>


<commit_before>package goq

import (
	"encoding/json"
	"errors"
	"time"
)

type Status struct {
	Code     uint8
	Progress uint8
}

type Job struct {
	ID         string
	JSON       string
	ResultJSON string
	Status     *Status
	processor  Processor
	queueName  string
}

// Method to requeue job, will remove this job from failed job if exists.
func (j *Job) Retry() error {
	err := client.SRem(JOB_FAILED_PREFIX+j.queueName, j.JSON).Err()
	if err != nil {
		return errors.New("Failed to remove from failed jobs set of job " + j.ID + " : " + err.Error())
	}
	j.processor(j)

	return nil
}

// Method to fail job to failed job set.
// Also set the status Code to 3
func (j *Job) Fail() error {
	err := client.SAdd(JOB_FAILED_PREFIX+j.queueName, j.JSON).Err()
	if err != nil {
		return errors.New("Failed to add to failed jobs set of job " + j.ID + " : " + err.Error())
	}
	err = j.SetStatus(3, 0)
	if err != nil {
		return err
	}

	return nil
}

// Method to set this job Status locally and to redis.
// Code must be 0 for waiting, 1 for working, 2 for completed, and 3 for failed.
// Progress must be 0-100
func (j *Job) SetStatus(code, progress uint8) error {
	if code < 0 || code > 3 {
		return errors.New("Failed to set status code of job " + j.ID + " : code must be 0-3")
	}
	if progress < 0 || progress > 100 {
		return errors.New("Failed to set status progress of job " + j.ID + " : progress must be 0-100")
	}

	j.Status.Code = code
	j.Status.Progress = progress

	statusJSON, err := json.Marshal(j.Status)
	if err != nil {
		return errors.New("Failed to marshal status of job " + j.ID + " : " + err.Error())
	}

	err = client.Set(JOB_STATUS_PREFIX+j.ID, string(statusJSON), 0).Err()
	if err != nil {
		return errors.New("Failed to set status of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to update this job Status from redis.
func (j *Job) GetStatus() error {
	dataJSON, err := client.Get(JOB_STATUS_PREFIX + j.ID).Result()
	if err != nil {
		return errors.New("Failed to get status of job " + j.ID + " : " + err.Error())
	}

	err = json.Unmarshal([]byte(dataJSON), j.Status)
	if err != nil {
		return errors.New("Failed to unmarshal status of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to save this job result to redis with ttl in seconds.
func (j *Job) SetCache(ttl time.Duration) error {
	err := client.Set(JOB_CACHE_PREFIX+j.ID, j.ResultJSON, ttl).Err()
	if err != nil {
		return errors.New("Failed to set cache of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to check if this job result is cached.
func (j *Job) IsCached() (bool, error) {
	exists, err := client.Exists(JOB_CACHE_PREFIX + j.ID).Result()
	if err != nil {
		return false, errors.New("Failed to check existence of job " + j.ID + " : " + err.Error())
	}
	if !exists {
		return false, nil
	}

	return true, nil
}

// Method to load cached job result from redis.
func (j *Job) GetCache() error {
	// check if cached or not first
	cached, err := j.IsCached()
	if err != nil {
		return err
	}
	if !cached {
		return errors.New("Failed to get cache of job " + j.ID + " : job is not cached")
	}
	j.ResultJSON, err = client.Get(JOB_CACHE_PREFIX + j.ID).Result()
	if err != nil {
		return errors.New("Failed to get cache of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Function to get cache of job result json by job id.
// Returns existence, the JSON, and error.
func GetCache(id string) (bool, string, error) {
	j := &Job{
		ID: id,
	}

	exists, err := j.IsCached()
	if err != nil {
		return false, "", err
	}
	if !exists {
		return false, "", nil
	}

	err = j.GetCache()
	if err != nil {
		return false, "", err
	}

	return true, j.ResultJSON, nil
}
<commit_msg>retry now set status to 0 again<commit_after>package goq

import (
	"encoding/json"
	"errors"
	"time"
)

type Status struct {
	Code     uint8
	Progress uint8
}

type Job struct {
	ID         string
	JSON       string
	ResultJSON string
	Status     *Status
	processor  Processor
	queueName  string
}

// Method to requeue job, will remove this job from failed job if exists.
func (j *Job) Retry() error {
	err := client.SRem(JOB_FAILED_PREFIX+j.queueName, j.JSON).Err()
	if err != nil {
		return errors.New("Failed to remove from failed jobs set of job " + j.ID + " : " + err.Error())
	}
	// set status to 0 again
	err = j.SetStatus(0, 0)
	if err != nil {
		return err
	}
	j.processor(j)

	return nil
}

// Method to fail job to failed job set.
// Also set the status Code to 3
func (j *Job) Fail() error {
	err := client.SAdd(JOB_FAILED_PREFIX+j.queueName, j.JSON).Err()
	if err != nil {
		return errors.New("Failed to add to failed jobs set of job " + j.ID + " : " + err.Error())
	}
	err = j.SetStatus(3, 0)
	if err != nil {
		return err
	}

	return nil
}

// Method to set this job Status locally and to redis.
// Code must be 0 for waiting, 1 for working, 2 for completed, and 3 for failed.
// Progress must be 0-100
func (j *Job) SetStatus(code, progress uint8) error {
	if code < 0 || code > 3 {
		return errors.New("Failed to set status code of job " + j.ID + " : code must be 0-3")
	}
	if progress < 0 || progress > 100 {
		return errors.New("Failed to set status progress of job " + j.ID + " : progress must be 0-100")
	}

	j.Status.Code = code
	j.Status.Progress = progress

	statusJSON, err := json.Marshal(j.Status)
	if err != nil {
		return errors.New("Failed to marshal status of job " + j.ID + " : " + err.Error())
	}

	err = client.Set(JOB_STATUS_PREFIX+j.ID, string(statusJSON), 0).Err()
	if err != nil {
		return errors.New("Failed to set status of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to update this job Status from redis.
func (j *Job) GetStatus() error {
	dataJSON, err := client.Get(JOB_STATUS_PREFIX + j.ID).Result()
	if err != nil {
		return errors.New("Failed to get status of job " + j.ID + " : " + err.Error())
	}

	err = json.Unmarshal([]byte(dataJSON), j.Status)
	if err != nil {
		return errors.New("Failed to unmarshal status of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to save this job result to redis with ttl in seconds.
func (j *Job) SetCache(ttl time.Duration) error {
	err := client.Set(JOB_CACHE_PREFIX+j.ID, j.ResultJSON, ttl).Err()
	if err != nil {
		return errors.New("Failed to set cache of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Method to check if this job result is cached.
func (j *Job) IsCached() (bool, error) {
	exists, err := client.Exists(JOB_CACHE_PREFIX + j.ID).Result()
	if err != nil {
		return false, errors.New("Failed to check existence of job " + j.ID + " : " + err.Error())
	}
	if !exists {
		return false, nil
	}

	return true, nil
}

// Method to load cached job result from redis.
func (j *Job) GetCache() error {
	// check if cached or not first
	cached, err := j.IsCached()
	if err != nil {
		return err
	}
	if !cached {
		return errors.New("Failed to get cache of job " + j.ID + " : job is not cached")
	}
	j.ResultJSON, err = client.Get(JOB_CACHE_PREFIX + j.ID).Result()
	if err != nil {
		return errors.New("Failed to get cache of job " + j.ID + " : " + err.Error())
	}

	return nil
}

// Function to get cache of job result json by job id.
// Returns existence, the JSON, and error.
func GetCache(id string) (bool, string, error) {
	j := &Job{
		ID: id,
	}

	exists, err := j.IsCached()
	if err != nil {
		return false, "", err
	}
	if !exists {
		return false, "", nil
	}

	err = j.GetCache()
	if err != nil {
		return false, "", err
	}

	return true, j.ResultJSON, nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

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

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	err := job.runImpl(ctx)
	switch err.(type) {
	case InvalidJobError:
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
	}
	return err
}

func (job *Job) runImpl(ctx context.Context) error {
	job.notification.notify(PROCESSING, job.message.MessageId(), "info")
	defer job.withNotify(CLEANUP, job.clearWorkspace)() // Call clearWorkspace even if setupWorkspace retuns error

	err := job.message.Validate()
	if err != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, err)
		return nil
	}

	err = job.setupWorkspace()
	if err != nil {
		return err
	}

	err = job.withNotify(PREPARING, job.setupDownloadFiles)()
	if err != nil {
		return err
	}

	err = job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	if job.workspace != "" {
		return os.RemoveAll(job.workspace)
	}
	return nil
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, &InvalidJobError{err.Error()}
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err := job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<commit_msg>:+1: Extract job.prepare from job.runImpl<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"

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

type (
	CommandConfig struct {
		Template []string            `json:"-"`
		Options  map[string][]string `json:"options,omitempty"`
		Dryrun   bool                `json:"dryrun,omitempty"`
	}

	Job struct {
		config *CommandConfig
		// https://godoc.org/google.golang.org/genproto/googleapis/pubsub/v1#ReceivedMessage
		message      *JobMessage
		notification *ProgressNotification
		storage      Storage

		// These are set at at setupWorkspace
		workspace     string
		downloads_dir string
		uploads_dir   string

		// These are set at setupDownloadFiles
		downloadFileMap     map[string]string
		remoteDownloadFiles interface{}
		localDownloadFiles  interface{}

		cmd *exec.Cmd
	}
)

func (job *Job) run(ctx context.Context) error {
	err := job.runImpl(ctx)
	switch err.(type) {
	case InvalidJobError:
		err := job.withNotify(CANCELLING, job.message.Ack)()
		if err != nil {
			return err
		}
	}
	return err
}

func (job *Job) runImpl(ctx context.Context) error {
	job.notification.notify(PROCESSING, job.message.MessageId(), "info")
	defer job.withNotify(CLEANUP, job.clearWorkspace)() // Call clearWorkspace even if setupWorkspace retuns error

	err := job.withNotify(PREPARING, job.prepare)()
	if err != nil {
		return err
	}

	go job.message.sendMADPeriodically()
	defer job.message.Done()

	err = job.withNotify(DOWNLOADING, job.downloadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(EXECUTING, job.execute)()
	if err != nil {
		return err
	}

	err = job.withNotify(UPLOADING, job.uploadFiles)()
	if err != nil {
		return err
	}

	err = job.withNotify(ACKSENDING, job.message.Ack)()
	if err != nil {
		return err
	}

	return err
}

func (job *Job) withNotify(progress int, f func() error) func() error {
	msg_id := job.message.MessageId()
	return func() error {
		job.notification.notify(progress, msg_id, "info")
		err := f()
		if err != nil {
			job.notification.notify(progress+2, msg_id, "error")
			return err
		}
		job.notification.notify(progress+1, msg_id, "info")
		return nil
	}
}

func (job *Job) prepare() error {
	err := job.message.Validate()
	if err != nil {
		log.Printf("Invalid Message: MessageId: %v, Message: %v, error: %v\n", job.message.MessageId(), job.message.raw.Message, err)
		return nil
	}

	err = job.setupWorkspace()
	if err != nil {
		return err
	}

	err = job.setupDownloadFiles()
	if err != nil {
		return err
	}

	err = job.build()
	if err != nil {
		log.Fatalf("Command build Error template: %v msg: %v cause of %v\n", job.config.Template, job.message, err)
		return err
	}
	return nil
}

func (job *Job) setupWorkspace() error {
	dir, err := ioutil.TempDir("", "workspace")
	if err != nil {
		log.Fatal(err)
		return err
	}

	subdirs := []string{
		filepath.Join(dir, "downloads"),
		filepath.Join(dir, "uploads"),
	}
	for _, subdir := range subdirs {
		err := os.MkdirAll(subdir, 0700)
		if err != nil {
			return err
		}
	}
	job.workspace = dir
	job.downloads_dir = subdirs[0]
	job.uploads_dir = subdirs[1]
	return nil
}

func (job *Job) clearWorkspace() error {
	if job.workspace != "" {
		return os.RemoveAll(job.workspace)
	}
	return nil
}

func (job *Job) setupDownloadFiles() error {
	job.downloadFileMap = map[string]string{}
	job.remoteDownloadFiles = job.message.DownloadFiles()
	objects := job.flatten(job.remoteDownloadFiles)
	remoteUrls := []string{}
	for _, obj := range objects {
		switch obj.(type) {
		case string:
			remoteUrls = append(remoteUrls, obj.(string))
		default:
			log.Printf("Invalid download file URL: %v [%T]", obj, obj)
		}
	}
	for _, remote_url := range remoteUrls {
		url, err := url.Parse(remote_url)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remote_url, err)
			return err
		}
		urlstr := fmt.Sprintf("gs://%v%v", url.Host, url.Path)
		destPath := filepath.Join(job.downloads_dir, url.Host, url.Path)
		job.downloadFileMap[urlstr] = destPath
	}
	job.localDownloadFiles = job.copyWithFileMap(job.remoteDownloadFiles)
	return nil
}

func (job *Job) copyWithFileMap(obj interface{}) interface{} {
	switch obj.(type) {
	case map[string]interface{}:
		result := map[string]interface{}{}
		for k, v := range obj.(map[string]interface{}) {
			result[k] = job.copyWithFileMap(v)
		}
		return result
	case []interface{}:
		result := []interface{}{}
		for _, v := range obj.([]interface{}) {
			result = append(result, job.copyWithFileMap(v))
		}
		return result
	case string:
		return job.downloadFileMap[obj.(string)]
	default:
		return obj
	}
}

func (job *Job) buildVariable() *Variable {
	return &Variable{
		data: map[string]interface{}{
			"workspace":             job.workspace,
			"downloads_dir":         job.downloads_dir,
			"uploads_dir":           job.uploads_dir,
			"download_files":        job.localDownloadFiles,
			"local_download_files":  job.localDownloadFiles,
			"remote_download_files": job.remoteDownloadFiles,
			"attrs":                 job.message.raw.Message.Attributes,
			"attributes":            job.message.raw.Message.Attributes,
			"data":                  job.message.raw.Message.Data,
		},
	}
}

func (job *Job) build() error {
	v := job.buildVariable()

	values, err := job.extract(v, job.config.Template)
	if err != nil {
		return err
	}
	if len(job.config.Options) > 0 {
		key := strings.Join(values, " ")
		t := job.config.Options[key]
		if t == nil {
			t = job.config.Options["default"]
		}
		if t != nil {
			values, err = job.extract(v, t)
			if err != nil {
				return err
			}
		}
	}
	job.cmd = exec.Command(values[0], values[1:]...)
	return nil
}

func (job *Job) extract(v *Variable, values []string) ([]string, error) {
	result := []string{}
	for _, src := range values {
		extracted, err := v.expand(src)
		if err != nil {
			return nil, &InvalidJobError{err.Error()}
		}
		vals := strings.Split(extracted, v.separator)
		for _, val := range vals {
			result = append(result, val)
		}
	}
	return result, nil
}

func (job *Job) downloadFiles() error {
	for remoteURL, destPath := range job.downloadFileMap {
		url, err := url.Parse(remoteURL)
		if err != nil {
			log.Fatalf("Invalid URL: %v because of %v\n", remoteURL, err)
			return err
		}

		dir := path.Dir(destPath)
		err = os.MkdirAll(dir, 0700)
		if err != nil {
			return err
		}

		err = job.storage.Download(url.Host, url.Path[1:], destPath)
		if err != nil {
			return err
		}
	}
	return nil
}

func (job *Job) execute() error {
	var out bytes.Buffer
	job.cmd.Stdout = &out
	job.cmd.Stderr = &out
	log.Printf("EXECUTE running: %v\n", job.cmd)
	err := job.cmd.Run()
	if err != nil {
		log.Printf("Command Error: cmd: %v cause of %v\n%v\n", job.cmd, err, out.String())
		return err
	}
	return nil
}

func (job *Job) uploadFiles() error {
	localPaths, err := job.listFiles(job.uploads_dir)
	if err != nil {
		return err
	}
	for _, localPath := range localPaths {
		relPath, err := filepath.Rel(job.uploads_dir, localPath)
		if err != nil {
			log.Fatalf("Error getting relative path of %v: %v\n", localPath, err)
			return err
		}
		sep := string([]rune{os.PathSeparator})
		parts := strings.Split(relPath, sep)
		bucket := parts[0]
		object := strings.Join(parts[1:], sep)
		err = job.storage.Upload(bucket, object, localPath)
		if err != nil {
			log.Fatalf("Error uploading %v to gs://%v/%v: %v\n", localPath, bucket, object, err)
			return err
		}
	}
	return nil
}

func (job *Job) listFiles(dir string) ([]string, error) {
	result := []string{}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			result = append(result, path)
		}
		return nil
	})
	if err != nil {
		log.Fatalf("Error listing upload files: %v\n", err)
		return nil, err
	}
	return result, nil
}

func (job *Job) flatten(obj interface{}) []interface{} {
	// Support only unmarshalled object from JSON
	// See https://golang.org/pkg/encoding/json/#Unmarshal also
	switch obj.(type) {
	case []interface{}:
		res := []interface{}{}
		for _, i := range obj.([]interface{}) {
			switch i.(type) {
			case bool, float64, string, nil:
				res = append(res, i)
			default:
				for _, j := range job.flatten(i) {
					res = append(res, j)
				}
			}
		}
		return res
	case map[string]interface{}:
		values := []interface{}{}
		for _, val := range obj.(map[string]interface{}) {
			values = append(values, val)
		}
		return job.flatten(values)
	default:
		return []interface{}{obj}
	}
}
<|endoftext|>


<commit_before>package dummy

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju/go/environs"
	"launchpad.net/juju/go/schema"
	"launchpad.net/juju/go/state"
	"sync"
	"errors"
)

type Operation struct {
	Kind        OperationKind
	EnvironName string
}

// Operation represents an action on the dummy provider.
type OperationKind int

const (
	_ OperationKind = iota
	OpBootstrap
	OpDestroy
	OpStartInstance
	OpStopInstances
)

var kindNames = []string{
	0: "OpUninitialized",
	OpBootstrap: "OpBootstrap",
	OpDestroy: "OpDestroy",
	OpStartInstance: "OpStartInstance",
	OpStopInstances: "OpStopInstances",
}

func (k OperationKind) String() string {
	return kindNames[k]
}

// environProvider represents the dummy provider.  There is only ever one
// instance of this type (providerInstance)
type environProvider struct {
	mu    sync.Mutex
	state *environState
	ops   chan<- Operation
}

// environState represents the state of an environment.
// It can be shared between several environ values,
// so that a given environment can be opened several times.
type environState struct {
	mu           sync.Mutex
	maxId        int // maximum instance id allocated so far.
	insts        map[string]*instance
	files        map[string][]byte
	bootstrapped bool
}

var providerInstance environProvider

// discardOperations discards all Operations written to it.
var discardOperations chan<- Operation

func init() {
	environs.RegisterProvider("dummy", &providerInstance)

	// Prime the first ops channel, so that naive clients can use
	// the testing environment by simply importing it.
	c := make(chan Operation)
	go func() {
		for _ = range c {
		}
	}()
	discardOperations = c
	Reset(discardOperations)
}

// Reset closes any previously registered operation channel,
// cleans the environment state, and registers c to receive
// notifications of operations performed on newly opened
// dummy environments. All opened environments after a Reset
// will share the same underlying state (instances, etc).
//
// The configuration YAML for the testing environment
// must specify a "zookeeper" property with a boolean
// value. If this is true, a zookeeper instance will be started
// the first time StateInfo is called on a newly reset environment.
// NOTE: ZooKeeper isn't actually being started yet.
//
// The configuration data also accepts a "broken" property
// of type boolean. If this is non-empty, any operation on
// after the environment has been opened will return
// the error "broken environment", and will also log that.
//
// The DNS name of instances is the same as the Id,
// with ".dns" appended.
func Reset(c chan<- Operation) {
	providerInstance.reset(c)
}

func (e *environProvider) reset(c chan <-Operation) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if c == nil {
		c = discardOperations
	}
	if ops := e.ops; ops != discardOperations && ops != nil {
		close(ops)
	}
	e.ops = c
	e.state = &environState{
		insts: make(map[string]*instance),
		files: make(map[string][]byte),
	}
}

func (e *environProvider) ConfigChecker() schema.Checker {
	return schema.FieldMap(
		schema.Fields{
			"type":      schema.Const("dummy"),
			"zookeeper": schema.Const(false), // TODO
			"broken": schema.Bool(),
		},
		[]string{
			"broken",
		},
	)
}

func (e *environProvider) Open(name string, attributes interface{}) (environs.Environ, error) {
	e.mu.Lock()
	defer e.mu.Unlock()
	cfg := attributes.(schema.MapType)

	env := &environ{
		name:      name,
		zookeeper: cfg["zookeeper"].(bool),
		ops:       e.ops,
		state:     e.state,
	}
	env.broken, _ = cfg["broken"].(bool)
	return env, nil
}

type environ struct {
	ops       chan<- Operation
	name      string
	state     *environState
	broken bool
	zookeeper bool
}

var errBroken = errors.New("broken environment")

// EnvironName returns the name of the environment,
// which must be opened from a dummy environment.
func EnvironName(e environs.Environ) string {
	return e.(*environ).name
}

func (e *environ) Bootstrap() error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpBootstrap, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	if e.state.bootstrapped {
		return fmt.Errorf("environment is already bootstrapped")
	}
	e.state.bootstrapped = true
	return nil
}

func (e *environ) StateInfo() (*state.Info, error) {
	if e.broken {
		return nil, errBroken
	}
	// TODO start a zookeeper server
	return &state.Info{Addrs: []string{"3.2.1.0:0"}}, nil
}

func (e *environ) Destroy([]environs.Instance) error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpDestroy, e.name}
	e.state.mu.Lock()
	e.state.bootstrapped = false
	e.state.mu.Unlock()
	return nil
}

func (e *environ) StartInstance(machineId int, _ *state.Info) (environs.Instance, error) {
	if e.broken {
		return nil, errBroken
	}
	e.ops <- Operation{OpStartInstance, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	i := &instance{
		id: fmt.Sprintf("%s-%d", e.name, e.state.maxId),
	}
	e.state.insts[i.id] = i
	e.state.maxId++
	return i, nil
}

func (e *environ) StopInstances(is []environs.Instance) error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpStopInstances, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	for _, i := range is {
		delete(e.state.insts, i.(*instance).id)
	}
	return nil
}

func (e *environ) Instances(ids []string) (insts []environs.Instance, err error) {
	if e.broken {
		return nil, errBroken
	}
	if len(ids) == 0 {
		return nil, nil
	}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	notFound := 0
	for _, id := range ids {
		inst := e.state.insts[id]
		if inst == nil {
			err = environs.ErrPartialInstances
			notFound++
		}
		insts = append(insts, inst)
	}
	if notFound == len(ids) {
		return nil, environs.ErrNoInstances
	}
	return
}

func (e *environ) PutFile(name string, r io.Reader, length int64) error {
	if e.broken {
		return errBroken
	}
	var buf bytes.Buffer
	_, err := io.Copy(&buf, r)
	if err != nil {
		return err
	}
	e.state.mu.Lock()
	e.state.files[name] = buf.Bytes()
	e.state.mu.Unlock()
	return nil
}

func (e *environ) GetFile(name string) (io.ReadCloser, error) {
	if e.broken {
		return nil, errBroken
	}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	data, ok := e.state.files[name]
	if !ok {
		return nil, fmt.Errorf("file %q not found", name)
	}
	return ioutil.NopCloser(bytes.NewBuffer(data)), nil
}

func (e *environ) RemoveFile(name string) error {
	if e.broken {
		return errBroken
	}
	e.state.mu.Lock()
	delete(e.state.files, name)
	e.state.mu.Unlock()
	return nil
}

type instance struct {
	id string
}

func (m *instance) Id() string {
	return m.id
}

func (m *instance) DNSName() (string, error) {
	return m.id + ".dns", nil
}

func (m *instance) WaitDNSName() (string, error) {
	return m.DNSName()
}
<commit_msg>go fmt<commit_after>package dummy

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"launchpad.net/juju/go/environs"
	"launchpad.net/juju/go/schema"
	"launchpad.net/juju/go/state"
	"sync"
)

type Operation struct {
	Kind        OperationKind
	EnvironName string
}

// Operation represents an action on the dummy provider.
type OperationKind int

const (
	_ OperationKind = iota
	OpBootstrap
	OpDestroy
	OpStartInstance
	OpStopInstances
)

var kindNames = []string{
	0:               "OpUninitialized",
	OpBootstrap:     "OpBootstrap",
	OpDestroy:       "OpDestroy",
	OpStartInstance: "OpStartInstance",
	OpStopInstances: "OpStopInstances",
}

func (k OperationKind) String() string {
	return kindNames[k]
}

// environProvider represents the dummy provider.  There is only ever one
// instance of this type (providerInstance)
type environProvider struct {
	mu    sync.Mutex
	state *environState
	ops   chan<- Operation
}

// environState represents the state of an environment.
// It can be shared between several environ values,
// so that a given environment can be opened several times.
type environState struct {
	mu           sync.Mutex
	maxId        int // maximum instance id allocated so far.
	insts        map[string]*instance
	files        map[string][]byte
	bootstrapped bool
}

var providerInstance environProvider

// discardOperations discards all Operations written to it.
var discardOperations chan<- Operation

func init() {
	environs.RegisterProvider("dummy", &providerInstance)

	// Prime the first ops channel, so that naive clients can use
	// the testing environment by simply importing it.
	c := make(chan Operation)
	go func() {
		for _ = range c {
		}
	}()
	discardOperations = c
	Reset(discardOperations)
}

// Reset closes any previously registered operation channel,
// cleans the environment state, and registers c to receive
// notifications of operations performed on newly opened
// dummy environments. All opened environments after a Reset
// will share the same underlying state (instances, etc).
//
// The configuration YAML for the testing environment
// must specify a "zookeeper" property with a boolean
// value. If this is true, a zookeeper instance will be started
// the first time StateInfo is called on a newly reset environment.
// NOTE: ZooKeeper isn't actually being started yet.
//
// The configuration data also accepts a "broken" property
// of type boolean. If this is non-empty, any operation on
// after the environment has been opened will return
// the error "broken environment", and will also log that.
//
// The DNS name of instances is the same as the Id,
// with ".dns" appended.
func Reset(c chan<- Operation) {
	providerInstance.reset(c)
}

func (e *environProvider) reset(c chan<- Operation) {
	e.mu.Lock()
	defer e.mu.Unlock()
	if c == nil {
		c = discardOperations
	}
	if ops := e.ops; ops != discardOperations && ops != nil {
		close(ops)
	}
	e.ops = c
	e.state = &environState{
		insts: make(map[string]*instance),
		files: make(map[string][]byte),
	}
}

func (e *environProvider) ConfigChecker() schema.Checker {
	return schema.FieldMap(
		schema.Fields{
			"type":      schema.Const("dummy"),
			"zookeeper": schema.Const(false), // TODO
			"broken":    schema.Bool(),
		},
		[]string{
			"broken",
		},
	)
}

func (e *environProvider) Open(name string, attributes interface{}) (environs.Environ, error) {
	e.mu.Lock()
	defer e.mu.Unlock()
	cfg := attributes.(schema.MapType)

	env := &environ{
		name:      name,
		zookeeper: cfg["zookeeper"].(bool),
		ops:       e.ops,
		state:     e.state,
	}
	env.broken, _ = cfg["broken"].(bool)
	return env, nil
}

type environ struct {
	ops       chan<- Operation
	name      string
	state     *environState
	broken    bool
	zookeeper bool
}

var errBroken = errors.New("broken environment")

// EnvironName returns the name of the environment,
// which must be opened from a dummy environment.
func EnvironName(e environs.Environ) string {
	return e.(*environ).name
}

func (e *environ) Bootstrap() error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpBootstrap, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	if e.state.bootstrapped {
		return fmt.Errorf("environment is already bootstrapped")
	}
	e.state.bootstrapped = true
	return nil
}

func (e *environ) StateInfo() (*state.Info, error) {
	if e.broken {
		return nil, errBroken
	}
	// TODO start a zookeeper server
	return &state.Info{Addrs: []string{"3.2.1.0:0"}}, nil
}

func (e *environ) Destroy([]environs.Instance) error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpDestroy, e.name}
	e.state.mu.Lock()
	e.state.bootstrapped = false
	e.state.mu.Unlock()
	return nil
}

func (e *environ) StartInstance(machineId int, _ *state.Info) (environs.Instance, error) {
	if e.broken {
		return nil, errBroken
	}
	e.ops <- Operation{OpStartInstance, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	i := &instance{
		id: fmt.Sprintf("%s-%d", e.name, e.state.maxId),
	}
	e.state.insts[i.id] = i
	e.state.maxId++
	return i, nil
}

func (e *environ) StopInstances(is []environs.Instance) error {
	if e.broken {
		return errBroken
	}
	e.ops <- Operation{OpStopInstances, e.name}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	for _, i := range is {
		delete(e.state.insts, i.(*instance).id)
	}
	return nil
}

func (e *environ) Instances(ids []string) (insts []environs.Instance, err error) {
	if e.broken {
		return nil, errBroken
	}
	if len(ids) == 0 {
		return nil, nil
	}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	notFound := 0
	for _, id := range ids {
		inst := e.state.insts[id]
		if inst == nil {
			err = environs.ErrPartialInstances
			notFound++
		}
		insts = append(insts, inst)
	}
	if notFound == len(ids) {
		return nil, environs.ErrNoInstances
	}
	return
}

func (e *environ) PutFile(name string, r io.Reader, length int64) error {
	if e.broken {
		return errBroken
	}
	var buf bytes.Buffer
	_, err := io.Copy(&buf, r)
	if err != nil {
		return err
	}
	e.state.mu.Lock()
	e.state.files[name] = buf.Bytes()
	e.state.mu.Unlock()
	return nil
}

func (e *environ) GetFile(name string) (io.ReadCloser, error) {
	if e.broken {
		return nil, errBroken
	}
	e.state.mu.Lock()
	defer e.state.mu.Unlock()
	data, ok := e.state.files[name]
	if !ok {
		return nil, fmt.Errorf("file %q not found", name)
	}
	return ioutil.NopCloser(bytes.NewBuffer(data)), nil
}

func (e *environ) RemoveFile(name string) error {
	if e.broken {
		return errBroken
	}
	e.state.mu.Lock()
	delete(e.state.files, name)
	e.state.mu.Unlock()
	return nil
}

type instance struct {
	id string
}

func (m *instance) Id() string {
	return m.id
}

func (m *instance) DNSName() (string, error) {
	return m.id + ".dns", nil
}

func (m *instance) WaitDNSName() (string, error) {
	return m.DNSName()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strings"
	"unicode"
)

type tokenType int

const (
	tokenText tokenType = iota
	tokenTagOpen
	tokenTagName
	tokenTagArguments
	tokenTagClose
	tokenSpace
	tokenEof
)

const (
	delimOpenTag      = "{%"
	delimCloseTag     = "%}"
	delimOpenPrint    = "{{"
	delimClosePrint   = "}}"
	delimOpenComment  = "{#"
	delimCloseComment = "#}"
)

type lexerState int

const (
	stateData lexerState = iota
	stateBlock
	stateVar
	stateString
	stateInterpolation
)

type token struct {
	value     string
	pos       int
	tokenType tokenType
}

type tokenStream []token

type stateFn func(*lexer) stateFn

type lexer struct {
	pos     int // The position of the last emission
	current int // The position of the cursor
	input   string
	tokens  tokenStream
	state   stateFn
}

func (lex *lexer) tokenize(code string) tokenStream {
	lex.pos = 0
	lex.current = 0
	lex.input = code
	lex.tokens = tokenStream{}

	for lex.state = lexData; lex.state != nil; {
		lex.state = lex.state(lex)
	}

	return lex.tokens
}

func (lex *lexer) next() string {
	fmt.Println(lex.current, len(lex.input))
	if lex.current+1 >= len(lex.input) {
		return ""
	}

	lex.current += 1

	return string(lex.input[lex.current])
}

func (lex *lexer) backup() {
	if lex.current <= lex.pos {
		return
	}

	fmt.Println("Backing up")
	lex.current -= 1
}

func (lex *lexer) peek() string {
	str := lex.next()
	lex.backup()

	return str
}

func (lex *lexer) emit(t tokenType) {
	fmt.Println(lex.pos, lex.current, len(lex.input))
	val := lex.input[lex.pos:lex.current]
	tok := token{val, lex.pos, t}
	fmt.Println(tok)
	lex.tokens = append(lex.tokens, tok)
	lex.pos = lex.current
}

func (lex *lexer) consumeWhitespace() {
	fmt.Println("Consuming whitespace")
	for {
		str := lex.input[lex.current:lex.current+1]
		fmt.Println("A space?", str)
		if (!isSpace(str)) {
			break
		}
		lex.next()
	}

	fmt.Println("Emitting a space token")
	lex.emit(tokenSpace)
}

func lexData(lex *lexer) stateFn {
	for {
		switch {
		case strings.HasPrefix(lex.input[lex.current:], delimOpenTag):
			if lex.current > lex.pos {
				lex.emit(tokenText)
			}
			return lexTagOpen
		}

		if lex.next() == "" {
			break
		}
	}

	if lex.current > lex.pos {
		lex.emit(tokenText)
	}

	lex.emit(tokenEof)

	return nil
}

func lexTagOpen(lex *lexer) stateFn {
	lex.current += len(delimOpenTag)
	lex.emit(tokenTagOpen)

	return lexTagName
}

func lexTagName(lex *lexer) stateFn {
	lex.consumeWhitespace()
	for {
		str := lex.next();
		if !isAlphaNumeric(str) {
			break
		}
	}

	lex.emit(tokenTagName)

	return lexTagArguments
}

func lexTagArguments(lex *lexer) stateFn {
	lex.consumeWhitespace()
	closePos := strings.Index(lex.input[lex.current:], delimCloseTag)
	if closePos > 0 {
		lex.current += closePos
		lex.emit(tokenTagArguments)
	}

	return lexTagClose
}

func lexTagClose(lex *lexer) stateFn {
	lex.current += len(delimCloseTag)
	lex.emit(tokenTagClose)

	return lexData
}

func isSpace(str string) bool {
	return str == " " || str == "\t"
}

func isAlphaNumeric(str string) bool {
	for _, s := range str {
		if string(s) != "_" && !unicode.IsLetter(s) && !unicode.IsDigit(s) {
			return false
		}
	}

	return true
}

func main() {
	data := "<html><head><title>{% block title %}{% endblock %}"

	lex := lexer{}

	fmt.Println(lex.tokenize(data))
}
<commit_msg>Added String() method to token<commit_after>package main

import (
	"fmt"
	"strings"
	"unicode"
)

type tokenType int

const (
	tokenText tokenType = iota
	tokenTagOpen
	tokenTagName
	tokenTagArguments
	tokenTagClose
	tokenSpace
	tokenEof
)

var names = map[tokenType]string{
	tokenText:         "TEXT",
	tokenTagOpen:      "TAG_OPEN",
	tokenTagName:      "TAG_NAME",
	tokenTagArguments: "TAG_ARGS",
	tokenTagClose:     "TAG_CLOSE",
	tokenEof:          "EOF",
}

const (
	delimOpenTag      = "{%"
	delimCloseTag     = "%}"
	delimOpenPrint    = "{{"
	delimClosePrint   = "}}"
	delimOpenComment  = "{#"
	delimCloseComment = "#}"
)

type lexerState int

const (
	stateData lexerState = iota
	stateBlock
	stateVar
	stateString
	stateInterpolation
)

type token struct {
	value     string
	pos       int
	tokenType tokenType
}

func (tok token) String() string {
	return fmt.Sprintf("{%s '%s' %d}\n", names[tok.tokenType], tok.value, tok.pos)
}

type tokenStream []token

type stateFn func(*lexer) stateFn

type lexer struct {
	pos     int // The position of the last emission
	current int // The position of the cursor
	input   string
	tokens  tokenStream
	state   stateFn
}

func (lex *lexer) tokenize(code string) tokenStream {
	lex.pos = 0
	lex.current = 0
	lex.input = code
	lex.tokens = tokenStream{}

	for lex.state = lexData; lex.state != nil; {
		lex.state = lex.state(lex)
	}

	return lex.tokens
}

func (lex *lexer) next() string {
	fmt.Println(lex.current, len(lex.input))
	if lex.current+1 >= len(lex.input) {
		return ""
	}

	lex.current += 1

	return string(lex.input[lex.current])
}

func (lex *lexer) backup() {
	if lex.current <= lex.pos {
		return
	}

	fmt.Println("Backing up")
	lex.current -= 1
}

func (lex *lexer) peek() string {
	str := lex.next()
	lex.backup()

	return str
}

func (lex *lexer) emit(t tokenType) {
	fmt.Println(lex.pos, lex.current, len(lex.input))
	val := lex.input[lex.pos:lex.current]
	tok := token{val, lex.pos, t}
	fmt.Println(tok)
	lex.tokens = append(lex.tokens, tok)
	lex.pos = lex.current
}

func (lex *lexer) consumeWhitespace() {
	fmt.Println("Consuming whitespace")
	for {
		str := lex.input[lex.current:lex.current+1]
		fmt.Println("A space?", str)
		if (!isSpace(str)) {
			break
		}
		lex.next()
	}

	fmt.Println("Emitting a space token")
	lex.emit(tokenSpace)
}

func lexData(lex *lexer) stateFn {
	for {
		switch {
		case strings.HasPrefix(lex.input[lex.current:], delimOpenTag):
			if lex.current > lex.pos {
				lex.emit(tokenText)
			}
			return lexTagOpen
		}

		if lex.next() == "" {
			break
		}
	}

	if lex.current > lex.pos {
		lex.emit(tokenText)
	}

	lex.emit(tokenEof)

	return nil
}

func lexTagOpen(lex *lexer) stateFn {
	lex.current += len(delimOpenTag)
	lex.emit(tokenTagOpen)

	return lexTagName
}

func lexTagName(lex *lexer) stateFn {
	lex.consumeWhitespace()
	for {
		str := lex.next()
		if !isAlphaNumeric(str) {
			break
		}
	}

	lex.emit(tokenTagName)

	return lexTagArguments
}

func lexTagArguments(lex *lexer) stateFn {
	lex.consumeWhitespace()
	closePos := strings.Index(lex.input[lex.current:], delimCloseTag)
	if closePos > 0 {
		lex.current += closePos
		lex.emit(tokenTagArguments)
	}

	return lexTagClose
}

func lexTagClose(lex *lexer) stateFn {
	lex.current += len(delimCloseTag)
	lex.emit(tokenTagClose)

	return lexData
}

func isSpace(str string) bool {
	return str == " " || str == "\t"
}

func isAlphaNumeric(str string) bool {
	for _, s := range str {
		if string(s) != "_" && !unicode.IsLetter(s) && !unicode.IsDigit(s) {
			return false
		}
	}

	return true
}

func main() {
	data := "<html><head><title>{% block title %}{% endblock %}"

	lex := lexer{}

	fmt.Println(lex.tokenize(data))
}
<|endoftext|>


<commit_before>package databaseService

import (
	"database/sql"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"github.com/hacksoc-manchester/www/helpers/validator"
	"os"
)

type userEntry struct {
	FirstName            string
	LastName             string
	Email                string
	SubscribedToArticles bool
	SubscribedToEvents   bool
}

var db *sql.DB

func init() {
	db, _ = sql.Open("mysql", os.Getenv("MYSQL_CONNECTION_STRING"))

	if err := db.Ping(); err != nil {
		panic(err.Error())
	}
}

// CreateUser adds the specified entry to the User table.
func CreateUser(firstName, lastName, email string, subscribedToArticles, subscribedToEvents bool) error {
	if !validator.IsValidName(firstName) {
		return errors.New(`Name "` + firstName + `" is not valid.`)
	}

	if !validator.IsValidName(lastName) {
		return errors.New(`Name "` + lastName + `" is not valid.`)
	}

	if !validator.IsValidEmail(email) {
		return errors.New(`Email "` + email + `" is not valid.`)
	}

	stmt, _ := db.Prepare(`insert into User(FirstName, LastName, Email, SubscribedToArticles, SubscribedToEvents)
		values (?, ?, ?, ?, ?)`)

	if _, err := stmt.Exec(firstName, lastName, email, subscribedToArticles, subscribedToEvents); err != nil {
		return errors.New(`Email "` + email + `" is already subscribed.`)
	}

	return nil
}

// GetUser retrieves the user with the specified email.
func GetUser(email string) (*userEntry, error) {
	stmt, _ := db.Prepare("select * from User where Email = ?")
	user := new(userEntry)
	err := stmt.QueryRow(email).Scan(
		&user.FirstName,
		&user.LastName,
		&user.Email,
		&user.SubscribedToArticles,
		&user.SubscribedToEvents)

	if err != nil {
		return nil, errors.New(`Could not find user with email "` + email + `".`)
	}

	return user, nil
}

// ExistsUser determines whether a user with the specified email exists.
func ExistsUser(email string) bool {
	user, _ := GetUser(email)

	return user != nil
}

// DeleteUser removes the specified entry from the User table.
func DeleteUser(email string) error {
	stmt, _ := db.Prepare("delete from User where Email = ?")

	if _, err := stmt.Exec(email); err != nil {
		return errors.New(`Could not delete user with email "` + email + `".`)
	}

	return nil
}
<commit_msg>Website working with no connection string<commit_after>package databaseService

import (
	"database/sql"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"github.com/hacksoc-manchester/www/helpers/validator"
	"log"
	"os"
)

type userEntry struct {
	FirstName            string
	LastName             string
	Email                string
	SubscribedToArticles bool
	SubscribedToEvents   bool
}

var db *sql.DB

func init() {

	if os.Getenv("MYSQL_CONNECTION_STRING") == "" {
		log.Println("MYSQL_CONNECTION_STRING unassigned. Signing up will not work!")
		return
	}

	db, _ = sql.Open("mysql", os.Getenv("MYSQL_CONNECTION_STRING"))

	if err := db.Ping(); err != nil {
		panic(err)
	}
}

// CreateUser adds the specified entry to the User table.
func CreateUser(firstName, lastName, email string, subscribedToArticles, subscribedToEvents bool) error {
	if !validator.IsValidName(firstName) {
		return errors.New(`Name "` + firstName + `" is not valid.`)
	}

	if !validator.IsValidName(lastName) {
		return errors.New(`Name "` + lastName + `" is not valid.`)
	}

	if !validator.IsValidEmail(email) {
		return errors.New(`Email "` + email + `" is not valid.`)
	}

	stmt, _ := db.Prepare(`insert into User(FirstName, LastName, Email, SubscribedToArticles, SubscribedToEvents)
		values (?, ?, ?, ?, ?)`)

	if _, err := stmt.Exec(firstName, lastName, email, subscribedToArticles, subscribedToEvents); err != nil {
		return errors.New(`Email "` + email + `" is already subscribed.`)
	}

	return nil
}

// GetUser retrieves the user with the specified email.
func GetUser(email string) (*userEntry, error) {
	stmt, _ := db.Prepare("select * from User where Email = ?")
	user := new(userEntry)
	err := stmt.QueryRow(email).Scan(
		&user.FirstName,
		&user.LastName,
		&user.Email,
		&user.SubscribedToArticles,
		&user.SubscribedToEvents)

	if err != nil {
		return nil, errors.New(`Could not find user with email "` + email + `".`)
	}

	return user, nil
}

// ExistsUser determines whether a user with the specified email exists.
func ExistsUser(email string) bool {
	user, _ := GetUser(email)

	return user != nil
}

// DeleteUser removes the specified entry from the User table.
func DeleteUser(email string) error {
	stmt, _ := db.Prepare("delete from User where Email = ?")

	if _, err := stmt.Exec(email); err != nil {
		return errors.New(`Could not delete user with email "` + email + `".`)
	}

	return nil
}
<|endoftext|>


<commit_before>package gode

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
)

// The Node version to install.
// Override this by setting client.Version.
const DefaultNodeVersion = "1.1.0"

// The NPM version to install.
// Override this by setting client.Version.
const DefaultNpmVersion = "2.4.1"

// Client is the interface between Node and Go.
// It also setups up the Node environment if needed.
type Client struct {
	RootPath    string
	NodeVersion string
	NpmVersion  string
	Registry    string
}

// NewClient creates a new Client at the specified rootPath
// The Node installation can then be setup here with client.Setup()
func NewClient(rootPath string) *Client {
	client := &Client{
		RootPath:    rootPath,
		NodeVersion: DefaultNodeVersion,
		NpmVersion:  DefaultNpmVersion,
	}

	return client
}

// NodeBase returns the directory name for the node install
func (c *Client) NodeBase() string {
	switch {
	case runtime.GOARCH == "386":
		return "iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-ia32"
	default:
		return "iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-x64"
	}
}

func (c *Client) nodeURL() string {
	switch {
	case runtime.GOOS == "windows":
		return "https://iojs.org/dist/v" + c.NodeVersion + "/win-x86/iojs.exe"
	case runtime.GOARCH == "386":
		return "https://iojs.org/dist/v" + c.NodeVersion + "/iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-x86.tar.gz"
	default:
		return "https://iojs.org/dist/v" + c.NodeVersion + "/" + c.NodeBase() + ".tar.gz"
	}
}

func (c *Client) nodePath() string {
	switch {
	case runtime.GOOS == "windows":
		return filepath.Join(c.RootPath, c.NodeBase(), "bin", "node.exe")
	default:
		return filepath.Join(c.RootPath, c.NodeBase(), "bin", "node")
	}
}

func (c *Client) npmURL() string {
	return "http://github.com/npm/npm/archive/v" + c.NpmVersion + ".zip"
}

func (c *Client) npmPath() string {
	return filepath.Join(c.RootPath, c.NodeBase(), "lib", "node_modules", "npm", "cli.js")
}

func (c *Client) tmpDir(prefix string) string {
	root := filepath.Join(c.RootPath, "tmp")
	err := os.MkdirAll(root, 0755)
	if err != nil {
		panic(err)
	}
	dir, err := ioutil.TempDir(root, prefix)
	if err != nil {
		panic(err)
	}
	return dir
}
<commit_msg>added arm support<commit_after>package gode

import (
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
)

// The Node version to install.
// Override this by setting client.Version.
const DefaultNodeVersion = "1.1.0"

// The NPM version to install.
// Override this by setting client.Version.
const DefaultNpmVersion = "2.4.1"

// Client is the interface between Node and Go.
// It also setups up the Node environment if needed.
type Client struct {
	RootPath    string
	NodeVersion string
	NpmVersion  string
	Registry    string
}

// NewClient creates a new Client at the specified rootPath
// The Node installation can then be setup here with client.Setup()
func NewClient(rootPath string) *Client {
	client := &Client{
		RootPath:    rootPath,
		NodeVersion: DefaultNodeVersion,
		NpmVersion:  DefaultNpmVersion,
	}

	return client
}

// NodeBase returns the directory name for the node install
func (c *Client) NodeBase() string {
	switch {
	case runtime.GOARCH == "386":
		return "iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-ia32"
	case runtime.GOARCH == "arm":
		return "iojs-v" + c.NodeVersion + "-armv7l"
	default:
		return "iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-x64"
	}
}

func (c *Client) nodeURL() string {
	switch {
	case runtime.GOOS == "windows":
		return "https://iojs.org/dist/v" + c.NodeVersion + "/win-x86/iojs.exe"
	case runtime.GOARCH == "386":
		return "https://iojs.org/dist/v" + c.NodeVersion + "/iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-x86.tar.gz"
	case runtime.GOARCH == "arm":
		return "https://iojs.org/dist/v" + c.NodeVersion + "/iojs-v" + c.NodeVersion + "-" + runtime.GOOS + "-armv7l.tar.gz"
	default:
		return "https://iojs.org/dist/v" + c.NodeVersion + "/" + c.NodeBase() + ".tar.gz"
	}
}

func (c *Client) nodePath() string {
	switch {
	case runtime.GOOS == "windows":
		return filepath.Join(c.RootPath, c.NodeBase(), "bin", "node.exe")
	default:
		return filepath.Join(c.RootPath, c.NodeBase(), "bin", "node")
	}
}

func (c *Client) npmURL() string {
	return "http://github.com/npm/npm/archive/v" + c.NpmVersion + ".zip"
}

func (c *Client) npmPath() string {
	return filepath.Join(c.RootPath, c.NodeBase(), "lib", "node_modules", "npm", "cli.js")
}

func (c *Client) tmpDir(prefix string) string {
	root := filepath.Join(c.RootPath, "tmp")
	err := os.MkdirAll(root, 0755)
	if err != nil {
		panic(err)
	}
	dir, err := ioutil.TempDir(root, prefix)
	if err != nil {
		panic(err)
	}
	return dir
}
<|endoftext|>


<commit_before>package socketio

import (
	"crypto/tls"
	"github.com/gorilla/websocket"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"time"
)

const (
	ProtocolVersion = 1
)

type Client struct {
	session  *Session
	endpoint string
	*EventEmitter
}

func Dial(origin, proxy string) (*Client, error) {
	u, err := url.Parse(origin)
	if err != nil {
		return nil, err
	}
	endpoint := parseEndpoint(u)
	u.Path = fmt.Sprintf("/socket.io/%d/", ProtocolVersion)

	url_ := u.String()

	proxyUrl, err := url.Parse(proxy)

	tr := &http.Transport{
		Proxy:           http.ProxyURL(proxyUrl),
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	request, err := http.NewRequest("GET", url_, nil)
	if err != nil {
		return nil, err
	}

	r, err := client.Do(request)

	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	if r.StatusCode != 200 {
		return nil, errors.New("invalid status: " + r.Status)
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	parts := strings.SplitN(string(body), ":", 4)
	if len(parts) != 4 {
		return nil, errors.New("invalid handshake: " + string(body))
	}
	if !strings.Contains(parts[3], "websocket") {
		return nil, errors.New("server does not support websockets")
	}
	sessionId := parts[0]
	u.Scheme = "ws" + u.Scheme[4:]
	u.Path = fmt.Sprintf("%swebsocket/%s", u.Path, sessionId)

	websocket.DefaultDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	websocket.DefaultDialer.Proxy = http.ProxyURL(proxyUrl)

	ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		return nil, err
	}
//	defer ws.Close()

	timeout, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return nil, err
	}

	ee := NewEventEmitter()
	emitters := make(map[string]*EventEmitter)
	emitters[endpoint] = ee
	if endpoint != "" {
		emitters[""] = NewEventEmitter()
	}
	session := NewSession(emitters, sessionId, int(timeout), false, nil)
	transport := newWebSocket(session)
	transport.conn = ws
	session.transport = transport
	if endpoint != "" {
		session.transport.Send(encodePacket(endpoint, new(connectPacket)))
	}

	return &Client{
		session:      session,
		endpoint:     endpoint,
		EventEmitter: ee,
	}, nil
}

func parseEndpoint(u *url.URL) string {
	path := u.Path
	if l := len(path); l > 0 && path[len(path)-1] == '/' {
		path = path[:l-1]
	}
	lastPath := strings.LastIndex(path, "/")
	endpoint := ""
	if lastPath >= 0 {
		path := path[lastPath:]
		if len(path) > 0 {
			endpoint = path
		}
	}
	return endpoint
}

func (c *Client) Run() {
	c.session.loop()
}

func (c *Client) Quit() error {
	dc := new(disconnectPacket)
	return c.session.defaultNS.sendPacket(dc)
}

func (c *Client) Of(name string) (nameSpace *NameSpace) {
	ee := c.session.emitters[name]
	ns := c.session.Of(name)
	if ee == nil {
		c.session.transport.Send(encodePacket(name, new(connectPacket)))
		ns.connected = true
	}
	return ns
}

func (c *Client) Call(name string, timeout time.Duration, reply []interface{}, args ...interface{}) error {
	return c.Of(c.endpoint).Call(name, timeout, reply, args...)
}

func (c *Client) Emit(name string, args ...interface{}) error {
	return c.Of(c.endpoint).Emit(name, args...)
}
<commit_msg>add headers<commit_after>package socketio

import (
	"crypto/tls"
	"errors"
	"fmt"
	"github.com/ddliu/go-httpclient"
	"github.com/gorilla/websocket"
	"io/ioutil"
	"net/http"
	"net/url"
	"reflect"
	"strconv"
	"strings"
	"time"
)

const (
	ProtocolVersion = 1
)

type Client struct {
	session  *Session
	endpoint string
	*EventEmitter
}

func Dial(origin, proxy string, headers httpclient.Map) (*Client, error) {
	u, err := url.Parse(origin)
	if err != nil {
		return nil, err
	}
	endpoint := parseEndpoint(u)
	u.Path = fmt.Sprintf("/socket.io/%d/", ProtocolVersion)

	url_ := u.String()

	proxyUrl, err := url.Parse(proxy)

	tr := &http.Transport{
		Proxy:           http.ProxyURL(proxyUrl),
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	}
	client := &http.Client{Transport: tr}

	request, err := http.NewRequest("GET", url_, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		if reflect.ValueOf(k).Kind() == reflect.String {
			request.Header.Set(k.(string), v.(string))
		}
	}
	r, err := client.Do(request)

	if err != nil {
		return nil, err
	}
	defer r.Body.Close()
	if r.StatusCode != 200 {
		return nil, errors.New("invalid status: " + r.Status)
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	parts := strings.SplitN(string(body), ":", 4)
	if len(parts) != 4 {
		return nil, errors.New("invalid handshake: " + string(body))
	}
	if !strings.Contains(parts[3], "websocket") {
		return nil, errors.New("server does not support websockets")
	}
	sessionId := parts[0]
	u.Scheme = "ws" + u.Scheme[4:]
	u.Path = fmt.Sprintf("%swebsocket/%s", u.Path, sessionId)

	websocket.DefaultDialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	websocket.DefaultDialer.Proxy = http.ProxyURL(proxyUrl)

	ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
	if err != nil {
		return nil, err
	}
//	defer ws.Close()

	timeout, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return nil, err
	}

	ee := NewEventEmitter()
	emitters := make(map[string]*EventEmitter)
	emitters[endpoint] = ee
	if endpoint != "" {
		emitters[""] = NewEventEmitter()
	}
	session := NewSession(emitters, sessionId, int(timeout), false, nil)
	transport := newWebSocket(session)
	transport.conn = ws
	session.transport = transport
	if endpoint != "" {
		session.transport.Send(encodePacket(endpoint, new(connectPacket)))
	}

	return &Client{
		session:      session,
		endpoint:     endpoint,
		EventEmitter: ee,
	}, nil
}

func parseEndpoint(u *url.URL) string {
	path := u.Path
	if l := len(path); l > 0 && path[len(path)-1] == '/' {
		path = path[:l-1]
	}
	lastPath := strings.LastIndex(path, "/")
	endpoint := ""
	if lastPath >= 0 {
		path := path[lastPath:]
		if len(path) > 0 {
			endpoint = path
		}
	}
	return endpoint
}

func (c *Client) Run() {
	c.session.loop()
}

func (c *Client) Quit() error {
	dc := new(disconnectPacket)
	return c.session.defaultNS.sendPacket(dc)
}

func (c *Client) Of(name string) (nameSpace *NameSpace) {
	ee := c.session.emitters[name]
	ns := c.session.Of(name)
	if ee == nil {
		c.session.transport.Send(encodePacket(name, new(connectPacket)))
		ns.connected = true
	}
	return ns
}

func (c *Client) Call(name string, timeout time.Duration, reply []interface{}, args ...interface{}) error {
	return c.Of(c.endpoint).Call(name, timeout, reply, args...)
}

func (c *Client) Emit(name string, args ...interface{}) error {
	return c.Of(c.endpoint).Emit(name, args...)
}
<|endoftext|>