<commit_before>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// Redis客户端.
// Redis中文手册文档请参考：http://redisdoc.com/ ，Redis官方命令请参考：https://redis.io/commands
package gredis

import (
    "time"
    "github.com/gomodule/redigo/redis"
    "gitee.com/johng/gf/g/container/gmap"
    "fmt"
)

const (
    gDEFAULT_POOL_MAX_IDLE      = 1
    gDEFAULT_POOL_MAX_ACTIVE    = 10
    gDEFAULT_POOL_IDLE_TIMEOUT  = 180 * time.Second
    gDEFAULT_POOL_MAX_LIFE_TIME = 60  * time.Second
)

// Redis客户端
type Redis struct {
    conn redis.Conn
    pool *redis.Pool
}

// Redis服务端但节点连接配置信息
type Config struct {
    Host string // IP/域名
    Port int    // 端口
    Db   int    // db
    Pass string // 密码
}

// Redis链接池统计信息
type PoolStats struct {
    redis.PoolStats
}

// 连接池map
var pools = gmap.NewStringInterfaceMap()

// 创建redis操作对象.
func New(config Config) *Redis {
    r       := &Redis{}
    poolKey := fmt.Sprintf("%s:%d,%d", config.Host, config.Port, config.Db)
    if v := pools.Get(poolKey); v == nil {
        pool := &redis.Pool {
            MaxIdle         : gDEFAULT_POOL_MAX_IDLE,
            MaxActive       : gDEFAULT_POOL_MAX_ACTIVE,
            IdleTimeout     : gDEFAULT_POOL_IDLE_TIMEOUT,
            MaxConnLifetime : gDEFAULT_POOL_MAX_LIFE_TIME,
            Dial            : func() (redis.Conn, error) {
                c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
                if err != nil {
                    return nil, err
                }
                if _, err := c.Do("SELECT", config.Db); err != nil {
                    return nil, err
                }
                if len(config.Pass) > 0 {
                    if _, err := c.Do("AUTH", config.Pass); err != nil {
                        return nil, err
                    }
                }
                return c, nil
            },
        }
        pools.Set(poolKey, pool)
        r.pool = pool
    } else {
        r.pool = v.(*redis.Pool)
    }
    r.conn = r.pool.Get()
    return r
}

// 关闭链接，将底层的redis对象放回池中
func (r *Redis) Close() error {
    return r.conn.Close()
}

// 设置属性 - MaxIdle
func (r *Redis) SetMaxIdle(value int) {
    r.pool.MaxIdle = value
}

// 设置属性 - MaxActive
func (r *Redis) SetMaxActive(value int) {
    r.pool.MaxActive = value
}

// 设置属性 - IdleTimeout
func (r *Redis) SetIdleTimeout(value time.Duration) {
    r.pool.IdleTimeout = value
}

// 设置属性 - MaxConnLifetime
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
    r.pool.MaxConnLifetime = value
}

// 获取当前连接池统计信息
func (r *Redis) Stats() *PoolStats {
    return &PoolStats{r.pool.Stats()}
}

// 执行同步命令 - Do
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
    return r.conn.Do(command, args...)
}

// 执行异步命令 - Send
func (r *Redis) Send(command string, args ...interface{}) error {
    return r.conn.Send(command, args...)
}

<commit_msg>!6 修改redis执行顺序 Merge pull request !6 from 张金富/master<commit_after>// Copyright 2017 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.

// Redis客户端.
// Redis中文手册文档请参考：http://redisdoc.com/ ，Redis官方命令请参考：https://redis.io/commands
package gredis

import (
    "time"
    "github.com/gomodule/redigo/redis"
    "gitee.com/johng/gf/g/container/gmap"
    "fmt"
)

const (
    gDEFAULT_POOL_MAX_IDLE      = 1
    gDEFAULT_POOL_MAX_ACTIVE    = 10
    gDEFAULT_POOL_IDLE_TIMEOUT  = 180 * time.Second
    gDEFAULT_POOL_MAX_LIFE_TIME = 60  * time.Second
)

// Redis客户端
type Redis struct {
    conn redis.Conn
    pool *redis.Pool
}

// Redis服务端但节点连接配置信息
type Config struct {
    Host string // IP/域名
    Port int    // 端口
    Db   int    // db
    Pass string // 密码
}

// Redis链接池统计信息
type PoolStats struct {
    redis.PoolStats
}

// 连接池map
var pools = gmap.NewStringInterfaceMap()

// 创建redis操作对象.
func New(config Config) *Redis {
    r       := &Redis{}
    poolKey := fmt.Sprintf("%s:%d,%d", config.Host, config.Port, config.Db)
    if v := pools.Get(poolKey); v == nil {
        pool := &redis.Pool {
            MaxIdle         : gDEFAULT_POOL_MAX_IDLE,
            MaxActive       : gDEFAULT_POOL_MAX_ACTIVE,
            IdleTimeout     : gDEFAULT_POOL_IDLE_TIMEOUT,
            MaxConnLifetime : gDEFAULT_POOL_MAX_LIFE_TIME,
            Dial            : func() (redis.Conn, error) {
                c, err := redis.Dial("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
                if err != nil {
                    return nil, err
                }
                if len(config.Pass) > 0 {
                    if _, err := c.Do("AUTH", config.Pass); err != nil {
                        return nil, err
                    }
                }
                if _, err := c.Do("SELECT", config.Db); err != nil {
                    return nil, err
                }
                return c, nil
            },
        }
        pools.Set(poolKey, pool)
        r.pool = pool
    } else {
        r.pool = v.(*redis.Pool)
    }
    r.conn = r.pool.Get()
    return r
}

// 关闭链接，将底层的redis对象放回池中
func (r *Redis) Close() error {
    return r.conn.Close()
}

// 设置属性 - MaxIdle
func (r *Redis) SetMaxIdle(value int) {
    r.pool.MaxIdle = value
}

// 设置属性 - MaxActive
func (r *Redis) SetMaxActive(value int) {
    r.pool.MaxActive = value
}

// 设置属性 - IdleTimeout
func (r *Redis) SetIdleTimeout(value time.Duration) {
    r.pool.IdleTimeout = value
}

// 设置属性 - MaxConnLifetime
func (r *Redis) SetMaxConnLifetime(value time.Duration) {
    r.pool.MaxConnLifetime = value
}

// 获取当前连接池统计信息
func (r *Redis) Stats() *PoolStats {
    return &PoolStats{r.pool.Stats()}
}

// 执行同步命令 - Do
func (r *Redis) Do(command string, args ...interface{}) (interface{}, error) {
    return r.conn.Do(command, args...)
}

// 执行异步命令 - Send
func (r *Redis) Send(command string, args ...interface{}) error {
    return r.conn.Send(command, args...)
}

<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
	gin "github.com/gin-gonic/gin"
)

func errorResponse(err error, c *gin.Context) {
	result := map[string]string{"error": err.Error()}
	c.JSON(400, result)
}

func setCorsHeaders(c *gin.Context) {
	c.Header("Access-Control-Allow-Origin", "*")
	c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
	c.Header("Access-Control-Expose-Headers", "*")
}

func HandleRun(c *gin.Context) {
	// We only need CORS for this endpoint
	setCorsHeaders(c)

	req, err := ParseRequest(c.Request)
	if err != nil {
		errorResponse(err, c)
		return
	}

	config, exists := c.Get("config")
	if !exists {
		errorResponse(fmt.Errorf("Cant get config"), c)
		return
	}

	client, exists := c.Get("client")
	if !exists {
		errorResponse(fmt.Errorf("Cant get client"), c)
		return
	}

	run := NewRun(config.(*Config), client.(*docker.Client), req)
	defer run.Destroy()

	if err := run.Setup(); err != nil {
		errorResponse(err, c)
		return
	}

	result, err := run.StartWithTimeout(run.Config.RunDuration)
	if err != nil {
		errorResponse(err, c)
		return
	}

	c.Header("X-Run-Command", req.Command)
	c.Header("X-Run-ExitCode", strconv.Itoa(result.ExitCode))
	c.Header("X-Run-Duration", result.Duration)

	c.Data(200, req.Format, result.Output)
}

func HandleConfig(c *gin.Context) {
	c.JSON(200, Extensions)
}

func throttleMiddleware(throttler *Throttler) gin.HandlerFunc {
	return func(c *gin.Context) {
		ip := strings.Split(c.Request.RemoteAddr, ":")[0]

		if err := throttler.Add(ip); err != nil {
			errorResponse(err, c)
			c.Abort()
			return
		}

		c.Next()
		throttler.Remove(ip)
	}
}

func RunApi(config *Config, client *docker.Client) {
	throttler := NewThrottler(config.ThrottleConcurrency, config.ThrottleQuota)
	throttler.StartPeriodicFlush()

	router := gin.Default()

	v1 := router.Group("/api/v1/")
	{
		v1.Use(throttleMiddleware(throttler))

		v1.Use(func(c *gin.Context) {
			c.Set("config", config)
			c.Set("client", client)
		})

		v1.GET("/config", HandleConfig)
		v1.POST("/run", HandleRun)
	}

	router.Run("127.0.0.1:5000")
}
<commit_msg>Add CORS middleware<commit_after>package main

import (
	"fmt"
	"strconv"
	"strings"

	docker "github.com/fsouza/go-dockerclient"
	gin "github.com/gin-gonic/gin"
)

func errorResponse(err error, c *gin.Context) {
	result := map[string]string{"error": err.Error()}
	c.JSON(400, result)
}

func HandleRun(c *gin.Context) {
	req, err := ParseRequest(c.Request)
	if err != nil {
		errorResponse(err, c)
		return
	}

	config, exists := c.Get("config")
	if !exists {
		errorResponse(fmt.Errorf("Cant get config"), c)
		return
	}

	client, exists := c.Get("client")
	if !exists {
		errorResponse(fmt.Errorf("Cant get client"), c)
		return
	}

	run := NewRun(config.(*Config), client.(*docker.Client), req)
	defer run.Destroy()

	if err := run.Setup(); err != nil {
		errorResponse(err, c)
		return
	}

	result, err := run.StartWithTimeout(run.Config.RunDuration)
	if err != nil {
		errorResponse(err, c)
		return
	}

	c.Header("X-Run-Command", req.Command)
	c.Header("X-Run-ExitCode", strconv.Itoa(result.ExitCode))
	c.Header("X-Run-Duration", result.Duration)

	c.Data(200, req.Format, result.Output)
}

func HandleConfig(c *gin.Context) {
	c.JSON(200, Extensions)
}

func throttleMiddleware(throttler *Throttler) gin.HandlerFunc {
	return func(c *gin.Context) {
		ip := strings.Split(c.Request.RemoteAddr, ":")[0]

		if err := throttler.Add(ip); err != nil {
			errorResponse(err, c)
			c.Abort()
			return
		}

		c.Next()
		throttler.Remove(ip)
	}
}

func corsMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Expose-Headers", "*")
	}
}

func RunApi(config *Config, client *docker.Client) {
	throttler := NewThrottler(config.ThrottleConcurrency, config.ThrottleQuota)
	throttler.StartPeriodicFlush()

	router := gin.Default()

	v1 := router.Group("/api/v1/")
	{
		v1.Use(corsMiddleware())
		v1.Use(throttleMiddleware(throttler))

		v1.Use(func(c *gin.Context) {
			c.Set("config", config)
			c.Set("client", client)
		})

		v1.GET("/config", HandleConfig)
		v1.POST("/run", HandleRun)
	}

	router.Run("127.0.0.1:5000")
}
<|endoftext|>


<commit_before>package amiando

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime/debug"
	"strings"
)

///////////////////////////////////////////////////////////////////////////////
// Api

func NewApi(key string) *Api {
	return &Api{Key: key}
}

type Api struct {
	Key  string
	http http.Client
}

func (self *Api) httpGet(url string) (body []byte, err error) {
	response, err := self.http.Get(url)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	return ioutil.ReadAll(response.Body)
}

func (self *Api) Call(resourceFormat string, resourceArg interface{}, result ErrorReporter) (err error) {
	result.Reset()

	sep := "?"
	if strings.Contains(resourceFormat, "?") {
		sep = "&"
	}
	resourceFormat = "http://www.amiando.com/api/" + resourceFormat + sep + "apikey=%s&version=1&format=json"
	url := fmt.Sprintf(resourceFormat, resourceArg, self.Key)

	j, err := self.httpGet(url)
	if err != nil {
		return err
	}

	err = json.Unmarshal(j, result)
	if err != nil {
		return err
	}

	return result.Err()
}

func (self *Api) Call_debug(resourceFormat string, resourceArg interface{}, result ErrorReporter) (err error) {
	result.Reset()

	sep := "?"
	if strings.Contains(resourceFormat, "?") {
		sep = "&"
	}
	resourceFormat = "http://www.amiando.com/api/" + resourceFormat + sep + "apikey=%s&version=1&format=json"
	url := fmt.Sprintf(resourceFormat, resourceArg, self.Key)

	fmt.Println("URL: ", url)
	j, err := self.httpGet(url)
	if err != nil {
		return err
	}
	fmt.Println("Result:\n", PrettifyJSON(j))

	// Catch nasty problems in json.Unmarshal
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v", r)
			fmt.Println(err)
			debug.PrintStack()
			os.Exit(-1)
		}
	}()

	err = json.Unmarshal(j, result)
	if err != nil {
		return err
	}
	fmt.Printf("%#v\n", result)

	return result.Err()
}

func (self *Api) Payment(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		Payment interface{} `json:"payment"`
	}
	result := Result{Payment: out}
	return self.Call("payment/%v", id, &result)
}

func (self *Api) TicketIDsOfPayment(paymentID ID) (ids []ID, err error) {
	type Result struct {
		ResultBase
		Tickets []ID `json:"tickets"`
	}
	var result Result
	err = self.Call_debug("payment/%v/tickets", paymentID, &result)
	if err != nil {
		return nil, err
	}
	return result.Tickets, nil
}

func (self *Api) Ticket(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		Ticket interface{} `json:"ticket"`
	}
	result := Result{Ticket: out}
	return self.Call_debug("ticket/%v", id, &result)
}

func (self *Api) User(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		User interface{} `json:"user"`
	}
	result := Result{User: out}
	return self.Call("user/%v", id, &result)
}
<commit_msg>no debug output<commit_after>package amiando

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"runtime/debug"
	"strings"
)

///////////////////////////////////////////////////////////////////////////////
// Api

func NewApi(key string) *Api {
	return &Api{Key: key}
}

type Api struct {
	Key  string
	http http.Client
}

func (self *Api) httpGet(url string) (body []byte, err error) {
	response, err := self.http.Get(url)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()
	return ioutil.ReadAll(response.Body)
}

func (self *Api) Call(resourceFormat string, resourceArg interface{}, result ErrorReporter) (err error) {
	result.Reset()

	sep := "?"
	if strings.Contains(resourceFormat, "?") {
		sep = "&"
	}
	resourceFormat = "http://www.amiando.com/api/" + resourceFormat + sep + "apikey=%s&version=1&format=json"
	url := fmt.Sprintf(resourceFormat, resourceArg, self.Key)

	j, err := self.httpGet(url)
	if err != nil {
		return err
	}

	err = json.Unmarshal(j, result)
	if err != nil {
		return err
	}

	return result.Err()
}

func (self *Api) Call_debug(resourceFormat string, resourceArg interface{}, result ErrorReporter) (err error) {
	result.Reset()

	sep := "?"
	if strings.Contains(resourceFormat, "?") {
		sep = "&"
	}
	resourceFormat = "http://www.amiando.com/api/" + resourceFormat + sep + "apikey=%s&version=1&format=json"
	url := fmt.Sprintf(resourceFormat, resourceArg, self.Key)

	fmt.Println("URL: ", url)
	j, err := self.httpGet(url)
	if err != nil {
		return err
	}
	fmt.Println("Result:\n", PrettifyJSON(j))

	// Catch nasty problems in json.Unmarshal
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("%v", r)
			fmt.Println(err)
			debug.PrintStack()
			os.Exit(-1)
		}
	}()

	err = json.Unmarshal(j, result)
	if err != nil {
		return err
	}
	fmt.Printf("%#v\n", result)

	return result.Err()
}

func (self *Api) Payment(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		Payment interface{} `json:"payment"`
	}
	result := Result{Payment: out}
	return self.Call("payment/%v", id, &result)
}

func (self *Api) TicketIDsOfPayment(paymentID ID) (ids []ID, err error) {
	type Result struct {
		ResultBase
		Tickets []ID `json:"tickets"`
	}
	var result Result
	err = self.Call("payment/%v/tickets", paymentID, &result)
	if err != nil {
		return nil, err
	}
	return result.Tickets, nil
}

func (self *Api) Ticket(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		Ticket interface{} `json:"ticket"`
	}
	result := Result{Ticket: out}
	return self.Call("ticket/%v", id, &result)
}

func (self *Api) User(id ID, out interface{}) (err error) {
	type Result struct {
		ResultBase
		User interface{} `json:"user"`
	}
	result := Result{User: out}
	return self.Call("user/%v", id, &result)
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

// App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function
type App struct {
	// The name of the program. Defaults to os.Args[0]
	Name string
	// Description of the program.
	Usage string
	// Version of the program
	Version string
	// List of commands to execute
	Commands []Command
	// List of flags to parse
	Flags []Flag
	// Boolean to enable bash completion commands
	EnableBashCompletion bool
	// An action to execute when the bash-completion flag is set
	BashComplete func(context *Context)
	// An action to execute before any subcommands are run, but after the context is ready
	// If a non-nil error is returned, no subcommands are run
	Before func(context *Context) error
	// The action to execute when no subcommands are specified
	Action func(context *Context)
	// Execute this function if the proper command cannot be found
	CommandNotFound func(context *Context, command string)
	// Compilation date
	Compiled time.Time
	// Author
	Author string
	// Author e-mail
	Email string
}

// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
	info, err := os.Stat(os.Args[0])
	if err != nil {
		return time.Now()
	}
	return info.ModTime()
}

// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
	return &App{
		Name:         os.Args[0],
		Usage:        "A new cli application",
		Version:      "0.0.0",
		BashComplete: DefaultAppComplete,
		Action:       helpCommand.Action,
		Compiled:     compileTime(),
		Author:       "Author",
		Email:        "unknown@email",
	}
}

// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error {
	// append help to commands
	if a.Command(helpCommand.Name) == nil {
		a.Commands = append(a.Commands, helpCommand)
	}

	//append version/help flags
	if a.EnableBashCompletion {
		a.appendFlag(BashCompletionFlag)
	}
	a.appendFlag(VersionFlag)
	a.appendFlag(HelpFlag)

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set)
	if nerr != nil {
		fmt.Println(nerr)
		context := NewContext(a, set, set)
		ShowAppHelp(context)
		fmt.Println("")
		return nerr
	}
	context := NewContext(a, set, set)

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowAppHelp(context)
		fmt.Println("")
		return err
	}

	if checkCompletions(context) {
		return nil
	}

	if checkHelp(context) {
		return nil
	}

	if checkVersion(context) {
		return nil
	}

	if a.Before != nil {
		err := a.Before(context)
		if err != nil {
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	a.Action(context)
	return nil
}

// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) error {
	// append help to commands
	if len(a.Commands) > 0 {
		if a.Command(helpCommand.Name) == nil {
			a.Commands = append(a.Commands, helpCommand)
		}
	}

	// append flags
	if a.EnableBashCompletion {
		a.appendFlag(BashCompletionFlag)
	}
	a.appendFlag(HelpFlag)

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(ctx.Args().Tail())
	nerr := normalizeFlags(a.Flags, set)
	context := NewContext(a, set, ctx.globalSet)

	if nerr != nil {
		fmt.Println(nerr)
		if len(a.Commands) > 0 {
			ShowSubcommandHelp(context)
		} else {
			ShowCommandHelp(ctx, context.Args().First())
		}
		fmt.Println("")
		return nerr
	}

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowSubcommandHelp(context)
		return err
	}

	if checkCompletions(context) {
		return nil
	}

	if len(a.Commands) > 0 {
		if checkSubcommandHelp(context) {
			return nil
		}
	} else {
		if checkCommandHelp(ctx, context.Args().First()) {
			return nil
		}
	}

	if a.Before != nil {
		err := a.Before(context)
		if err != nil {
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	if len(a.Commands) > 0 {
		a.Action(context)
	} else {
		a.Action(ctx)
	}

	return nil
}

// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
	for _, c := range a.Commands {
		if c.HasName(name) {
			return &c
		}
	}

	return nil
}

func (a *App) hasFlag(flag Flag) bool {
	for _, f := range a.Flags {
		if flag == f {
			return true
		}
	}

	return false
}

func (a *App) appendFlag(flag Flag) {
	if !a.hasFlag(flag) {
		a.Flags = append(a.Flags, flag)
	}
}
<commit_msg>Add App.RunAndExitOnError shortcut<commit_after>package cli

import (
	"fmt"
	"io/ioutil"
	"os"
	"time"
)

// App is the main structure of a cli application. It is recomended that
// and app be created with the cli.NewApp() function
type App struct {
	// The name of the program. Defaults to os.Args[0]
	Name string
	// Description of the program.
	Usage string
	// Version of the program
	Version string
	// List of commands to execute
	Commands []Command
	// List of flags to parse
	Flags []Flag
	// Boolean to enable bash completion commands
	EnableBashCompletion bool
	// An action to execute when the bash-completion flag is set
	BashComplete func(context *Context)
	// An action to execute before any subcommands are run, but after the context is ready
	// If a non-nil error is returned, no subcommands are run
	Before func(context *Context) error
	// The action to execute when no subcommands are specified
	Action func(context *Context)
	// Execute this function if the proper command cannot be found
	CommandNotFound func(context *Context, command string)
	// Compilation date
	Compiled time.Time
	// Author
	Author string
	// Author e-mail
	Email string
}

// Tries to find out when this binary was compiled.
// Returns the current time if it fails to find it.
func compileTime() time.Time {
	info, err := os.Stat(os.Args[0])
	if err != nil {
		return time.Now()
	}
	return info.ModTime()
}

// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
func NewApp() *App {
	return &App{
		Name:         os.Args[0],
		Usage:        "A new cli application",
		Version:      "0.0.0",
		BashComplete: DefaultAppComplete,
		Action:       helpCommand.Action,
		Compiled:     compileTime(),
		Author:       "Author",
		Email:        "unknown@email",
	}
}

// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
func (a *App) Run(arguments []string) error {
	// append help to commands
	if a.Command(helpCommand.Name) == nil {
		a.Commands = append(a.Commands, helpCommand)
	}

	//append version/help flags
	if a.EnableBashCompletion {
		a.appendFlag(BashCompletionFlag)
	}
	a.appendFlag(VersionFlag)
	a.appendFlag(HelpFlag)

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(arguments[1:])
	nerr := normalizeFlags(a.Flags, set)
	if nerr != nil {
		fmt.Println(nerr)
		context := NewContext(a, set, set)
		ShowAppHelp(context)
		fmt.Println("")
		return nerr
	}
	context := NewContext(a, set, set)

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowAppHelp(context)
		fmt.Println("")
		return err
	}

	if checkCompletions(context) {
		return nil
	}

	if checkHelp(context) {
		return nil
	}

	if checkVersion(context) {
		return nil
	}

	if a.Before != nil {
		err := a.Before(context)
		if err != nil {
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	a.Action(context)
	return nil
}

// Another entry point to the cli app, takes care of passing arguments and error handling
func (a *App) RunAndExitOnError() {
	if err := a.Run(os.Args); err != nil {
		os.Stderr.WriteString(fmt.Sprintln(err))
		os.Exit(1)
	}
}

// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
func (a *App) RunAsSubcommand(ctx *Context) error {
	// append help to commands
	if len(a.Commands) > 0 {
		if a.Command(helpCommand.Name) == nil {
			a.Commands = append(a.Commands, helpCommand)
		}
	}

	// append flags
	if a.EnableBashCompletion {
		a.appendFlag(BashCompletionFlag)
	}
	a.appendFlag(HelpFlag)

	// parse flags
	set := flagSet(a.Name, a.Flags)
	set.SetOutput(ioutil.Discard)
	err := set.Parse(ctx.Args().Tail())
	nerr := normalizeFlags(a.Flags, set)
	context := NewContext(a, set, ctx.globalSet)

	if nerr != nil {
		fmt.Println(nerr)
		if len(a.Commands) > 0 {
			ShowSubcommandHelp(context)
		} else {
			ShowCommandHelp(ctx, context.Args().First())
		}
		fmt.Println("")
		return nerr
	}

	if err != nil {
		fmt.Printf("Incorrect Usage.\n\n")
		ShowSubcommandHelp(context)
		return err
	}

	if checkCompletions(context) {
		return nil
	}

	if len(a.Commands) > 0 {
		if checkSubcommandHelp(context) {
			return nil
		}
	} else {
		if checkCommandHelp(ctx, context.Args().First()) {
			return nil
		}
	}

	if a.Before != nil {
		err := a.Before(context)
		if err != nil {
			return err
		}
	}

	args := context.Args()
	if args.Present() {
		name := args.First()
		c := a.Command(name)
		if c != nil {
			return c.Run(context)
		}
	}

	// Run default Action
	if len(a.Commands) > 0 {
		a.Action(context)
	} else {
		a.Action(ctx)
	}

	return nil
}

// Returns the named command on App. Returns nil if the command does not exist
func (a *App) Command(name string) *Command {
	for _, c := range a.Commands {
		if c.HasName(name) {
			return &c
		}
	}

	return nil
}

func (a *App) hasFlag(flag Flag) bool {
	for _, f := range a.Flags {
		if flag == f {
			return true
		}
	}

	return false
}

func (a *App) appendFlag(flag Flag) {
	if !a.hasFlag(flag) {
		a.Flags = append(a.Flags, flag)
	}
}
<|endoftext|>


<commit_before>package fake_backend

import (
	"sync"
	"time"

	"github.com/cloudfoundry-incubator/garden/warden"
)

type FakeBackend struct {
	Started    bool
	StartError error

	Stopped bool

	CreateResult    *FakeContainer
	CreateError     error
	DestroyError    error
	ContainersError error

	CreatedContainers   map[string]*FakeContainer
	DestroyedContainers []string

	ContainersFilters []warden.Properties

	sync.RWMutex
}

type UnknownHandleError struct {
	Handle string
}

func (e UnknownHandleError) Error() string {
	return "unknown handle: " + e.Handle
}

func New() *FakeBackend {
	return &FakeBackend{
		CreatedContainers: make(map[string]*FakeContainer),
	}
}

func (b *FakeBackend) Start() error {
	if b.StartError != nil {
		return b.StartError
	}

	b.Started = true

	return nil
}

func (b *FakeBackend) Stop() {
	b.Stopped = true
}

func (b *FakeBackend) Create(spec warden.ContainerSpec) (warden.Container, error) {
	if b.CreateError != nil {
		return nil, b.CreateError
	}

	var container *FakeContainer

	if b.CreateResult != nil {
		container = b.CreateResult
	} else {
		container = NewFakeContainer(spec)
	}

	b.Lock()
	defer b.Unlock()

	b.CreatedContainers[container.Handle()] = container

	return container, nil
}

func (b *FakeBackend) Destroy(handle string) error {
	if b.DestroyError != nil {
		return b.DestroyError
	}

	b.Lock()
	defer b.Unlock()

	delete(b.CreatedContainers, handle)

	b.DestroyedContainers = append(b.DestroyedContainers, handle)

	return nil
}

func (b *FakeBackend) Containers(properties warden.Properties) ([]warden.Container, error) {
	if b.ContainersError != nil {
		return nil, b.ContainersError
	}

	b.Lock()
	defer b.Unlock()

	b.ContainersFilters = append(b.ContainersFilters, properties)

	containers := []warden.Container{}
	for _, c := range b.CreatedContainers {
		containers = append(containers, c)
	}

	return containers, nil
}

func (b *FakeBackend) Lookup(handle string) (warden.Container, error) {
	b.RLock()
	defer b.RUnlock()

	container, found := b.CreatedContainers[handle]
	if !found {
		return nil, UnknownHandleError{handle}
	}

	return container, nil
}

func (b *FakeBackend) GraceTime(container warden.Container) time.Duration {
	return container.(*FakeContainer).GraceTime()
}
<commit_msg>fake backend implements filtering<commit_after>package fake_backend

import (
	"sync"
	"time"

	"github.com/cloudfoundry-incubator/garden/warden"
)

type FakeBackend struct {
	Started    bool
	StartError error

	Stopped bool

	CreateResult    *FakeContainer
	CreateError     error
	DestroyError    error
	ContainersError error

	CreatedContainers   map[string]*FakeContainer
	DestroyedContainers []string

	ContainersFilters []warden.Properties

	sync.RWMutex
}

type UnknownHandleError struct {
	Handle string
}

func (e UnknownHandleError) Error() string {
	return "unknown handle: " + e.Handle
}

func New() *FakeBackend {
	return &FakeBackend{
		CreatedContainers: make(map[string]*FakeContainer),
	}
}

func (b *FakeBackend) Start() error {
	if b.StartError != nil {
		return b.StartError
	}

	b.Started = true

	return nil
}

func (b *FakeBackend) Stop() {
	b.Stopped = true
}

func (b *FakeBackend) Create(spec warden.ContainerSpec) (warden.Container, error) {
	if b.CreateError != nil {
		return nil, b.CreateError
	}

	var container *FakeContainer

	if b.CreateResult != nil {
		container = b.CreateResult
	} else {
		container = NewFakeContainer(spec)
	}

	b.Lock()
	defer b.Unlock()

	b.CreatedContainers[container.Handle()] = container

	return container, nil
}

func (b *FakeBackend) Destroy(handle string) error {
	if b.DestroyError != nil {
		return b.DestroyError
	}

	b.Lock()
	defer b.Unlock()

	delete(b.CreatedContainers, handle)

	b.DestroyedContainers = append(b.DestroyedContainers, handle)

	return nil
}

func (b *FakeBackend) Containers(properties warden.Properties) ([]warden.Container, error) {
	if b.ContainersError != nil {
		return nil, b.ContainersError
	}

	b.Lock()
	defer b.Unlock()

	b.ContainersFilters = append(b.ContainersFilters, properties)

	containers := []warden.Container{}
	for _, c := range b.CreatedContainers {
		if containerHasProperties(c, properties) {
			containers = append(containers, c)
		}
	}

	return containers, nil
}

func (b *FakeBackend) Lookup(handle string) (warden.Container, error) {
	b.RLock()
	defer b.RUnlock()

	container, found := b.CreatedContainers[handle]
	if !found {
		return nil, UnknownHandleError{handle}
	}

	return container, nil
}

func (b *FakeBackend) GraceTime(container warden.Container) time.Duration {
	return container.(*FakeContainer).GraceTime()
}

func containerHasProperties(container *FakeContainer, properties warden.Properties) bool {
	containerProps := container.Properties()

	for key, val := range properties {
		cval, ok := containerProps[key]
		if !ok {
			return false
		}

		if cval != val {
			return false
		}
	}

	return true
}
<|endoftext|>


<commit_before>package generator

import (
	"bytes"
	"fmt"
	goparser "go/parser"
	"go/token"
	"io/ioutil"
	"testing"

	"github.com/devimteam/microgen/generator/template"
	"github.com/vetcher/godecl"
	"github.com/vetcher/godecl/types"
)

func findInterface(file *types.File, ifaceName string) *types.Interface {
	for i := range file.Interfaces {
		if file.Interfaces[i].Name == ifaceName {
			return &file.Interfaces[i]
		}
	}
	return nil
}

func loadInterface(sourceFile, ifaceName string) (*types.Interface, error) {
	src, err := ioutil.ReadFile(sourceFile)
	if err != nil {
		return nil, fmt.Errorf("read source file error: %v", err)
	}
	tree, err := goparser.ParseFile(token.NewFileSet(), "", src, 0)
	if err != nil {
		return nil, fmt.Errorf("unable to parse file: %v", err)
	}
	// TODO(nicolai): Figure out if packagePath needs to be anything
	packagePath := ""
	info, err := godecl.ParseAstFile(tree, packagePath)
	if err != nil {
		fmt.Printf("error when parsing info from file: %v\n", err)
	}
	i := findInterface(info, ifaceName)
	if i == nil {
		return nil, fmt.Errorf("could not find %s interface", ifaceName)
	}
	return i, nil
}

func TestTemplates(t *testing.T) {
	fs, err := loadInterface("service.go.txt", "StringService")
	if err != nil {
		t.Fatal(err)
	}
	allTemplateTests := []struct {
		TestName    string
		Template    template.Template
		OutFilePath string
	}{
		{
			TestName: "Endpoints",
			Template: template.NewEndpointsTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "endpoints.go.txt",
		},
		{
			TestName: "Exchange",
			Template: template.NewExchangeTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "exchange.go.txt",
		},
		{
			TestName: "Middleware",
			Template: template.NewMiddlewareTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "middleware.go.txt",
		},
		{
			TestName: "Logging",
			Template: template.NewLoggingTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "logging.go.txt",
		},
		{
			TestName: "GRPC Server",
			Template: template.NewGRPCServerTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "grpc_server.go.txt",
		},
		{
			TestName: "GRPC Client",
			Template: template.NewGRPCClientTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "grpc_client.go.txt",
		},
		{
			TestName: "GRPC Converter",
			Template: template.NewGRPCEndpointConverterTemplate(&template.GenerationInfo{
				ServiceImportPath:        "github.com/devimteam/microgen/example/svc",
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "grpc_converters.go.txt",
		},
		{
			TestName: "GRPC Type Converter",
			Template: template.NewStubGRPCTypeConverterTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "grpc_type.go.txt",
		},
	}
	for _, test := range allTemplateTests {
		t.Run(test.TestName, func(t *testing.T) {
			out, err := ioutil.ReadFile(test.OutFilePath)
			if err != nil {
				t.Fatalf("read out file error: %v", err)
			}

			buf := bytes.NewBuffer([]byte{})
			// TODO(nicolai): NewGenerator was commented out in [1]
			// Need to use NewGenUnit now.
			// Also consider testify instead of doing diff heavy-lifting
			// [1] https://github.com/devimteam/microgen/commit/0e094fadf97df0da8c14f5b778a3beea4f646545#diff-338bea067d2a01b69c90f1c032c6a24b

			// TODO(nicolai): Figure out if absOutPath needs to be anything
			absOutPath := ""
			gen := NewGenUnit(test.Template, absOutPath)
			err = gen.Generate()
			if err != nil {
				t.Fatalf("unable to generate: %v", err)
			}
			if buf.String() != string(out[:]) {
				t.Errorf("Got:\n/////////\n%s\n/////////\nExpected:\n/////////\n%s\n/////////", buf.String(), string(out[:]))
				t.Errorf("1: Got(bytes), 2: Expected(bytes):\n/////////\n1: %v\n2: %v\n/////////", buf.Bytes(), out[:])
				x, y, z, line := findDifference(buf.String(), string(out[:]))
				t.Errorf("line:pos:raw %d:%d:%d %d!=%d %v\n`%s`", x+1, y+1, z, buf.Bytes()[z], out[z], len(buf.String()) == len(string(out[:])), line)
			}
		})
	}
}

func findDifference(first, second string) (line int, pos int, raw int, strLine string) {
	for i, sym := range first {
		if first[i] != second[i] {
			return
		}
		if sym == '\n' {
			line += 1
			pos = 0
			strLine = ""
		}
		pos += 1
		raw += 1
		strLine += string(sym)
	}
	return 0, 0, 0, ""
}
<commit_msg>fix: NewGenUnit returns two parameters<commit_after>package generator

import (
	"bytes"
	"fmt"
	goparser "go/parser"
	"go/token"
	"io/ioutil"
	"testing"

	"github.com/devimteam/microgen/generator/template"
	"github.com/vetcher/godecl"
	"github.com/vetcher/godecl/types"
)

func findInterface(file *types.File, ifaceName string) *types.Interface {
	for i := range file.Interfaces {
		if file.Interfaces[i].Name == ifaceName {
			return &file.Interfaces[i]
		}
	}
	return nil
}

func loadInterface(sourceFile, ifaceName string) (*types.Interface, error) {
	src, err := ioutil.ReadFile(sourceFile)
	if err != nil {
		return nil, fmt.Errorf("read source file error: %v", err)
	}
	tree, err := goparser.ParseFile(token.NewFileSet(), "", src, 0)
	if err != nil {
		return nil, fmt.Errorf("unable to parse file: %v", err)
	}
	// TODO(nicolai): Figure out if packagePath needs to be anything
	packagePath := ""
	info, err := godecl.ParseAstFile(tree, packagePath)
	if err != nil {
		fmt.Printf("error when parsing info from file: %v\n", err)
	}
	i := findInterface(info, ifaceName)
	if i == nil {
		return nil, fmt.Errorf("could not find %s interface", ifaceName)
	}
	return i, nil
}

func TestTemplates(t *testing.T) {
	fs, err := loadInterface("service.go.txt", "StringService")
	if err != nil {
		t.Fatal(err)
	}
	allTemplateTests := []struct {
		TestName    string
		Template    template.Template
		OutFilePath string
	}{
		{
			TestName: "Endpoints",
			Template: template.NewEndpointsTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "endpoints.go.txt",
		},
		{
			TestName: "Exchange",
			Template: template.NewExchangeTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "exchange.go.txt",
		},
		{
			TestName: "Middleware",
			Template: template.NewMiddlewareTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "middleware.go.txt",
		},
		{
			TestName: "Logging",
			Template: template.NewLoggingTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "logging.go.txt",
		},
		{
			TestName: "GRPC Server",
			Template: template.NewGRPCServerTemplate(&template.GenerationInfo{
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "grpc_server.go.txt",
		},
		{
			TestName: "GRPC Client",
			Template: template.NewGRPCClientTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "grpc_client.go.txt",
		},
		{
			TestName: "GRPC Converter",
			Template: template.NewGRPCEndpointConverterTemplate(&template.GenerationInfo{
				ServiceImportPath:        "github.com/devimteam/microgen/example/svc",
				ServiceImportPackageName: "stringsvc",
			}),
			OutFilePath: "grpc_converters.go.txt",
		},
		{
			TestName: "GRPC Type Converter",
			Template: template.NewStubGRPCTypeConverterTemplate(&template.GenerationInfo{
				ServiceImportPath: "github.com/devimteam/microgen/example/svc",
			}),
			OutFilePath: "grpc_type.go.txt",
		},
	}
	for _, test := range allTemplateTests {
		t.Run(test.TestName, func(t *testing.T) {
			out, err := ioutil.ReadFile(test.OutFilePath)
			if err != nil {
				t.Fatalf("read out file error: %v", err)
			}

			buf := bytes.NewBuffer([]byte{})
			// TODO(nicolai): NewGenerator was commented out in [1]
			// Need to use NewGenUnit now.
			// Also consider testify instead of doing diff heavy-lifting
			// [1] https://github.com/devimteam/microgen/commit/0e094fadf97df0da8c14f5b778a3beea4f646545#diff-338bea067d2a01b69c90f1c032c6a24b

			// TODO(nicolai): Figure out if absOutPath needs to be anything
			absOutPath := ""
			gen, err := NewGenUnit(test.Template, absOutPath)
			if err != nil {
				t.Fatalf("NewGenUnit: %v", err)
			}
			err = gen.Generate()
			if err != nil {
				t.Fatalf("unable to generate: %v", err)
			}
			if buf.String() != string(out[:]) {
				t.Errorf("Got:\n/////////\n%s\n/////////\nExpected:\n/////////\n%s\n/////////", buf.String(), string(out[:]))
				t.Errorf("1: Got(bytes), 2: Expected(bytes):\n/////////\n1: %v\n2: %v\n/////////", buf.Bytes(), out[:])
				x, y, z, line := findDifference(buf.String(), string(out[:]))
				t.Errorf("line:pos:raw %d:%d:%d %d!=%d %v\n`%s`", x+1, y+1, z, buf.Bytes()[z], out[z], len(buf.String()) == len(string(out[:])), line)
			}
		})
	}
}

func findDifference(first, second string) (line int, pos int, raw int, strLine string) {
	for i, sym := range first {
		if first[i] != second[i] {
			return
		}
		if sym == '\n' {
			line += 1
			pos = 0
			strLine = ""
		}
		pos += 1
		raw += 1
		strLine += string(sym)
	}
	return 0, 0, 0, ""
}
<|endoftext|>


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

package orchestrator

import (
	"fmt"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/web/view/viewmodel"
)

type ConversionModelOrchestrator struct {
	*Orchestrator

	fileOrchestrator *FileOrchestrator
}

func (orchestrator *ConversionModelOrchestrator) GetConversionModel(hostname string, route route.Route) (model viewmodel.ConversionModel, found bool) {

	// get the root item
	root := orchestrator.rootItem()
	if root == nil {
		return model, false
	}

	// get the requested item
	item := orchestrator.getItem(route)
	if item == nil {
		orchestrator.logger.Info("There was no item for route %q.", route)
		return model, false
	}

	// create the path provider
	addressPrefix := fmt.Sprintf("http://%s/", hostname)
	pathProvider := orchestrator.absolutePather(addressPrefix)

	// convert content
	convertedContent, err := orchestrator.converter.Convert(orchestrator.getItemByAlias, pathProvider, item)
	if err != nil {
		return model, false
	}

	// create a view model
	model = viewmodel.ConversionModel{
		Base:    getBaseModel(root, item, pathProvider),
		Content: convertedContent,

		// files
		Files: orchestrator.fileOrchestrator.GetFiles(route),
	}

	return model, true
}
<commit_msg>Fixed the conversion model orchestrator path issue which led to broken image links.<commit_after>// Copyright 2014 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package orchestrator

import (
	"fmt"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/web/view/viewmodel"
)

type ConversionModelOrchestrator struct {
	*Orchestrator

	fileOrchestrator *FileOrchestrator
}

func (orchestrator *ConversionModelOrchestrator) GetConversionModel(hostname string, route route.Route) (model viewmodel.ConversionModel, found bool) {

	// get the root item
	root := orchestrator.rootItem()
	if root == nil {
		return model, false
	}

	// get the requested item
	item := orchestrator.getItem(route)
	if item == nil {
		orchestrator.logger.Info("There was no item for route %q.", route)
		return model, false
	}

	// create the path provider
	addressPrefix := fmt.Sprintf("/%s/%s/", hostname, item.Route().Value())
	pathProvider := orchestrator.absolutePather(addressPrefix)

	// convert content
	convertedContent, err := orchestrator.converter.Convert(orchestrator.getItemByAlias, pathProvider, item)
	if err != nil {
		return model, false
	}

	// create a view model
	model = viewmodel.ConversionModel{
		Base:    getBaseModel(root, item, pathProvider),
		Content: convertedContent,

		// files
		Files: orchestrator.fileOrchestrator.GetFiles(route),
	}

	return model, true
}
<|endoftext|>


<commit_before>package services

import (
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/zaaksam/dproxy/go/logger"
	"github.com/zaaksam/dproxy/go/model"
)

// Proxy 代理服务对象
var Proxy proxyService

func init() {
	Proxy.proxys = make(map[int64]*proxy)
}

type proxyService struct {
	proxys map[int64]*proxy
	mx     sync.Mutex
}

func (s *proxyService) addProxy(id int64, p *proxy) {
	s.mx.Lock()
	defer s.mx.Unlock()

	s.proxys[id] = p
}

func (s *proxyService) delProxy(id int64) {
	s.mx.Lock()
	defer s.mx.Unlock()

	delete(s.proxys, id)
}

func (s *proxyService) StartAll() error {
	list, err := PortMap.Find(1, 500, "", "", "")
	if err != nil {
		return err
	}

	if list.Total <= 0 {
		return nil
	}

	var mds []*model.PortMapModel
	if items, ok := list.Items.(*[]*model.PortMapModel); ok {
		mds = *items
	} else {
		return nil
	}

	for i, l := 0, len(mds); i < l; i++ {
		if !mds[i].IsStart {
			err = s.Start(mds[i].ID)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (s *proxyService) StopAll() {
	list, err := PortMap.Find(1, 500, "", "", "")
	if err != nil {
		return
	}

	if list.Total <= 0 {
		return
	}

	for partMapID := range Proxy.proxys {
		s.Stop(partMapID)
	}
}

// Start 启动代理请求服务
func (s *proxyService) Start(portMapID int64) error {
	if _, ok := s.proxys[portMapID]; !ok {
		//检查是否存在该配置
		md, err := PortMap.Get(portMapID)
		if err != nil {
			return err
		}

		//开启代理请求协程
		sourceAddr := fmt.Sprintf("%s:%d", md.SourceIP, md.SourcePort)
		targetAddr := fmt.Sprintf("%s:%d", md.TargetIP, md.TargetPort)
		listener, err := net.Listen("tcp", sourceAddr)
		if err != nil {
			return errors.New("代理启动失败：" + err.Error())
		}

		p := &proxy{
			listener: listener,
			stopChan: make(chan bool),
			clients:  make(map[string]chan bool),
		}
		s.addProxy(portMapID, p)

		go func() {
			for {
				//创建 client
				clientConn, err := p.listener.Accept()
				if err != nil {
					if !strings.Contains(err.Error(), "use of closed network connection") {
						logger.Error("客户端请求响应失败：", err)
					}
					break
				}

				//白名单检查
				clientAddr := strings.ToLower(clientConn.RemoteAddr().String())
				ip := strings.Split(clientAddr, ":")[0]
				if !WhiteList.Check(ip) {
					clientConn.Close()

					logger.Warning("非法请求：", clientAddr, "->", sourceAddr, "(", targetAddr, ")")
					continue
				}

				//创建 server
				serverConn, err := net.DialTimeout("tcp", targetAddr, 30*time.Second)
				if err != nil {
					clientConn.Close()

					logger.Error("服务端请求响应失败：", err)
					break
				}

				//注册客户端
				invalidChan := p.addClient(ip)
				//代理停止命令监测
				endChan := make(chan bool)
				go func() {
					select {
					case <-p.stopChan:
						//代理停止命令
					case <-invalidChan:
						//白名单失效通知，可能删除，可能过期
					case <-endChan:
						//单次请求连接结束通知
					}

					//关闭服务端连接，忽略各种状态判断，作为补充措施
					serverConn.Close()
				}()

				// server -> client
				go func() {
					_, err := io.Copy(clientConn, serverConn)
					if err == nil {
						//服务端EOF，关闭客户端连接
						clientConn.Close()
					} else {
						if strings.Contains(err.Error(), "use of closed network connection") {
							clientConn.Close()
						} else {
							logger.Error("服务响应接收失败：", err)
						}
					}
				}()

				// client -> server
				go func() {
					_, err := io.Copy(serverConn, clientConn)
					if err == nil {
						//客户端EOF，关闭服务端连接
						serverConn.Close()
					} else {
						if strings.Contains(err.Error(), "use of closed network connection") {
							serverConn.Close()
						} else {
							logger.Error("代理请求发送失败：", err)
						}
					}

					//本次请求结束
					close(endChan)
				}()
			}
		}()
	}

	return nil
}

func (s *proxyService) Stop(portMapID int64) {
	if p, ok := s.proxys[portMapID]; ok {
		p.listener.Close()
		close(p.stopChan)

		s.delProxy(portMapID)
	}
}

func (s *proxyService) setClientInvalid(ip string) {
	for _, p := range s.proxys {
		p.setClientInvalid(ip)
	}
}

// ================ proxy 定义 ========================

type proxy struct {
	listener net.Listener
	stopChan chan bool
	clients  map[string]chan bool // map[ip]invalidChan
	mx       sync.Mutex

	// clients  map[string]map[string]net.Conn // map[ip]map[port]net.Conn
	// ipMutex   sync.RWMutex
	// portMutex sync.RWMutex
}

func (p *proxy) addClient(ip string) <-chan bool {
	p.mx.Lock()
	defer p.mx.Unlock()

	invalidChan, ok := p.clients[ip]
	if !ok {
		invalidChan = make(chan bool)
		p.clients[ip] = invalidChan
	}

	return invalidChan
}

func (p *proxy) removeClient(ip string) {
	p.mx.Lock()
	defer p.mx.Unlock()

	delete(p.clients, ip)
}

func (p *proxy) setClientInvalid(ip string) {
	if invalidChan, ok := p.clients[ip]; ok {
		close(invalidChan)

		p.removeClient(ip)
	}
}
<commit_msg>在一端结束或发生错误时，总是关闭另一端<commit_after>package services

import (
	"errors"
	"fmt"
	"io"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/zaaksam/dproxy/go/logger"
	"github.com/zaaksam/dproxy/go/model"
)

// Proxy 代理服务对象
var Proxy proxyService

func init() {
	Proxy.proxys = make(map[int64]*proxy)
}

type proxyService struct {
	proxys map[int64]*proxy
	mx     sync.Mutex
}

func (s *proxyService) addProxy(id int64, p *proxy) {
	s.mx.Lock()
	defer s.mx.Unlock()

	s.proxys[id] = p
}

func (s *proxyService) delProxy(id int64) {
	s.mx.Lock()
	defer s.mx.Unlock()

	delete(s.proxys, id)
}

func (s *proxyService) StartAll() error {
	list, err := PortMap.Find(1, 500, "", "", "")
	if err != nil {
		return err
	}

	if list.Total <= 0 {
		return nil
	}

	var mds []*model.PortMapModel
	if items, ok := list.Items.(*[]*model.PortMapModel); ok {
		mds = *items
	} else {
		return nil
	}

	for i, l := 0, len(mds); i < l; i++ {
		if !mds[i].IsStart {
			err = s.Start(mds[i].ID)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func (s *proxyService) StopAll() {
	list, err := PortMap.Find(1, 500, "", "", "")
	if err != nil {
		return
	}

	if list.Total <= 0 {
		return
	}

	for partMapID := range Proxy.proxys {
		s.Stop(partMapID)
	}
}

// Start 启动代理请求服务
func (s *proxyService) Start(portMapID int64) error {
	if _, ok := s.proxys[portMapID]; !ok {
		//检查是否存在该配置
		md, err := PortMap.Get(portMapID)
		if err != nil {
			return err
		}

		//开启代理请求协程
		sourceAddr := fmt.Sprintf("%s:%d", md.SourceIP, md.SourcePort)
		targetAddr := fmt.Sprintf("%s:%d", md.TargetIP, md.TargetPort)
		listener, err := net.Listen("tcp", sourceAddr)
		if err != nil {
			return errors.New("代理启动失败：" + err.Error())
		}

		p := &proxy{
			listener: listener,
			stopChan: make(chan bool),
			clients:  make(map[string]chan bool),
		}
		s.addProxy(portMapID, p)

		go func() {
			for {
				//创建 client
				clientConn, err := p.listener.Accept()
				if err != nil {
					if !strings.Contains(err.Error(), "use of closed network connection") {
						logger.Error("客户端请求响应失败：", err)
					}
					break
				}

				//白名单检查
				clientAddr := strings.ToLower(clientConn.RemoteAddr().String())
				ip := strings.Split(clientAddr, ":")[0]
				if !WhiteList.Check(ip) {
					clientConn.Close()

					logger.Warning("非法请求：", clientAddr, "->", sourceAddr, "(", targetAddr, ")")
					continue
				}

				//创建 server
				serverConn, err := net.DialTimeout("tcp", targetAddr, 30*time.Second)
				if err != nil {
					clientConn.Close()

					logger.Error("服务端请求响应失败：", err)
					break
				}

				//注册客户端
				invalidChan := p.addClient(ip)
				//代理停止命令监测
				endChan := make(chan bool)
				go func() {
					select {
					case <-p.stopChan:
						//代理停止命令
					case <-invalidChan:
						//白名单失效通知，可能删除，可能过期
					case <-endChan:
						//单次请求连接结束通知
					}

					//关闭服务端连接，忽略各种状态判断，作为补充措施
					serverConn.Close()
				}()

				// server -> client
				go func() {
					_, err := io.Copy(clientConn, serverConn)
					if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
						logger.Error("服务响应接收失败：", err)
					}

					//服务端EOF 或 发生错误，均尝试关闭客户端连接
					clientConn.Close()
				}()

				// client -> server
				go func() {
					_, err := io.Copy(serverConn, clientConn)
					if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
						logger.Error("代理请求发送失败：", err)
					}

					//本次请求结束
					close(endChan)
				}()
			}
		}()
	}

	return nil
}

func (s *proxyService) Stop(portMapID int64) {
	if p, ok := s.proxys[portMapID]; ok {
		p.listener.Close()
		close(p.stopChan)

		s.delProxy(portMapID)
	}
}

func (s *proxyService) setClientInvalid(ip string) {
	for _, p := range s.proxys {
		p.setClientInvalid(ip)
	}
}

// ================ proxy 定义 ========================

type proxy struct {
	listener net.Listener
	stopChan chan bool
	clients  map[string]chan bool // map[ip]invalidChan
	mx       sync.Mutex

	// clients  map[string]map[string]net.Conn // map[ip]map[port]net.Conn
	// ipMutex   sync.RWMutex
	// portMutex sync.RWMutex
}

func (p *proxy) addClient(ip string) <-chan bool {
	p.mx.Lock()
	defer p.mx.Unlock()

	invalidChan, ok := p.clients[ip]
	if !ok {
		invalidChan = make(chan bool)
		p.clients[ip] = invalidChan
	}

	return invalidChan
}

func (p *proxy) removeClient(ip string) {
	p.mx.Lock()
	defer p.mx.Unlock()

	delete(p.clients, ip)
}

func (p *proxy) setClientInvalid(ip string) {
	if invalidChan, ok := p.clients[ip]; ok {
		close(invalidChan)

		p.removeClient(ip)
	}
}
<|endoftext|>


<commit_before>package systests

import (
	"bytes"
	"net/http"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/protocol/stellar1"
	"github.com/keybase/client/go/stellar"
	"github.com/keybase/client/go/teams"
	"github.com/keybase/stellarnet"
	"github.com/stellar/go/build"
	"github.com/stellar/go/clients/horizon"
	"github.com/stretchr/testify/require"
)

func TestStellarNoteRoundtripAndResets(t *testing.T) {
	ctx := newSMUContext(t)
	defer ctx.cleanup()

	// Sign up two users, bob and alice.
	alice := ctx.installKeybaseForUser("alice", 10)
	alice.signup()
	divDebug(ctx, "Signed up alice (%s)", alice.username)
	bob := ctx.installKeybaseForUser("bob", 10)
	bob.signup()
	divDebug(ctx, "Signed up bob (%s)", bob.username)

	t.Logf("note to self")
	encB64, err := stellar.NoteEncryptB64(context.Background(), alice.getPrimaryGlobalContext(), sampleNote(), nil)
	require.NoError(t, err)
	note, err := stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("note to both users")
	other := bob.userVersion()
	encB64, err = stellar.NoteEncryptB64(context.Background(), alice.getPrimaryGlobalContext(), sampleNote(), &other)
	require.NoError(t, err)

	t.Logf("decrypt as self")
	note, err = stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("decrypt as other")
	note, err = stellar.NoteDecryptB64(context.Background(), bob.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("reset sender")
	alice.reset()
	divDebug(ctx, "Reset bob (%s)", bob.username)
	alice.loginAfterReset(10)
	divDebug(ctx, "Bob logged in after reset")

	t.Logf("fail to decrypt as post-reset self")
	note, err = stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.Error(t, err)
	require.Equal(t, "note not encrypted for logged-in user", err.Error())

	t.Logf("decrypt as other")
	note, err = stellar.NoteDecryptB64(context.Background(), bob.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)
}

// Test took 38s on a dev server 2018-06-07
func TestStellarRelayAutoClaims(t *testing.T) {
	testStellarRelayAutoClaims(t, false, false)
}

// Test took 29s on a dev server 2018-06-07
func TestStellarRelayAutoClaimsWithPUK(t *testing.T) {
	testStellarRelayAutoClaims(t, true, true)
}

// Part 1:
// XLM is sent to a user before they have a [PUK / wallet].
// In the form of multiple relay payments.
// They then [get a PUK,] add a wallet, and enter the impteam,
// which all kick the autoclaim into gear.
//
// Part 2:
// A relay payment is sent to the user who already has a wallet.
// The funds should be claimed asap.
//
// To debug this test use log filter "stellar_test|poll-|AutoClaim|stellar.claim|pollfor"
func testStellarRelayAutoClaims(t *testing.T, startWithPUK, skipPart2 bool) {
	tt := newTeamTester(t)
	defer tt.cleanup()
	useStellarTestNet(t)

	alice := tt.addUser("alice")
	var bob *userPlusDevice
	if startWithPUK {
		bob = tt.addWalletlessUser("bob")
	} else {
		bob = tt.addPuklessUser("bob")
	}
	alice.kickTeamRekeyd()

	t.Logf("alice gets funded")
	res, err := alice.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
	require.NoError(t, err)
	gift(t, res[0].AccountID)

	t.Logf("alice sends a first relay payment to bob P1")
	attachIdentifyUI(t, alice.tc.G, newSimpleIdentifyUI())
	cmd := client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "50",
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	t.Logf("alice sends a second relay payment to bob P2")
	cmd = client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "30",
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	t.Logf("get the impteam seqno to wait on later")
	team, _, _, err := teams.LookupImplicitTeam(context.Background(), alice.tc.G, alice.username+","+bob.username, false)
	require.NoError(t, err)
	nextSeqno := team.NextSeqno()

	if startWithPUK {
		t.Logf("bob gets a wallet")
		bob.tc.Tp.DisableAutoWallet = false
		bob.tc.G.GetStellar().CreateWalletSoft(context.Background())
	} else {
		t.Logf("bob gets a PUK and wallet")
		bob.perUserKeyUpgrade()
		bob.tc.G.GetStellar().CreateWalletSoft(context.Background())

		t.Logf("wait for alice to add bob to their impteam")
		alice.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ID: team.ID}, nextSeqno)
	}

	pollTime := 20 * time.Second
	if libkb.UseCITime(bob.tc.G) {
		// This test is especially slow.
		pollTime = 30 * time.Second
	}

	pollFor(t, "claims to complete", pollTime, bob.tc.G, func(i int) bool {
		res, err = bob.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
		require.NoError(t, err)
		t.Logf("poll-1-%v: %v", i, res[0].BalanceDescription)
		if res[0].BalanceDescription == "0 XLM" {
			return false
		}
		if res[0].BalanceDescription == "49.9999800 XLM" {
			t.Logf("poll-1-%v: received T1 but not T2", i)
			return false
		}
		if res[0].BalanceDescription == "29.9999800 XLM" {
			t.Logf("poll-1-%v: received T2 but not T1", i)
			return false
		}
		t.Logf("poll-1-%v: received both payments", i)
		require.Equal(t, "79.9999700 XLM", res[0].BalanceDescription)
		return true
	})

	if skipPart2 {
		t.Logf("Skipping part 2")
		return
	}

	t.Logf("--------------------")
	t.Logf("Part 2: Alice sends a relay payment to bob who now already has a wallet")
	cmd = client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "10",
		ForceRelay:   true,
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	pollFor(t, "final claim to complete", pollTime, bob.tc.G, func(i int) bool {
		res, err = bob.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
		require.NoError(t, err)
		t.Logf("poll-2-%v: %v", i, res[0].BalanceDescription)
		if res[0].BalanceDescription == "79.9999700 XLM" {
			return false
		}
		t.Logf("poll-1-%v: received final payment", i)
		require.Equal(t, "89.9999600 XLM", res[0].BalanceDescription)
		return true
	})

}

func sampleNote() stellar1.NoteContents {
	return stellar1.NoteContents{
		Note:      "wizbang",
		StellarID: stellar1.TransactionID("6653fc2fdbc42ad51ccbe77ee0a3c29e258a5513c62fdc532cbfff91ab101abf"),
	}
}

// Friendbot sends someone XLM
func gift(t testing.TB, accountID stellar1.AccountID) {
	t.Logf("gift -> %v", accountID)
	url := "https://friendbot.stellar.org/?addr=" + accountID.String()
	for i := 0; i < retryCount; i++ {
		t.Logf("gift url: %v", url)
		res, err := http.Get(url)
		if err != nil {
			t.Logf("http get %s error: %s", url, err)
			continue
		}
		bodyBuf := new(bytes.Buffer)
		bodyBuf.ReadFrom(res.Body)
		res.Body.Close()
		t.Logf("gift res: %v", bodyBuf.String())
		if res.StatusCode == 200 {
			return
		}
		t.Logf("gift status not ok: %d", res.StatusCode)
	}
	t.Fatalf("gift to %s failed after multiple attempts", accountID)
}

func useStellarTestNet(t testing.TB) {
	stellarnet.SetClientAndNetwork(horizon.DefaultTestNetClient, build.TestNetwork)
}
<commit_msg>Disable stellar tests due to friendbot issues<commit_after>package systests

import (
	"bytes"
	"net/http"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol/keybase1"
	"github.com/keybase/client/go/protocol/stellar1"
	"github.com/keybase/client/go/stellar"
	"github.com/keybase/client/go/teams"
	"github.com/keybase/stellarnet"
	"github.com/stellar/go/build"
	"github.com/stellar/go/clients/horizon"
	"github.com/stretchr/testify/require"
)

const disable = false
const disableMsg = "friendbot issues"

func TestStellarNoteRoundtripAndResets(t *testing.T) {
	if disable {
		t.Skip(disableMsg)
	}
	ctx := newSMUContext(t)
	defer ctx.cleanup()

	// Sign up two users, bob and alice.
	alice := ctx.installKeybaseForUser("alice", 10)
	alice.signup()
	divDebug(ctx, "Signed up alice (%s)", alice.username)
	bob := ctx.installKeybaseForUser("bob", 10)
	bob.signup()
	divDebug(ctx, "Signed up bob (%s)", bob.username)

	t.Logf("note to self")
	encB64, err := stellar.NoteEncryptB64(context.Background(), alice.getPrimaryGlobalContext(), sampleNote(), nil)
	require.NoError(t, err)
	note, err := stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("note to both users")
	other := bob.userVersion()
	encB64, err = stellar.NoteEncryptB64(context.Background(), alice.getPrimaryGlobalContext(), sampleNote(), &other)
	require.NoError(t, err)

	t.Logf("decrypt as self")
	note, err = stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("decrypt as other")
	note, err = stellar.NoteDecryptB64(context.Background(), bob.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)

	t.Logf("reset sender")
	alice.reset()
	divDebug(ctx, "Reset bob (%s)", bob.username)
	alice.loginAfterReset(10)
	divDebug(ctx, "Bob logged in after reset")

	t.Logf("fail to decrypt as post-reset self")
	note, err = stellar.NoteDecryptB64(context.Background(), alice.getPrimaryGlobalContext(), encB64)
	require.Error(t, err)
	require.Equal(t, "note not encrypted for logged-in user", err.Error())

	t.Logf("decrypt as other")
	note, err = stellar.NoteDecryptB64(context.Background(), bob.getPrimaryGlobalContext(), encB64)
	require.NoError(t, err)
	require.Equal(t, sampleNote(), note)
}

// Test took 38s on a dev server 2018-06-07
func TestStellarRelayAutoClaims(t *testing.T) {
	if disable {
		t.Skip(disableMsg)
	}
	testStellarRelayAutoClaims(t, false, false)
}

// Test took 29s on a dev server 2018-06-07
func TestStellarRelayAutoClaimsWithPUK(t *testing.T) {
	if disable {
		t.Skip(disableMsg)
	}
	testStellarRelayAutoClaims(t, true, true)
}

// Part 1:
// XLM is sent to a user before they have a [PUK / wallet].
// In the form of multiple relay payments.
// They then [get a PUK,] add a wallet, and enter the impteam,
// which all kick the autoclaim into gear.
//
// Part 2:
// A relay payment is sent to the user who already has a wallet.
// The funds should be claimed asap.
//
// To debug this test use log filter "stellar_test|poll-|AutoClaim|stellar.claim|pollfor"
func testStellarRelayAutoClaims(t *testing.T, startWithPUK, skipPart2 bool) {
	tt := newTeamTester(t)
	defer tt.cleanup()
	useStellarTestNet(t)

	alice := tt.addUser("alice")
	var bob *userPlusDevice
	if startWithPUK {
		bob = tt.addWalletlessUser("bob")
	} else {
		bob = tt.addPuklessUser("bob")
	}
	alice.kickTeamRekeyd()

	t.Logf("alice gets funded")
	res, err := alice.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
	require.NoError(t, err)
	gift(t, res[0].AccountID)

	t.Logf("alice sends a first relay payment to bob P1")
	attachIdentifyUI(t, alice.tc.G, newSimpleIdentifyUI())
	cmd := client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "50",
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	t.Logf("alice sends a second relay payment to bob P2")
	cmd = client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "30",
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	t.Logf("get the impteam seqno to wait on later")
	team, _, _, err := teams.LookupImplicitTeam(context.Background(), alice.tc.G, alice.username+","+bob.username, false)
	require.NoError(t, err)
	nextSeqno := team.NextSeqno()

	if startWithPUK {
		t.Logf("bob gets a wallet")
		bob.tc.Tp.DisableAutoWallet = false
		bob.tc.G.GetStellar().CreateWalletSoft(context.Background())
	} else {
		t.Logf("bob gets a PUK and wallet")
		bob.perUserKeyUpgrade()
		bob.tc.G.GetStellar().CreateWalletSoft(context.Background())

		t.Logf("wait for alice to add bob to their impteam")
		alice.pollForTeamSeqnoLinkWithLoadArgs(keybase1.LoadTeamArg{ID: team.ID}, nextSeqno)
	}

	pollTime := 20 * time.Second
	if libkb.UseCITime(bob.tc.G) {
		// This test is especially slow.
		pollTime = 30 * time.Second
	}

	pollFor(t, "claims to complete", pollTime, bob.tc.G, func(i int) bool {
		res, err = bob.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
		require.NoError(t, err)
		t.Logf("poll-1-%v: %v", i, res[0].BalanceDescription)
		if res[0].BalanceDescription == "0 XLM" {
			return false
		}
		if res[0].BalanceDescription == "49.9999800 XLM" {
			t.Logf("poll-1-%v: received T1 but not T2", i)
			return false
		}
		if res[0].BalanceDescription == "29.9999800 XLM" {
			t.Logf("poll-1-%v: received T2 but not T1", i)
			return false
		}
		t.Logf("poll-1-%v: received both payments", i)
		require.Equal(t, "79.9999700 XLM", res[0].BalanceDescription)
		return true
	})

	if skipPart2 {
		t.Logf("Skipping part 2")
		return
	}

	t.Logf("--------------------")
	t.Logf("Part 2: Alice sends a relay payment to bob who now already has a wallet")
	cmd = client.CmdWalletSend{
		Contextified: libkb.NewContextified(alice.tc.G),
		Recipient:    bob.username,
		Amount:       "10",
		ForceRelay:   true,
	}
	for i := 0; i < retryCount; i++ {
		err = cmd.Run()
		if err == nil {
			break
		}
	}
	require.NoError(t, err)

	pollFor(t, "final claim to complete", pollTime, bob.tc.G, func(i int) bool {
		res, err = bob.stellarClient.GetWalletAccountsLocal(context.Background(), 0)
		require.NoError(t, err)
		t.Logf("poll-2-%v: %v", i, res[0].BalanceDescription)
		if res[0].BalanceDescription == "79.9999700 XLM" {
			return false
		}
		t.Logf("poll-1-%v: received final payment", i)
		require.Equal(t, "89.9999600 XLM", res[0].BalanceDescription)
		return true
	})

}

func sampleNote() stellar1.NoteContents {
	return stellar1.NoteContents{
		Note:      "wizbang",
		StellarID: stellar1.TransactionID("6653fc2fdbc42ad51ccbe77ee0a3c29e258a5513c62fdc532cbfff91ab101abf"),
	}
}

// Friendbot sends someone XLM
func gift(t testing.TB, accountID stellar1.AccountID) {
	t.Logf("gift -> %v", accountID)
	url := "https://friendbot.stellar.org/?addr=" + accountID.String()
	for i := 0; i < retryCount; i++ {
		t.Logf("gift url: %v", url)
		res, err := http.Get(url)
		if err != nil {
			t.Logf("http get %s error: %s", url, err)
			continue
		}
		bodyBuf := new(bytes.Buffer)
		bodyBuf.ReadFrom(res.Body)
		res.Body.Close()
		t.Logf("gift res: %v", bodyBuf.String())
		if res.StatusCode == 200 {
			return
		}
		t.Logf("gift status not ok: %d", res.StatusCode)
	}
	t.Fatalf("gift to %s failed after multiple attempts", accountID)
}

func useStellarTestNet(t testing.TB) {
	stellarnet.SetClientAndNetwork(horizon.DefaultTestNetClient, build.TestNetwork)
}
<|endoftext|>


<commit_before>package goltime

import (
  "strconv"
  "time"
)

type Timestamp struct{
  Year, Month, Day, Hour, Min, Sec int
}

func CreateTimestamp(time_point []string) Timestamp{
  year, _   := strconv.Atoi(time_point[0])
  month, _  := strconv.Atoi(time_point[1])
  day, _    := strconv.Atoi(time_point[2])
  hour, _   := strconv.Atoi(time_point[3])
  min, _    := strconv.Atoi(time_point[4])
  sec, _    := strconv.Atoi(time_point[5])

  return Timestamp{
    Year: year,
    Month: month,
    Day: day,
    Hour: hour,
    Min: min,
    Sec: sec,
  }
}

func (timestamp *Timestamp) Time() time.Time{
  return time.Date(timestamp.Year, time.Month(timestamp.Month), timestamp.Day,
                   timestamp.Hour, timestamp.Min, timestamp.Sec, 0, time.UTC)
}
<commit_msg>goltime create timestamp from http request<commit_after>package goltime

import (
  "strconv"
  "time"
  "net/http"
)

type Timestamp struct{
  Year, Month, Day, Hour, Min, Sec int
}

func CreateTimestamp(time_point []string) Timestamp{
  year, _   := strconv.Atoi(time_point[0])
  month, _  := strconv.Atoi(time_point[1])
  day, _    := strconv.Atoi(time_point[2])
  hour, _   := strconv.Atoi(time_point[3])
  min, _    := strconv.Atoi(time_point[4])
  sec, _    := strconv.Atoi(time_point[5])

  return Timestamp{
    Year: year,
    Month: month,
    Day: day,
    Hour: hour,
    Min: min,
    Sec: sec,
  }
}

func TimestampFromHTTPRequest(req *http.Request) Timestamp{
  return CreateTimestamp([]string {
    req.Form["year"][0], req.Form["month"][0], req.Form["day"][0],
    req.Form["hour"][0], req.Form["min"][0], req.Form["sec"][0],
  })
}

func (timestamp *Timestamp) Time() time.Time{
  return time.Date(timestamp.Year, time.Month(timestamp.Month), timestamp.Day,
                   timestamp.Hour, timestamp.Min, timestamp.Sec, 0, time.UTC)
}
<|endoftext|>


<commit_before>package gosql

import (
	"fmt"
	"reflect"
	"strings"
)

func Columns(db Dbi) ([]*Column, error) {
	names, err := columnNames(db, &Column{})
	if err != nil {
		return nil, err
	}
	q := "SELECT " + strings.Join(names, ", ") + " FROM information_schema.columns"
	rows, err := db.Query(q)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		t := &Column{}
		rows.Scan(&t.TableCatalog, &t.TableSchema, &t.TableName, &t.ColumnName, &t.OrdinalPosition, &t.ColumnDefault, &t.IsNullable, &t.DataType, &t.CharacterMaximumLength, &t.CharacterOctetLength, &t.NumericPrecision, &t.NumericPrecisionRadix, &t.NumericScale, &t.DatetimePrecision, &t.IntervalType, &t.IntervalPrecision, &t.CharacterSetCatalog, &t.CharacterSetSchema, &t.CharacterSetName, &t.CollationCatalog, &t.CollationSchema, &t.CollationName, &t.DomainCatalog, &t.DomainSchema, &t.DomainName, &t.UdtCatalog, &t.UdtSchema, &t.UdtName, &t.ScopeCatalog, &t.ScopeSchema, &t.ScopeName, &t.MaximumCardinality, &t.DtdIdentifier, &t.IsSelfReferencing, &t.IsIdentity, &t.IdentityGeneration, &t.IdentityStart, &t.IdentityIncrement, &t.IdentityMaximum, &t.IdentityMinimum, &t.IdentityCycle, &t.IsGenerated, &t.GenerationExpression, &t.IsUpdatable)
		err = rows.Scan()
		if err != nil {
			return nil, err
		}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return nil, nil
}

type tableOptions struct {
	TableSchema    string
	TableName      string
	IncludeColumns bool
}

type tableOpt func(*tableOptions)

func (t *tableOptions) apply(funcs ...tableOpt) {
	for _, f := range funcs {
		f(t)
	}
}

func WithName(s string) tableOpt {
	return func(o *tableOptions) {
		o.TableName = s
	}
}

var PublicTables = func(o *tableOptions) {
	o.TableSchema = "public"
}

func Tables(db Dbi, funcs ...tableOpt) ([]*Table, error) {
	opt := &tableOptions{}
	opt.apply(funcs...)

	names, err := columnNames(db, &Table{})
	if err != nil {
		return nil, err
	}
	q := "SELECT " + strings.Join(names, ", ") + " FROM information_schema.tables"
	v := []interface{}{}
	w := []string{}
	if opt.TableSchema != "" {
		w = append(w, "table_schema = $1")
		v = append(v, opt.TableSchema)
	}
	if opt.TableName != "" {
		w = append(w, fmt.Sprintf("table_name = $%d", len(w)+1))
		v = append(v, opt.TableName)
	}
	if len(w) > 0 {
		q += " WHERE " + strings.Join(w, " AND ")
	}
	rows, err := db.Query(q, v...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []*Table{}
	return out, UnmarshalRows(rows, &out)
}

func columnNames(db Dbi, i interface{}) ([]string, error) {
	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	t := v.Type()
	out := []string{}
	for i := 0; i < t.NumField(); i++ {
		column := t.Field(i).Tag.Get("sql")
		if column != "" {
			out = append(out, column)
		}
	}
	return out, nil
}

type Table struct {
	TableCatalog              string  `sql:"table_catalog"`
	TableSchema               string  `sql:"table_schema"`
	TableName                 string  `sql:"table_name"`
	TableType                 string  `sql:"table_type"`
	SelfReferencingColumnName *string `sql:"self_referencing_column_name"`
	ReferenceGeneration       *string `sql:"reference_generation"`
	UserDefinedTypeCatalog    *string `sql:"user_defined_type_catalog"`
	UserDefinedTypeSchema     *string `sql:"user_defined_type_schema"`
	UserDefinedTypeName       *string `sql:"user_defined_type_name"`
	IsInsertableInto          string  `sql:"is_insertable_into"`
	IsTyped                   string  `sql:"is_typed"`
	CommitAction              *string `sql:"commit_action"`
}

type Column struct {
	TableCatalog           string `sql:"table_catalog"`            // information_schema.sql_identifier
	TableSchema            string `sql:"table_schema"`             // information_schema.sql_identifier
	TableName              string `sql:"table_name"`               // information_schema.sql_identifier
	ColumnName             string `sql:"column_name"`              // information_schema.sql_identifier
	OrdinalPosition        int    `sql:"ordinal_position"`         // information_schema.cardinal_number
	ColumnDefault          string `sql:"column_default"`           // information_schema.character_data
	IsNullable             bool   `sql:"is_nullable"`              // information_schema.yes_or_no
	DataType               string `sql:"data_type"`                // information_schema.character_data
	CharacterMaximumLength int    `sql:"character_maximum_length"` // information_schema.cardinal_number
	CharacterOctetLength   int    `sql:"character_octet_length"`   // information_schema.cardinal_number
	NumericPrecision       int    `sql:"numeric_precision"`        // information_schema.cardinal_number
	NumericPrecisionRadix  int    `sql:"numeric_precision_radix"`  // information_schema.cardinal_number
	NumericScale           int    `sql:"numeric_scale"`            // information_schema.cardinal_number
	DatetimePrecision      int    `sql:"datetime_precision"`       // information_schema.cardinal_number
	IntervalType           string `sql:"interval_type"`            // information_schema.character_data
	IntervalPrecision      int    `sql:"interval_precision"`       // information_schema.cardinal_number
	CharacterSetCatalog    string `sql:"character_set_catalog"`    // information_schema.sql_identifier
	CharacterSetSchema     string `sql:"character_set_schema"`     // information_schema.sql_identifier
	CharacterSetName       string `sql:"character_set_name"`       // information_schema.sql_identifier
	CollationCatalog       string `sql:"collation_catalog"`        // information_schema.sql_identifier
	CollationSchema        string `sql:"collation_schema"`         // information_schema.sql_identifier
	CollationName          string `sql:"collation_name"`           // information_schema.sql_identifier
	DomainCatalog          string `sql:"domain_catalog"`           // information_schema.sql_identifier
	DomainSchema           string `sql:"domain_schema"`            // information_schema.sql_identifier
	DomainName             string `sql:"domain_name"`              // information_schema.sql_identifier
	UdtCatalog             string `sql:"udt_catalog"`              // information_schema.sql_identifier
	UdtSchema              string `sql:"udt_schema"`               // information_schema.sql_identifier
	UdtName                string `sql:"udt_name"`                 // information_schema.sql_identifier
	ScopeCatalog           string `sql:"scope_catalog"`            // information_schema.sql_identifier
	ScopeSchema            string `sql:"scope_schema"`             // information_schema.sql_identifier
	ScopeName              string `sql:"scope_name"`               // information_schema.sql_identifier
	MaximumCardinality     int    `sql:"maximum_cardinality"`      // information_schema.cardinal_number
	DtdIdentifier          string `sql:"dtd_identifier"`           // information_schema.sql_identifier
	IsSelfReferencing      bool   `sql:"is_self_referencing"`      // information_schema.yes_or_no
	IsIdentity             bool   `sql:"is_identity"`              // information_schema.yes_or_no
	IdentityGeneration     string `sql:"identity_generation"`      // information_schema.character_data
	IdentityStart          string `sql:"identity_start"`           // information_schema.character_data
	IdentityIncrement      string `sql:"identity_increment"`       // information_schema.character_data
	IdentityMaximum        string `sql:"identity_maximum"`         // information_schema.character_data
	IdentityMinimum        string `sql:"identity_minimum"`         // information_schema.character_data
	IdentityCycle          bool   `sql:"identity_cycle"`           // information_schema.yes_or_no
	IsGenerated            string `sql:"is_generated"`             // information_schema.character_data
	GenerationExpression   string `sql:"generation_expression"`    // information_schema.character_data
	IsUpdatable            bool   `sql:"is_updatable"`             // information_schema.yes_or_no
}
<commit_msg>add view support for InformationSchema<commit_after>package gosql

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

func Columns(db Dbi) ([]*Column, error) {
	names, err := columnNames(db, &Column{})
	if err != nil {
		return nil, err
	}
	q := "SELECT " + strings.Join(names, ", ") + " FROM information_schema.columns"
	rows, err := db.Query(q)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		t := &Column{}
		rows.Scan(&t.TableCatalog, &t.TableSchema, &t.TableName, &t.ColumnName, &t.OrdinalPosition, &t.ColumnDefault, &t.IsNullable, &t.DataType, &t.CharacterMaximumLength, &t.CharacterOctetLength, &t.NumericPrecision, &t.NumericPrecisionRadix, &t.NumericScale, &t.DatetimePrecision, &t.IntervalType, &t.IntervalPrecision, &t.CharacterSetCatalog, &t.CharacterSetSchema, &t.CharacterSetName, &t.CollationCatalog, &t.CollationSchema, &t.CollationName, &t.DomainCatalog, &t.DomainSchema, &t.DomainName, &t.UdtCatalog, &t.UdtSchema, &t.UdtName, &t.ScopeCatalog, &t.ScopeSchema, &t.ScopeName, &t.MaximumCardinality, &t.DtdIdentifier, &t.IsSelfReferencing, &t.IsIdentity, &t.IdentityGeneration, &t.IdentityStart, &t.IdentityIncrement, &t.IdentityMaximum, &t.IdentityMinimum, &t.IdentityCycle, &t.IsGenerated, &t.GenerationExpression, &t.IsUpdatable)
		err = rows.Scan()
		if err != nil {
			return nil, err
		}
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return nil, nil
}

type tableOptions struct {
	TableSchema    string
	TableName      string
	IncludeColumns bool
}

type tableOpt func(*tableOptions)

func (t *tableOptions) apply(funcs ...tableOpt) {
	for _, f := range funcs {
		f(t)
	}
}

func WithName(s string) tableOpt {
	return func(o *tableOptions) {
		o.TableName = s
	}
}

func PublicTables(o *tableOptions) {
	o.TableSchema = "public"
}

type View struct {
	TableCatalog            string `sql:"table_catalog"`              //              | information_schema.sql_identifier |
	TableSchema             string `sql:"table_schema"`               //               | information_schema.sql_identifier |
	TableName               string `sql:"table_name"`                 //                 | information_schema.sql_identifier |
	ViewDefinition          string `sql:"view_definition"`            //            | information_schema.character_data |
	CheckOption             string `sql:"check_option"`               //               | information_schema.character_data |
	IsUpdatable             string `sql:"is_updatable"`               //               | information_schema.yes_or_no      |
	IsInsertableInto        string `sql:"is_insertable_into"`         //         | information_schema.yes_or_no      |
	IsTriggerUpdatable      string `sql:"is_trigger_updatable"`       //       | information_schema.yes_or_no      |
	IsTriggerDeletable      string `sql:"is_trigger_deletable"`       //       | information_schema.yes_or_no      |
	IsTriggerInsertableInto string `sql:"is_trigger_insertable_into"` // | information_schema.yes_or_no      |
}

type viewOptions struct {
	Catalog string
	Schema  string
	Name    string
}

func PublicViews(o *viewOptions) {
	o.Schema = "public"
}

type viewOpt func(*viewOptions)

func Views(db Dbi, funcs ...viewOpt) ([]*View, error) {
	o := &viewOptions{}
	for _, f := range funcs {
		f(o)
	}
	w := []string{}
	i := []interface{}{}
	for _, v := range []struct{ Name, Value string }{
		{"table_catalog", o.Catalog},
		{"table_schema", o.Schema},
		{"table_name", o.Name},
	} {
		if v.Value != "" {
			w = append(w, v.Name+" = $"+strconv.Itoa(len(w)+1))
			i = append(i, v.Value)
		}
	}
	q := "SELECT table_catalog, table_schema, table_name FROM information_schema.views"
	if len(w) > 0 {
		q += " WHERE " + strings.Join(w, " AND ")
	}
	var v []*View
	return v, SelectStructs(db, q, &v, i...)
}

func Tables(db Dbi, funcs ...tableOpt) ([]*Table, error) {
	opt := &tableOptions{}
	opt.apply(funcs...)

	names, err := columnNames(db, &Table{})
	if err != nil {
		return nil, err
	}
	q := "SELECT " + strings.Join(names, ", ") + " FROM information_schema.tables"
	v := []interface{}{}
	w := []string{}
	if opt.TableSchema != "" {
		w = append(w, "table_schema = $1")
		v = append(v, opt.TableSchema)
	}
	if opt.TableName != "" {
		w = append(w, fmt.Sprintf("table_name = $%d", len(w)+1))
		v = append(v, opt.TableName)
	}
	if len(w) > 0 {
		q += " WHERE " + strings.Join(w, " AND ")
	}
	rows, err := db.Query(q, v...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	out := []*Table{}
	return out, UnmarshalRows(rows, &out)
}

func columnNames(db Dbi, i interface{}) ([]string, error) {
	v := reflect.ValueOf(i)
	if v.Kind() == reflect.Ptr {
		v = v.Elem()
	}
	t := v.Type()
	out := []string{}
	for i := 0; i < t.NumField(); i++ {
		column := t.Field(i).Tag.Get("sql")
		if column != "" {
			out = append(out, column)
		}
	}
	return out, nil
}

type Table struct {
	TableCatalog              string  `sql:"table_catalog"`
	TableSchema               string  `sql:"table_schema"`
	TableName                 string  `sql:"table_name"`
	TableType                 string  `sql:"table_type"`
	SelfReferencingColumnName *string `sql:"self_referencing_column_name"`
	ReferenceGeneration       *string `sql:"reference_generation"`
	UserDefinedTypeCatalog    *string `sql:"user_defined_type_catalog"`
	UserDefinedTypeSchema     *string `sql:"user_defined_type_schema"`
	UserDefinedTypeName       *string `sql:"user_defined_type_name"`
	IsInsertableInto          string  `sql:"is_insertable_into"`
	IsTyped                   string  `sql:"is_typed"`
	CommitAction              *string `sql:"commit_action"`
}

type Column struct {
	TableCatalog           string `sql:"table_catalog"`            // information_schema.sql_identifier
	TableSchema            string `sql:"table_schema"`             // information_schema.sql_identifier
	TableName              string `sql:"table_name"`               // information_schema.sql_identifier
	ColumnName             string `sql:"column_name"`              // information_schema.sql_identifier
	OrdinalPosition        int    `sql:"ordinal_position"`         // information_schema.cardinal_number
	ColumnDefault          string `sql:"column_default"`           // information_schema.character_data
	IsNullable             bool   `sql:"is_nullable"`              // information_schema.yes_or_no
	DataType               string `sql:"data_type"`                // information_schema.character_data
	CharacterMaximumLength int    `sql:"character_maximum_length"` // information_schema.cardinal_number
	CharacterOctetLength   int    `sql:"character_octet_length"`   // information_schema.cardinal_number
	NumericPrecision       int    `sql:"numeric_precision"`        // information_schema.cardinal_number
	NumericPrecisionRadix  int    `sql:"numeric_precision_radix"`  // information_schema.cardinal_number
	NumericScale           int    `sql:"numeric_scale"`            // information_schema.cardinal_number
	DatetimePrecision      int    `sql:"datetime_precision"`       // information_schema.cardinal_number
	IntervalType           string `sql:"interval_type"`            // information_schema.character_data
	IntervalPrecision      int    `sql:"interval_precision"`       // information_schema.cardinal_number
	CharacterSetCatalog    string `sql:"character_set_catalog"`    // information_schema.sql_identifier
	CharacterSetSchema     string `sql:"character_set_schema"`     // information_schema.sql_identifier
	CharacterSetName       string `sql:"character_set_name"`       // information_schema.sql_identifier
	CollationCatalog       string `sql:"collation_catalog"`        // information_schema.sql_identifier
	CollationSchema        string `sql:"collation_schema"`         // information_schema.sql_identifier
	CollationName          string `sql:"collation_name"`           // information_schema.sql_identifier
	DomainCatalog          string `sql:"domain_catalog"`           // information_schema.sql_identifier
	DomainSchema           string `sql:"domain_schema"`            // information_schema.sql_identifier
	DomainName             string `sql:"domain_name"`              // information_schema.sql_identifier
	UdtCatalog             string `sql:"udt_catalog"`              // information_schema.sql_identifier
	UdtSchema              string `sql:"udt_schema"`               // information_schema.sql_identifier
	UdtName                string `sql:"udt_name"`                 // information_schema.sql_identifier
	ScopeCatalog           string `sql:"scope_catalog"`            // information_schema.sql_identifier
	ScopeSchema            string `sql:"scope_schema"`             // information_schema.sql_identifier
	ScopeName              string `sql:"scope_name"`               // information_schema.sql_identifier
	MaximumCardinality     int    `sql:"maximum_cardinality"`      // information_schema.cardinal_number
	DtdIdentifier          string `sql:"dtd_identifier"`           // information_schema.sql_identifier
	IsSelfReferencing      bool   `sql:"is_self_referencing"`      // information_schema.yes_or_no
	IsIdentity             bool   `sql:"is_identity"`              // information_schema.yes_or_no
	IdentityGeneration     string `sql:"identity_generation"`      // information_schema.character_data
	IdentityStart          string `sql:"identity_start"`           // information_schema.character_data
	IdentityIncrement      string `sql:"identity_increment"`       // information_schema.character_data
	IdentityMaximum        string `sql:"identity_maximum"`         // information_schema.character_data
	IdentityMinimum        string `sql:"identity_minimum"`         // information_schema.character_data
	IdentityCycle          bool   `sql:"identity_cycle"`           // information_schema.yes_or_no
	IsGenerated            string `sql:"is_generated"`             // information_schema.character_data
	GenerationExpression   string `sql:"generation_expression"`    // information_schema.character_data
	IsUpdatable            bool   `sql:"is_updatable"`             // information_schema.yes_or_no
}
<|endoftext|>