<commit_before>package main

import (
	"bufio"
	"fmt"
	"github.com/keimoon/bog"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// Archive creates an archive from a folder or file
func Archive() int {
	if len(Args) <= 1 {
		Usage()
		return 2
	}
	folder := Args[1]
	stat, err := os.Stat(folder)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}
	filePackageName := makePackageName(folder)
	var outputFileName string
	var varName string
	if len(filePackageName) > 0 {
		outputFileName = filePackageName + "-archive.go"
		varName = makePublicVariableName(filePackageName) + "Archive"
	} else {
		outputFileName = Options.PackageName + "-archive.go"
		varName = makePublicVariableName(Options.PackageName) + "Archive"
	}
	outputFolder := "."
	if !Options.isCwd && Options.PackageName != "main" {
		outputFolder = Options.PackageName
		err = os.RemoveAll(outputFolder)
		if err != nil {
			fmt.Println(err)
			return 1
		}
	} else {
		err = os.RemoveAll(outputFileName)
		if err != nil {
			fmt.Println(err)
			return 1
		}
	}
	fileVars := []*FileVar{}
	now := strconv.FormatInt(time.Now().Unix(), 10)
	if !Options.Dev {
		if stat.IsDir() {
			err = walk(folder, func(path string, info os.FileInfo, children []string) error {
				fileVar := &FileVar{
					VarName: makeVariableName(now + "_" + path),
					Path:    "/" + strings.TrimLeft(strings.TrimPrefix(path, folder), "/"),
					Stat: &bog.FileInfo{
						FileName:    info.Name(),
						FileSize:    info.Size(),
						FileMode:    info.Mode(),
						FileModTime: info.ModTime(),
					},
				}
				if info.IsDir() {
					fileVar.IsDir = true
					for _, child := range children {
						fileVar.Children = append(fileVar.Children, makeVariableName(now+"_"+path+"_"+child))
					}
				} else {
					b, err := ioutil.ReadFile(path)
					if err != nil {
						return err
					}
					fileVar.Data = b
				}
				fileVars = append(fileVars, fileVar)
				return nil
			}, ignoreRules{})
			if err != nil {
				fmt.Println(err)
				return 1
			}
		} else {
			fileVar := &FileVar{
				VarName: makeVariableName(now + "_" + folder),
				Path:    "/",
				Stat:    stat,
			}
			b, err := ioutil.ReadFile(folder)
			if err != nil {
				return 2
			}
			fileVar.Data = b
			fileVars = append(fileVars, fileVar)
		}
	}
	if !Options.isCwd {
		err = os.MkdirAll(outputFolder, 0755)
	}
	f, err := os.Create(filepath.Join(outputFolder, outputFileName))
	if err != nil {
		fmt.Println(err)
		return 1
	}
	defer f.Close()
	mainTmpl, err := loadTemplate("main.go.tmpl")
	if err != nil {
		fmt.Println(err)
		return 1
	}
	tmplData := &struct {
		PackageName string
		Files       []*FileVar
		Root        string
		VarName     string
		IsFile      bool
		Dev         bool
	}{
		PackageName: Options.PackageName,
		Files:       fileVars,
		Root:        folder,
		VarName:     varName,
		IsFile:      !stat.IsDir(),
		Dev:         Options.Dev,
	}
	err = mainTmpl.Execute(f, tmplData)
	if err != nil {
		fmt.Println(err)
		return 1
	}
	return 0
}

// FileVar represents a file or folder
type FileVar struct {
	VarName  string
	Path     string
	IsDir    bool
	Stat     os.FileInfo
	Data     []byte
	Children []string
}

type ignoreRules []string

func (r ignoreRules) ignore(name string) bool {
	if name == ".bogignore" {
		return true
	}
	for _, rule := range r {
		if matched, _ := filepath.Match(rule, name); matched {
			return true
		}
	}
	return false
}

type walkFunc func(path string, info os.FileInfo, children []string) error

func walk(root string, walkFn walkFunc, rules ignoreRules) error {
	info, err := os.Stat(root)
	if err != nil {
		return err
	}
	children := []string{}
	if info.IsDir() {
		f, err := os.Open(filepath.Join(root, ".bogignore"))
		if err == nil {
			defer f.Close()
			scanner := bufio.NewScanner(f)
			for scanner.Scan() {
				rules = append(rules, scanner.Text())
			}
			if err = scanner.Err(); err != nil {
				return err
			}
		}
		childrenInfos, err := ioutil.ReadDir(root)
		if err != nil {
			return err
		}
		for _, childInfo := range childrenInfos {
			if rules.ignore(childInfo.Name()) {
				continue
			}
			err = walk(filepath.Join(root, childInfo.Name()), walkFn, rules)
			if err != nil {
				return err
			}
			children = append(children, childInfo.Name())
		}
	}
	return walkFn(root, info, children)
}

func loadTemplate(name string) (*template.Template, error) {
	f, err := TemplatesArchive.Open("main.go.tmpl")
	if err != nil {
		return nil, err
	}
	b, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	t := template.New(name)
	return t.Parse(string(b))
}
<commit_msg>No unlink output folder anymore<commit_after>package main

import (
	"bufio"
	"fmt"
	"github.com/keimoon/bog"
	"io/ioutil"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"text/template"
	"time"
)

// Archive creates an archive from a folder or file
func Archive() int {
	if len(Args) <= 1 {
		Usage()
		return 2
	}
	folder := Args[1]
	stat, err := os.Stat(folder)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		return 1
	}
	filePackageName := makePackageName(folder)
	var outputFileName string
	var varName string
	if len(filePackageName) > 0 {
		outputFileName = filePackageName + "-archive.go"
		varName = makePublicVariableName(filePackageName) + "Archive"
	} else {
		outputFileName = Options.PackageName + "-archive.go"
		varName = makePublicVariableName(Options.PackageName) + "Archive"
	}
	outputFolder := "."
	if !Options.isCwd && Options.PackageName != "main" {
		outputFolder = Options.PackageName
		if err != nil {
			fmt.Println(err)
			return 1
		}
	}
	err = os.RemoveAll(outputFileName)
	if err != nil {
		fmt.Println(err)
		return 1
	}
	fileVars := []*FileVar{}
	now := strconv.FormatInt(time.Now().Unix(), 10)
	if !Options.Dev {
		if stat.IsDir() {
			err = walk(folder, func(path string, info os.FileInfo, children []string) error {
				fileVar := &FileVar{
					VarName: makeVariableName(now + "_" + path),
					Path:    "/" + strings.TrimLeft(strings.TrimPrefix(path, folder), "/"),
					Stat: &bog.FileInfo{
						FileName:    info.Name(),
						FileSize:    info.Size(),
						FileMode:    info.Mode(),
						FileModTime: info.ModTime(),
					},
				}
				if info.IsDir() {
					fileVar.IsDir = true
					for _, child := range children {
						fileVar.Children = append(fileVar.Children, makeVariableName(now+"_"+path+"_"+child))
					}
				} else {
					b, err := ioutil.ReadFile(path)
					if err != nil {
						return err
					}
					fileVar.Data = b
				}
				fileVars = append(fileVars, fileVar)
				return nil
			}, ignoreRules{})
			if err != nil {
				fmt.Println(err)
				return 1
			}
		} else {
			fileVar := &FileVar{
				VarName: makeVariableName(now + "_" + folder),
				Path:    "/",
				Stat:    stat,
			}
			b, err := ioutil.ReadFile(folder)
			if err != nil {
				return 2
			}
			fileVar.Data = b
			fileVars = append(fileVars, fileVar)
		}
	}
	if !Options.isCwd {
		err = os.MkdirAll(outputFolder, 0755)
	}
	f, err := os.Create(filepath.Join(outputFolder, outputFileName))
	if err != nil {
		fmt.Println(err)
		return 1
	}
	defer f.Close()
	mainTmpl, err := loadTemplate("main.go.tmpl")
	if err != nil {
		fmt.Println(err)
		return 1
	}
	tmplData := &struct {
		PackageName string
		Files       []*FileVar
		Root        string
		VarName     string
		IsFile      bool
		Dev         bool
	}{
		PackageName: Options.PackageName,
		Files:       fileVars,
		Root:        folder,
		VarName:     varName,
		IsFile:      !stat.IsDir(),
		Dev:         Options.Dev,
	}
	err = mainTmpl.Execute(f, tmplData)
	if err != nil {
		fmt.Println(err)
		return 1
	}
	return 0
}

// FileVar represents a file or folder
type FileVar struct {
	VarName  string
	Path     string
	IsDir    bool
	Stat     os.FileInfo
	Data     []byte
	Children []string
}

type ignoreRules []string

func (r ignoreRules) ignore(name string) bool {
	if name == ".bogignore" {
		return true
	}
	for _, rule := range r {
		if matched, _ := filepath.Match(rule, name); matched {
			return true
		}
	}
	return false
}

type walkFunc func(path string, info os.FileInfo, children []string) error

func walk(root string, walkFn walkFunc, rules ignoreRules) error {
	info, err := os.Stat(root)
	if err != nil {
		return err
	}
	children := []string{}
	if info.IsDir() {
		f, err := os.Open(filepath.Join(root, ".bogignore"))
		if err == nil {
			defer f.Close()
			scanner := bufio.NewScanner(f)
			for scanner.Scan() {
				rules = append(rules, scanner.Text())
			}
			if err = scanner.Err(); err != nil {
				return err
			}
		}
		childrenInfos, err := ioutil.ReadDir(root)
		if err != nil {
			return err
		}
		for _, childInfo := range childrenInfos {
			if rules.ignore(childInfo.Name()) {
				continue
			}
			err = walk(filepath.Join(root, childInfo.Name()), walkFn, rules)
			if err != nil {
				return err
			}
			children = append(children, childInfo.Name())
		}
	}
	return walkFn(root, info, children)
}

func loadTemplate(name string) (*template.Template, error) {
	f, err := TemplatesArchive.Open("main.go.tmpl")
	if err != nil {
		return nil, err
	}
	b, err := ioutil.ReadAll(f)
	if err != nil {
		return nil, err
	}
	t := template.New(name)
	return t.Parse(string(b))
}
<|endoftext|>


<commit_before>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package sqlbuilder

import (
	"bytes"
	"database/sql"
	"errors"

	"github.com/issue9/orm/fetch"
	"github.com/issue9/orm/forward"
)

// 一组标记位，用于标记某些可重复调用的函数，是否是第一次调用。
// 比如 OrderBy 在第一次调用时，需要填 `ORDER BY`字符串，之后
// 的只要跟着列名和相应的排序方式即可。
const (
	flagWhere  int8 = 1 << iota
	flagOrder       // ORDER BY
	flagColumn      // SELECT
	flagSet         // UPDATE 的 SET
	flagValues      // INSERT 的 VALUES
)

// 当出现此错误时，说明在构建 SQL 语句的过程中出现了错误，
// 需要调用 SQLBuilder.Errors() 获取详细的错误信息。
var ErrHasErrors = errors.New("语句中包含一个或多个错误")

// SQLBuilder 一个简单的 SQL 语句接接工具。
// NOTE: SQLBuilder 的所有函数调用，将直接拼接到字符串，
// 而不会做缓存，所以调用顺序必须与 SQL 语法相同。
//
// DELETE
//  sql := New(engine).
//      Delete("table1").
//      Where("id>?", 5).
//      And("type=?", 2)
//  query, vals, err := sql.String()
type SQLBuilder struct {
	engine forward.Engine
	buffer *bytes.Buffer
	args   []interface{}
	flag   int8
	errors []error
}

// 声明一个 SQLBuilder 实例
func New(e forward.Engine) *SQLBuilder {
	return &SQLBuilder{
		engine: e,
		buffer: new(bytes.Buffer),
		args:   make([]interface{}, 0, 10),
		flag:   0,
		errors: make([]error, 0, 5),
	}
}

// 重置所有的数据为初始值，这样可以重复利用该 SQLBuilder 对象。
func (sb *SQLBuilder) Reset() *SQLBuilder {
	sb.buffer.Reset()
	sb.args = sb.args[:0]
	sb.flag = 0
	sb.errors = sb.errors[:0]

	return sb
}

func (sb *SQLBuilder) isSetFlag(flag int8) bool {
	return sb.flag&flag > 0
}

func (sb *SQLBuilder) setFlag(flag int8) {
	sb.flag |= flag
}

// 是否在构建过程中触发错误信息。
//
// NOTE: 在构建完 SQL 语句，准备执行数据库操作之前，
// 都应该调用此函数确认是否存在错误。
func (sb *SQLBuilder) HasError() bool {
	return len(sb.errors) > 0
}

// 返回所有的错误内容。
func (sb *SQLBuilder) Errors() []error {
	return sb.errors
}

func (sb *SQLBuilder) WriteByte(c byte) *SQLBuilder {
	err := sb.buffer.WriteByte(c)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	return sb
}

func (sb *SQLBuilder) WriteString(s string) *SQLBuilder {
	_, err := sb.buffer.WriteString(s)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	return sb
}

// 去掉尾部的 n 个字符。
func (sb *SQLBuilder) TruncateLast(n int) *SQLBuilder {
	sb.buffer.Truncate(sb.buffer.Len() - n)
	return sb
}

// 启动一个 DELETE 语句。
func (sb *SQLBuilder) Delete(table string) *SQLBuilder {
	return sb.WriteString("DELETE FROM ").WriteString(table)
}

// 启动一个 SELECT 语句，并指定列名。可多次调用。
func (sb *SQLBuilder) Select(cols ...string) *SQLBuilder {
	if !sb.isSetFlag(flagColumn) {
		sb.WriteString("SELECT ")
		sb.setFlag(flagColumn)
	}

	for _, col := range cols {
		sb.WriteString(col)
		sb.WriteByte(',')
	}
	return sb.TruncateLast(1)
}

// 启动一个 INSERT 语句。
func (sb *SQLBuilder) Insert(table string) *SQLBuilder {
	return sb.WriteString("INSERT INTO ").WriteString(table)
}

// 启动一个 UPDATE 语句。
func (sb *SQLBuilder) Update(table string) *SQLBuilder {
	return sb.WriteString("UPDATE ").WriteString(table)
}

// 拼接表名字符串。当调用 Select() 之后，此方法用于指定表名。
func (sb *SQLBuilder) From(table string) *SQLBuilder {
	sb.WriteString(table)
	return sb
}

// 构建 WHERE 语句，op 只能是 AND 或是 OR
func (sb *SQLBuilder) where(op string, cond string, args ...interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagWhere) {
		sb.setFlag(flagWhere)
		op = " WHERE "
	}

	sb.WriteString(op)
	sb.WriteString(cond)
	sb.args = append(sb.args, args...)

	return sb
}

// And 的别名。
func (sb *SQLBuilder) Where(cond string, args ...interface{}) *SQLBuilder {
	return sb.And(cond, args...)
}

func (sb *SQLBuilder) And(cond string, args ...interface{}) *SQLBuilder {
	return sb.where(" AND ", cond, args...)
}

func (sb *SQLBuilder) Or(cond string, args ...interface{}) *SQLBuilder {
	return sb.where(" OR ", cond, args...)
}

func (sb *SQLBuilder) orderBy(order, col string) *SQLBuilder {
	if !sb.isSetFlag(flagOrder) {
		sb.setFlag(flagOrder)
		sb.WriteString(" ORDER BY ")
	} else {
		sb.WriteByte(',')
	}

	sb.WriteString(col)
	sb.WriteString(order)

	return sb
}

func (sb *SQLBuilder) Desc(col string) *SQLBuilder {
	return sb.orderBy(" DESC ", col)
}

func (sb *SQLBuilder) Asc(col string) *SQLBuilder {
	return sb.orderBy(" ASC ", col)
}

func (sb *SQLBuilder) Limit(limit, offset int) *SQLBuilder {
	vals, err := sb.engine.Dialect().LimitSQL(sb.buffer, limit, offset)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	args := make([]interface{}, 0, 2)
	for _, val := range vals {
		args = append(args, val)
	}

	sb.args = append(sb.args, args...)

	return sb
}

// 指定插入数据时的列名
func (sb *SQLBuilder) Keys(keys ...string) *SQLBuilder {
	sb.WriteByte('(')
	for _, key := range keys {
		sb.WriteString(key)
		sb.WriteByte(',')
	}
	sb.TruncateLast(1)
	sb.WriteByte(')')

	return sb
}

// 指定插入的数据，需要与 Keys 中的名称一一对应。
//
// NOTE: 若数据库支持多行插入，可多次调用，每次指定一行数据。
func (sb *SQLBuilder) Values(vals ...interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagValues) {
		sb.WriteString("VALUES(")
		sb.setFlag(flagValues)
	} else {
		if !sb.engine.Dialect().SupportInsertMany() {
			sb.errors = append(sb.errors, errors.New("当前数据库不支持多行插入"))
			return sql
		}
		sb.WriteString(",(")
	}

	for _, v := range vals {
		sb.WriteString("?,")
		sb.args = append(sb.args, v)
	}
	sb.TruncateLast(1)

	sb.WriteByte(')')

	return sb
}

// 指定需要更新的数据。
// 仅针对 UPDATE 语句，INSERT 请使用 Keys() 和 Values() 两个函数指定。
func (sb *SQLBuilder) Set(k string, v interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagSet) {
		sb.WriteString(" SET ")
		sb.setFlag(flagSet)
	} else {
		sb.WriteByte(',')
	}

	sb.WriteString(k)
	sb.WriteString("=?")

	sb.args = append(sb.args, v)

	return sb
}

// 拼接 SELECT 语句的 JOIN 部分。
func (sb *SQLBuilder) Join(typ, table, on string) *SQLBuilder {
	sb.WriteString(typ)
	sb.WriteString(" JOIN ")
	sb.WriteString(table)
	sb.WriteByte(' ')
	sb.WriteString(on)

	return sb
}

// 返回 SQL 语句和其对应的值。
func (sb *SQLBuilder) String() (string, []interface{}, error) {
	if sb.HasError() {
		return "", nil, ErrHasErrors
	}

	return sb.buffer.String(), sb.args, nil
}

// 返回预编译的实例及对应的值。
func (sb *SQLBuilder) Prepare() (*sql.Stmt, []interface{}, error) {
	if sb.HasError() {
		return nil, nil, ErrHasErrors
	}

	stmt, err := sb.engine.Prepare(true, sb.buffer.String())
	if err != nil {
		return nil, nil, err
	}

	return stmt, sb.args, nil
}

// 执行 SQL 语句。
func (sb *SQLBuilder) Exec(replace bool) (sql.Result, error) {
	query, vals, err := sb.String()
	if err != nil {
		return nil, err
	}

	return sb.engine.Exec(replace, query, vals...)
}

// 执行 SQL 查询语句。仅对 SELECT 启作用。
func (sb *SQLBuilder) Query(replace bool) (*sql.Rows, error) {
	query, vals, err := sb.String()
	if err != nil {
		return nil, err
	}

	return sb.engine.Query(replace, query, vals...)
}

// 返回符合当前条件的所有记录。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryMap(replace bool) ([]map[string]interface{}, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.Map(false, rows)
}

// 返回符合当前条件的所有记录，功能与 QueryMap 相同，但 map 中的值全为字符串类型。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryMapString(replace bool) ([]map[string]string, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.MapString(false, rows)
}

// 将符合当前条件的所有记录依次写入 objs 中。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryObj(replace bool, objs interface{}) (int, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	return fetch.Obj(objs, rows)
}

// 返回符合条件的记录中的某一列值。
func (sb *SQLBuilder) QueryColumn(replace bool, col string) ([]interface{}, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.Column(false, col, rows)
}

// 返回符合条件的记录中的某一列值，功能与 QueryColumn 相同，但返回的值均为字符串类型。
func (sb *SQLBuilder) QueryColumnString(replace bool, col string) ([]string, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.ColumnString(false, col, rows)
}
<commit_msg>当前不支持多行插入时，提示更加友好，语句依然构建<commit_after>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package sqlbuilder

import (
	"bytes"
	"database/sql"
	"errors"
	"fmt"

	"github.com/issue9/orm/fetch"
	"github.com/issue9/orm/forward"
)

// 一组标记位，用于标记某些可重复调用的函数，是否是第一次调用。
// 比如 OrderBy 在第一次调用时，需要填 `ORDER BY`字符串，之后
// 的只要跟着列名和相应的排序方式即可。
const (
	flagWhere  int8 = 1 << iota
	flagOrder       // ORDER BY
	flagColumn      // SELECT
	flagSet         // UPDATE 的 SET
	flagValues      // INSERT 的 VALUES
)

// 当出现此错误时，说明在构建 SQL 语句的过程中出现了错误，
// 需要调用 SQLBuilder.Errors() 获取详细的错误信息。
var ErrHasErrors = errors.New("语句中包含一个或多个错误")

// SQLBuilder 一个简单的 SQL 语句接接工具。
// NOTE: SQLBuilder 的所有函数调用，将直接拼接到字符串，
// 而不会做缓存，所以调用顺序必须与 SQL 语法相同。
//
// DELETE
//  sql := New(engine).
//      Delete("table1").
//      Where("id>?", 5).
//      And("type=?", 2)
//  query, vals, err := sql.String()
type SQLBuilder struct {
	engine forward.Engine
	buffer *bytes.Buffer
	args   []interface{}
	flag   int8
	errors []error
}

// 声明一个 SQLBuilder 实例
func New(e forward.Engine) *SQLBuilder {
	return &SQLBuilder{
		engine: e,
		buffer: new(bytes.Buffer),
		args:   make([]interface{}, 0, 10),
		flag:   0,
		errors: make([]error, 0, 5),
	}
}

// 重置所有的数据为初始值，这样可以重复利用该 SQLBuilder 对象。
func (sb *SQLBuilder) Reset() *SQLBuilder {
	sb.buffer.Reset()
	sb.args = sb.args[:0]
	sb.flag = 0
	sb.errors = sb.errors[:0]

	return sb
}

func (sb *SQLBuilder) isSetFlag(flag int8) bool {
	return sb.flag&flag > 0
}

func (sb *SQLBuilder) setFlag(flag int8) {
	sb.flag |= flag
}

// 是否在构建过程中触发错误信息。
//
// NOTE: 在构建完 SQL 语句，准备执行数据库操作之前，
// 都应该调用此函数确认是否存在错误。
func (sb *SQLBuilder) HasError() bool {
	return len(sb.errors) > 0
}

// 返回所有的错误内容。
func (sb *SQLBuilder) Errors() []error {
	return sb.errors
}

func (sb *SQLBuilder) WriteByte(c byte) *SQLBuilder {
	err := sb.buffer.WriteByte(c)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	return sb
}

func (sb *SQLBuilder) WriteString(s string) *SQLBuilder {
	_, err := sb.buffer.WriteString(s)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	return sb
}

// 去掉尾部的 n 个字符。
func (sb *SQLBuilder) TruncateLast(n int) *SQLBuilder {
	sb.buffer.Truncate(sb.buffer.Len() - n)
	return sb
}

// 启动一个 DELETE 语句。
func (sb *SQLBuilder) Delete(table string) *SQLBuilder {
	return sb.WriteString("DELETE FROM ").WriteString(table)
}

// 启动一个 SELECT 语句，并指定列名。可多次调用。
func (sb *SQLBuilder) Select(cols ...string) *SQLBuilder {
	if !sb.isSetFlag(flagColumn) {
		sb.WriteString("SELECT ")
		sb.setFlag(flagColumn)
	}

	for _, col := range cols {
		sb.WriteString(col)
		sb.WriteByte(',')
	}
	return sb.TruncateLast(1)
}

// 启动一个 INSERT 语句。
func (sb *SQLBuilder) Insert(table string) *SQLBuilder {
	return sb.WriteString("INSERT INTO ").WriteString(table)
}

// 启动一个 UPDATE 语句。
func (sb *SQLBuilder) Update(table string) *SQLBuilder {
	return sb.WriteString("UPDATE ").WriteString(table)
}

// 拼接表名字符串。当调用 Select() 之后，此方法用于指定表名。
func (sb *SQLBuilder) From(table string) *SQLBuilder {
	sb.WriteString(table)
	return sb
}

// 构建 WHERE 语句，op 只能是 AND 或是 OR
func (sb *SQLBuilder) where(op string, cond string, args ...interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagWhere) {
		sb.setFlag(flagWhere)
		op = " WHERE "
	}

	sb.WriteString(op)
	sb.WriteString(cond)
	sb.args = append(sb.args, args...)

	return sb
}

// And 的别名。
func (sb *SQLBuilder) Where(cond string, args ...interface{}) *SQLBuilder {
	return sb.And(cond, args...)
}

func (sb *SQLBuilder) And(cond string, args ...interface{}) *SQLBuilder {
	return sb.where(" AND ", cond, args...)
}

func (sb *SQLBuilder) Or(cond string, args ...interface{}) *SQLBuilder {
	return sb.where(" OR ", cond, args...)
}

func (sb *SQLBuilder) orderBy(order, col string) *SQLBuilder {
	if !sb.isSetFlag(flagOrder) {
		sb.setFlag(flagOrder)
		sb.WriteString(" ORDER BY ")
	} else {
		sb.WriteByte(',')
	}

	sb.WriteString(col)
	sb.WriteString(order)

	return sb
}

func (sb *SQLBuilder) Desc(col string) *SQLBuilder {
	return sb.orderBy(" DESC ", col)
}

func (sb *SQLBuilder) Asc(col string) *SQLBuilder {
	return sb.orderBy(" ASC ", col)
}

func (sb *SQLBuilder) Limit(limit, offset int) *SQLBuilder {
	vals, err := sb.engine.Dialect().LimitSQL(sb.buffer, limit, offset)
	if err != nil {
		sb.errors = append(sb.errors, err)
	}

	args := make([]interface{}, 0, 2)
	for _, val := range vals {
		args = append(args, val)
	}

	sb.args = append(sb.args, args...)

	return sb
}

// 指定插入数据时的列名
func (sb *SQLBuilder) Keys(keys ...string) *SQLBuilder {
	sb.WriteByte('(')
	for _, key := range keys {
		sb.WriteString(key)
		sb.WriteByte(',')
	}
	sb.TruncateLast(1)
	sb.WriteByte(')')

	return sb
}

// 指定插入的数据，需要与 Keys 中的名称一一对应。
//
// NOTE: 若数据库支持多行插入，可多次调用，每次指定一行数据。
func (sb *SQLBuilder) Values(vals ...interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagValues) {
		sb.WriteString("VALUES(")
		sb.setFlag(flagValues)
	} else {
		d := sb.engine.Dialect()
		if !d.SupportInsertMany() {
			sb.errors = append(sb.errors, fmt.Errorf("当前数据库[%v]不支持多行插入", d.Name()))
		}
		sb.WriteString(",(")
	}

	for _, v := range vals {
		sb.WriteString("?,")
		sb.args = append(sb.args, v)
	}
	sb.TruncateLast(1)

	sb.WriteByte(')')

	return sb
}

// 指定需要更新的数据。
// 仅针对 UPDATE 语句，INSERT 请使用 Keys() 和 Values() 两个函数指定。
func (sb *SQLBuilder) Set(k string, v interface{}) *SQLBuilder {
	if !sb.isSetFlag(flagSet) {
		sb.WriteString(" SET ")
		sb.setFlag(flagSet)
	} else {
		sb.WriteByte(',')
	}

	sb.WriteString(k)
	sb.WriteString("=?")

	sb.args = append(sb.args, v)

	return sb
}

// 拼接 SELECT 语句的 JOIN 部分。
func (sb *SQLBuilder) Join(typ, table, on string) *SQLBuilder {
	sb.WriteString(typ)
	sb.WriteString(" JOIN ")
	sb.WriteString(table)
	sb.WriteByte(' ')
	sb.WriteString(on)

	return sb
}

// 返回 SQL 语句和其对应的值。
func (sb *SQLBuilder) String() (string, []interface{}, error) {
	if sb.HasError() {
		return "", nil, ErrHasErrors
	}

	return sb.buffer.String(), sb.args, nil
}

// 返回预编译的实例及对应的值。
func (sb *SQLBuilder) Prepare() (*sql.Stmt, []interface{}, error) {
	if sb.HasError() {
		return nil, nil, ErrHasErrors
	}

	stmt, err := sb.engine.Prepare(true, sb.buffer.String())
	if err != nil {
		return nil, nil, err
	}

	return stmt, sb.args, nil
}

// 执行 SQL 语句。
func (sb *SQLBuilder) Exec(replace bool) (sql.Result, error) {
	query, vals, err := sb.String()
	if err != nil {
		return nil, err
	}

	return sb.engine.Exec(replace, query, vals...)
}

// 执行 SQL 查询语句。仅对 SELECT 启作用。
func (sb *SQLBuilder) Query(replace bool) (*sql.Rows, error) {
	query, vals, err := sb.String()
	if err != nil {
		return nil, err
	}

	return sb.engine.Query(replace, query, vals...)
}

// 返回符合当前条件的所有记录。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryMap(replace bool) ([]map[string]interface{}, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.Map(false, rows)
}

// 返回符合当前条件的所有记录，功能与 QueryMap 相同，但 map 中的值全为字符串类型。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryMapString(replace bool) ([]map[string]string, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.MapString(false, rows)
}

// 将符合当前条件的所有记录依次写入 objs 中。
//
// replace，是否需将对语句的占位符进行替换。
func (sb *SQLBuilder) QueryObj(replace bool, objs interface{}) (int, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	return fetch.Obj(objs, rows)
}

// 返回符合条件的记录中的某一列值。
func (sb *SQLBuilder) QueryColumn(replace bool, col string) ([]interface{}, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.Column(false, col, rows)
}

// 返回符合条件的记录中的某一列值，功能与 QueryColumn 相同，但返回的值均为字符串类型。
func (sb *SQLBuilder) QueryColumnString(replace bool, col string) ([]string, error) {
	rows, err := sb.Query(replace)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	return fetch.ColumnString(false, col, rows)
}
<|endoftext|>


<commit_before>// Package router provides a fast HTTP router.
package router

import (
	"fmt"
	"net/http"
	"strings"
)

// The Router is the main structure of this package.
type Router struct {
	trees map[string]*[]*node // trees is a map of methods with their path nodes.
}

type node struct {
	s        string
	params   []string
	children []*node
	handler  http.Handler
}

// New returns a fresh rounting unit.
func New() *Router {
	return &Router{trees: make(map[string]*[]*node)}
}

// Handle adds a route with method, path and handler.
func (rt *Router) Handle(method, path string, h http.Handler) {
	if len(path) == 0 || path[0] != '/' {
		panic(fmt.Errorf("router: path %q must begin with %q", path, "/"))
	}

	// Extract params from path
	var params []string
	var paramStart, paramEnd int
	for {
		paramStart = strings.IndexByte(path[paramEnd:], ':')
		if paramStart == -1 { // No more params: make node.
			break
		}
		paramStart += paramEnd
		paramStart++ // Position on parameter name instead of ":".
		paramEnd = strings.IndexByte(path[paramStart:], '/')
		if paramEnd == -1 { // Parameter is at the end the path.
			params = append(params, path[paramStart:len(path)])
			break
		}
		paramEnd += paramStart
		params = append(params, path[paramStart:paramEnd])
	}

	// Get (or set) tree for method
	tree := rt.trees[method]
	if tree == nil {
		n := make([]*node, 0)
		rt.trees[method] = &n
		tree = &n
	}

	makeNode(tree, path, params, h)
}

// makeNode adds a node to the tree.
func makeNode(nodes *[]*node, s string, params []string, h http.Handler) {
LoopNodes:
	for _, n := range *nodes {
		minlen := len(n.s)
		if len(s) < minlen {
			minlen = len(s)
		}
		for i := 0; i < minlen; i++ {
			if n.s[i] != s[i] {
				if i == 0 {
					continue LoopNodes // No match from the first byte: see next same-level node.
				}
				// Difference in the middle of a node: split current node to make subnode and transfer handler to it.
				*n = node{
					s: n.s[:i],
					children: []*node{
						{s: n.s[i:], params: n.params, children: n.children, handler: n.handler},
						{s: s[i:], params: params, handler: h},
					},
				}
				return
			}
		}
		if len(s) < len(n.s) { // s fully matched first part of n.s: split node.
			*n = node{
				s: n.s[:len(s)],
				children: []*node{
					{s: n.s[len(s):], params: n.params, children: n.children, handler: n.handler},
				},
				handler: h,
			}
		} else if len(s) > len(n.s) { // n.s fully matched first part of s: see subnodes for the rest.
			makeNode(&n.children, s[len(n.s):], params, h)
		} else { // s == n.s and no rest: node hasn't handler or route is duplicated.
			if n.handler == nil {
				n.handler = h
				return
			}
			panic(fmt.Errorf("router: route %q is duplicated", s))
		}
		return
	}
	*nodes = append(*nodes, &node{s: s, params: params, handler: h}) // Not a single byte match on same-level nodes: append a new one.
}

func (rt Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	trees := rt.trees[r.Method]
	if trees != nil {
		n := findNode(*trees, r.URL.Path)
		if n != nil && n.handler != nil {
			n.handler.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func findNode(nodes []*node, s string) *node {
	for _, n := range nodes {
		if strings.HasPrefix(s, n.s) {
			s = s[len(n.s):]
			if len(s) == 0 {
				return n
			}
			return findNode(n.children, s)
		}
	}
	return nil
}

func (rt *Router) String() (s string) {
	for method, nodes := range rt.trees {
		s += method + "\n"
		for _, n := range *nodes {
			s += n.string(1)
		}
	}
	return
}

func (n *node) string(level int) (s string) {
	s += fmt.Sprintf("%s%q  %v  %v\n", strings.Repeat("\t", level), n.s, n.params, n.handler)
	for _, n := range n.children {
		s += n.string(level + 1)
	}
	return
}
<commit_msg>Extract params form path and fix makeNode<commit_after>// Package router provides a fast HTTP router.
package router

import (
	"fmt"
	"net/http"
	"strings"
)

// The Router is the main structure of this package.
type Router struct {
	trees map[string]*[]*node // trees is a map of methods with their path nodes.
}

type node struct {
	s        string
	params   []string
	children []*node
	handler  http.Handler
}

// New returns a fresh rounting unit.
func New() *Router {
	return &Router{trees: make(map[string]*[]*node)}
}

// Handle adds a route with method, path and handler.
func (rt *Router) Handle(method, path string, h http.Handler) {
	if len(path) == 0 || path[0] != '/' {
		panic(fmt.Errorf("router: path %q must begin with %q", path, "/"))
	}

	// Extract params from path
	var params []string
	var paramStart, paramEnd int
	for {
		paramStart = strings.IndexByte(path[paramEnd:], ':')
		if paramStart == -1 { // No more params: make node.
			break
		}
		paramStart += paramEnd
		paramStart++ // Position on parameter name instead of ":".
		paramEnd = strings.IndexByte(path[paramStart:], '/')
		if paramEnd == -1 { // Parameter is at the end the path.
			params = append(params, path[paramStart:len(path)])
			path = path[:paramStart]
			break
		}
		paramEnd += paramStart
		params = append(params, path[paramStart:paramEnd])
		path = path[:paramStart] + path[paramEnd:]
		paramEnd -= paramEnd - paramStart
	}

	// Get (or set) tree for method
	tree := rt.trees[method]
	if tree == nil {
		n := make([]*node, 0)
		rt.trees[method] = &n
		tree = &n
	}

	makeNode(tree, path, params, h)
}

// makeNode adds a node to the tree.
func makeNode(nodes *[]*node, s string, params []string, h http.Handler) {
LoopNodes:
	for _, n := range *nodes {
		minlen := len(n.s)
		if len(s) < minlen {
			minlen = len(s)
		}
		for i := 0; i < minlen; i++ {
			if n.s[i] != s[i] {
				if i == 0 {
					continue LoopNodes // No match from the first byte: see next same-level node.
				}
				// Difference in the middle of a node: split current node to make subnode and transfer handler to it.
				*n = node{
					s: n.s[:i],
					children: []*node{
						{s: n.s[i:], params: n.params, children: n.children, handler: n.handler},
						{s: s[i:], params: params, handler: h},
					},
				}
				return
			}
		}
		if len(s) < len(n.s) { // s fully matched first part of n.s: split node.
			*n = node{
				s:      n.s[:len(s)],
				params: params,
				children: []*node{
					{s: n.s[len(s):], params: n.params, children: n.children, handler: n.handler},
				},
				handler: h,
			}
		} else if len(s) > len(n.s) { // n.s fully matched first part of s: see subnodes for the rest.
			makeNode(&n.children, s[len(n.s):], params, h)
		} else { // s == n.s and no rest: node has no handler or route is duplicated.
			if n.handler == nil {
				n.params = params
				n.handler = h
				return
			}
			panic(fmt.Errorf("router: route %q is duplicated", s))
		}
		return
	}
	*nodes = append(*nodes, &node{s: s, params: params, handler: h}) // Not a single byte match on same-level nodes: append a new one.
}

func (rt Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	trees := rt.trees[r.Method]
	if trees != nil {
		n := findNode(*trees, r.URL.Path)
		if n != nil && n.handler != nil {
			n.handler.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func findNode(nodes []*node, s string) *node {
	for _, n := range nodes {
		if strings.HasPrefix(s, n.s) {
			s = s[len(n.s):]
			if len(s) == 0 {
				return n
			}
			return findNode(n.children, s)
		}
	}
	return nil
}

func (rt *Router) String() (s string) {
	for method, nodes := range rt.trees {
		s += method + "\n"
		for _, n := range *nodes {
			s += n.string(1)
		}
	}
	return
}

func (n *node) string(level int) (s string) {
	s += fmt.Sprintf("%s%q  %v  %v\n", strings.Repeat("\t", level), n.s, n.params, n.handler)
	for _, n := range n.children {
		s += n.string(level + 1)
	}
	return
}
<|endoftext|>


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

// Package httprouter is a trie based high performance HTTP request router
//
// A trivial example server is:
//
//  package main
//
//  import (
//      "github.com/julienschmidt/httprouter"
//      "io"
//      "net/http"
//      "log"
//  )
//
//  func Hello(w http.ResponseWriter, req *http.Request, vars map[string]string) {
//      io.WriteString(w, "hello, " + vars["name"] + "!\n")
//  }
//
//  func main() {
//      router := httprouter.New()
//      router.GET("/hello/:name", Hello)
//
//      log.Fatal(http.ListenAndServe(":12345", router))
//  }
//
//
package httprouter

import (
	"net/http"
)

// Handle is a function that can be registered to a route to handle HTTP
// requests. Like http.HandlerFunc, but has a third parameter for the route
// parameters.
type Handle func(http.ResponseWriter, *http.Request, map[string]string)

// NotFound is the default HTTP handler func for routes that can't be matched
// with an existing route.
// NotFound tries to redirect to a canonical URL generated with CleanPath.
// Otherwise the request is delegated to http.NotFound.
func NotFound(w http.ResponseWriter, req *http.Request) {
	if req.Method != "CONNECT" {
		path := req.URL.Path
		if cp := CleanPath(path); cp != path && cp != req.Referer() {
			http.Redirect(w, req, cp, http.StatusMovedPermanently)
			return
		}
	}

	http.NotFound(w, req)
}

// Router is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
	node

	// Enables automatic redirection if the current route can't be matched but
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301.
	RedirectTrailingSlash bool

	// Configurable handler func which is used when no matching route is found.
	// Default is the NotFound func of this package.
	NotFound http.HandlerFunc

	// Handler func to handle panics recovered from http handlers.
	// It should be used to generate a error page and return the http error code
	// "500 - Internal Server Error".
	// The handler can be used to keep your server from crashing because of
	// unrecovered panics.
	PanicHandler func(http.ResponseWriter, *http.Request, interface{})
}

// Make sure the Router conforms with the http.Handler interface
var _ http.Handler = New()

// New returnes a new initialized Router.
// The router can be configured to also match the requested HTTP method or the
// requested Host.
func New() *Router {
	return &Router{
		RedirectTrailingSlash: true,
		NotFound:              NotFound,
	}
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (r *Router) GET(path string, handle Handle) {
	r.Handle("GET", path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (r *Router) POST(path string, handle Handle) {
	r.Handle("POST", path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (r *Router) PUT(path string, handle Handle) {
	r.Handle("PUT", path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, handle Handle) {
	r.Handle("DELETE", path, handle)
}

// Handle registers a new request handle with the given path and method.
func (r *Router) Handle(method, path string, handle Handle) {
	if path[0] != '/' {
		panic("path must begin with '/'")
	}
	r.addRoute(method, path, handle)
}

// HandlerFunc is an adapter which allows the usage of a http.HandlerFunc as a
// request handle.
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	r.Handle(method, path,
		func(w http.ResponseWriter, req *http.Request, _ map[string]string) {
			handler(w, req)
		},
	)
}

func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
	if rcv := recover(); rcv != nil {
		r.PanicHandler(w, req, rcv)
	}
}

// Make the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if r.PanicHandler != nil {
		defer r.recv(w, req)
	}

	path := req.URL.Path

	if handle, vars, tsr := r.getValue(req.Method, path); handle != nil {
		handle(w, req, vars)
	} else if tsr && r.RedirectTrailingSlash && path != "/" {
		if path[len(path)-1] == '/' {
			path = path[:len(path)-1]
		} else {
			path = path + "/"
		}
		http.Redirect(w, req, path, http.StatusMovedPermanently)
		return
	} else { // Handle 404
		if r.NotFound != nil {
			r.NotFound(w, req)
		} else {
			http.NotFound(w, req)
		}
	}
}
<commit_msg>Add a function to serve files<commit_after>// Copyright 2013 Julien Schmidt. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found
// in the LICENSE file.

// Package httprouter is a trie based high performance HTTP request router
//
// A trivial example server is:
//
//  package main
//
//  import (
//      "github.com/julienschmidt/httprouter"
//      "io"
//      "net/http"
//      "log"
//  )
//
//  func Hello(w http.ResponseWriter, req *http.Request, vars map[string]string) {
//      io.WriteString(w, "hello, " + vars["name"] + "!\n")
//  }
//
//  func main() {
//      router := httprouter.New()
//      router.GET("/hello/:name", Hello)
//
//      log.Fatal(http.ListenAndServe(":12345", router))
//  }
//
//
package httprouter

import (
	"net/http"
)

// Handle is a function that can be registered to a route to handle HTTP
// requests. Like http.HandlerFunc, but has a third parameter for the route
// parameters.
type Handle func(http.ResponseWriter, *http.Request, map[string]string)

// NotFound is the default HTTP handler func for routes that can't be matched
// with an existing route.
// NotFound tries to redirect to a canonical URL generated with CleanPath.
// Otherwise the request is delegated to http.NotFound.
func NotFound(w http.ResponseWriter, req *http.Request) {
	if req.Method != "CONNECT" {
		path := req.URL.Path
		if cp := CleanPath(path); cp != path && cp != req.Referer() {
			http.Redirect(w, req, cp, http.StatusMovedPermanently)
			return
		}
	}

	http.NotFound(w, req)
}

// Router is a http.Handler which can be used to dispatch requests to different
// handler functions via configurable routes
type Router struct {
	node

	// Enables automatic redirection if the current route can't be matched but
	// handler for the path with (without) the trailing slash exists.
	// For example if /foo/ is requested but a route only exists for /foo, the
	// client is redirected to /foo with http status code 301.
	RedirectTrailingSlash bool

	// Configurable handler func which is used when no matching route is found.
	// Default is the NotFound func of this package.
	NotFound http.HandlerFunc

	// Handler func to handle panics recovered from http handlers.
	// It should be used to generate a error page and return the http error code
	// "500 - Internal Server Error".
	// The handler can be used to keep your server from crashing because of
	// unrecovered panics.
	PanicHandler func(http.ResponseWriter, *http.Request, interface{})
}

// Make sure the Router conforms with the http.Handler interface
var _ http.Handler = New()

// New returnes a new initialized Router.
// The router can be configured to also match the requested HTTP method or the
// requested Host.
func New() *Router {
	return &Router{
		RedirectTrailingSlash: true,
		NotFound:              NotFound,
	}
}

// GET is a shortcut for router.Handle("GET", path, handle)
func (r *Router) GET(path string, handle Handle) {
	r.Handle("GET", path, handle)
}

// POST is a shortcut for router.Handle("POST", path, handle)
func (r *Router) POST(path string, handle Handle) {
	r.Handle("POST", path, handle)
}

// PUT is a shortcut for router.Handle("PUT", path, handle)
func (r *Router) PUT(path string, handle Handle) {
	r.Handle("PUT", path, handle)
}

// DELETE is a shortcut for router.Handle("DELETE", path, handle)
func (r *Router) DELETE(path string, handle Handle) {
	r.Handle("DELETE", path, handle)
}

// Handle registers a new request handle with the given path and method.
func (r *Router) Handle(method, path string, handle Handle) {
	if path[0] != '/' {
		panic("path must begin with '/'")
	}
	r.addRoute(method, path, handle)
}

// HandlerFunc is an adapter which allows the usage of a http.HandlerFunc as a
// request handle.
func (r *Router) HandlerFunc(method, path string, handler http.HandlerFunc) {
	r.Handle(method, path,
		func(w http.ResponseWriter, req *http.Request, _ map[string]string) {
			handler(w, req)
		},
	)
}

// ServeFiles ... from root. The path must contain "*filepath", files are then
// served from the local path /defined/root/dir/*filepath.
// For example if root is "/etc" and *filepath is "passwd", the local file
// "/etc/passwd" would be served.
// Internally a http.FileServer is used, therefore http.NotFound is used instead
// of the Router's NotFound handler.
func (r *Router) ServeFiles(path string, root http.FileSystem) {
	if len(path) < 10 || path[len(path)-9:] != "*filepath" {
		panic("path must end with *filepath")
	}

	fileServer := http.FileServer(root)

	r.GET(path, func(w http.ResponseWriter, req *http.Request, vars map[string]string) {
		fp, ok := vars["filepath"]
		if !ok {
			panic("routed request has no *filepath")
		}

		println(fp)

		req.URL.Path = fp
		fileServer.ServeHTTP(w, req)
	})
}

func (r *Router) recv(w http.ResponseWriter, req *http.Request) {
	if rcv := recover(); rcv != nil {
		r.PanicHandler(w, req, rcv)
	}
}

// Make the router implement the http.Handler interface.
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if r.PanicHandler != nil {
		defer r.recv(w, req)
	}

	path := req.URL.Path

	if handle, vars, tsr := r.getValue(req.Method, path); handle != nil {
		handle(w, req, vars)
	} else if tsr && r.RedirectTrailingSlash && path != "/" {
		if path[len(path)-1] == '/' {
			path = path[:len(path)-1]
		} else {
			path = path + "/"
		}
		http.Redirect(w, req, path, http.StatusMovedPermanently)
		return
	} else { // Handle 404
		if r.NotFound != nil {
			r.NotFound(w, req)
		} else {
			http.NotFound(w, req)
		}
	}
}
<|endoftext|>


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

package fc

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/opensds/opensds/contrib/connector"
)

func getSCSIWWN(devicePath string) (string, error) {
	out, err := connector.ExecCmd("/lib/udev/scsi_id", "--page", "0x83", "--whitelisted", devicePath)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device wwn: %s, %v\n", out, err)
		log.Printf(errMsg)
		return "", errors.New(errMsg)
	}
	return strings.TrimSpace(out), nil
}

func getContentfromSymboliclink(symboliclink string) string {
	out, _ := connector.ExecCmd("readlink", "-f", symboliclink)
	return strings.TrimSuffix(out, "\n")
}

func rescanHosts(tgtWWN []string, hbas []map[string]string) error {
	for _, hba := range hbas {
		cmd := fmt.Sprintf("echo \"- - -\" > /sys/class/scsi_host/%s/scan", hba["host_device"])
		out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
		if err != nil {
			errMsg := fmt.Sprintf("Error occurred when rescan hosts: %s, %v\n", out, err)
			log.Printf(errMsg)
			return errors.New(errMsg)
		}
	}
	return nil
}

func getFChbas() ([]map[string]string, error) {
	if !fcSupport() {
		errMsg := fmt.Sprintf("No Fibre Channel support detected.\n")
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	out, err := connector.ExecCmd("systool", "-c", "fc_host", "-v")
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get FC hbas info: systool is not installed: %s, %v\n", out, err)
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	if out == "" {
		errMsg := fmt.Sprintf("No Fibre Channel support detected.\n")
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	lines := strings.Split(out, "\n")
	lines = lines[2:]
	hba := make(map[string]string)
	hbas := []map[string]string{}
	lastline := ""

	for _, line := range lines {
		line = strings.TrimSpace(line)
		// 2 newlines denotes a new hba port
		if line == "" && lastline == "" {
			if len(hba) > 0 {
				hbas = append(hbas, hba)
				hba = make(map[string]string)
			}
		} else {
			val := strings.Split(line, "=")
			if len(val) == 2 {
				key := strings.Replace(val[0], " ", "", -1)
				key = trimDoubleQuotesInText(key)

				val := strings.Replace(val[1], " ", "", -1)
				val = trimDoubleQuotesInText(val)

				hba[key] = val
			}
		}
		lastline = line
	}
	return hbas, nil
}

func removeSCSIDevice(path string) error {
	cmd := "echo 1 >" + path
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when remove scsi device: %s, %v\n", out, err)
		log.Printf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

func flushDeviceIO(device string) error {
	cmd := "blockdev --flushbufs " + device
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device info when detach volume: %s, %v\n", out, err)
		log.Printf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

func getDeviceInfo(devicePath string) (map[string]string, error) {
	cmd := "sg_scan " + devicePath
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device info: %s, %v\n", out, err)
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	devInfo := make(map[string]string)
	devInfo["device"] = devicePath

	line := strings.TrimSpace(out)

	info := strings.Split(line, " ")

	for _, v := range info {
		if strings.Contains(v, "=") {
			pair := strings.Split(v, "=")
			devInfo[pair[0]] = pair[1]
		}
		if strings.Contains(v, "scsi") {
			devInfo["host"] = strings.Replace(v, "scsi", "", -1)
		}
	}

	return devInfo, nil
}

func fcSupport() bool {
	var FcHostSYSFcPATH = "/sys/class/fc_host"
	return pathExists(FcHostSYSFcPATH)
}

func pathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func trimDoubleQuotesInText(str string) string {
	if strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\"") {
		return str[1 : len(str)-1]
	}
	return str
}

func getMultipathDevice(deviceWWN string) (string, error) {
	cmd := fmt.Sprintf("ls -l /dev/disk/by-id/ | grep %s", deviceWWN)
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("error occurred when find DM of wwn %s: %s, %v", deviceWWN, out, err)
		log.Println(errMsg)
		return "", errors.New(errMsg)
	}

	lines := strings.Split(strings.TrimSpace(out), "\n")
	for _, line := range lines {
		splits := strings.Split(line, "../../")
		if len(splits) == 2 {
			name := splits[1]
			if strings.HasPrefix(name, "dm") {
				return fmt.Sprintf("/dev/%s", name), nil
			}
		}
	}

	msg := fmt.Sprintf("No DM of wwn %s exist", deviceWWN)
	log.Println(msg)
	return "", nil
}
<commit_msg>Support multipath device (#14)<commit_after>// Copyright 2019 The OpenSDS Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package fc

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strings"

	"github.com/opensds/opensds/contrib/connector"
)

func getSCSIWWN(devicePath string) (string, error) {
	out, err := connector.ExecCmd("/lib/udev/scsi_id", "--page", "0x83", "--whitelisted", devicePath)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device wwn: %s, %v\n", out, err)
		log.Printf(errMsg)
		return "", errors.New(errMsg)
	}
	return strings.TrimSpace(out), nil
}

func getContentfromSymboliclink(symboliclink string) string {
	out, _ := connector.ExecCmd("readlink", "-f", symboliclink)
	return strings.TrimSuffix(out, "\n")
}

func rescanHosts(tgtWWN []string, hbas []map[string]string) error {
	for _, hba := range hbas {
		cmd := fmt.Sprintf("echo \"- - -\" > /sys/class/scsi_host/%s/scan", hba["host_device"])
		out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
		if err != nil {
			errMsg := fmt.Sprintf("Error occurred when rescan hosts: %s, %v\n", out, err)
			log.Printf(errMsg)
			return errors.New(errMsg)
		}
	}
	return nil
}

func getFChbas() ([]map[string]string, error) {
	if !fcSupport() {
		errMsg := fmt.Sprintf("No Fibre Channel support detected.\n")
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	out, err := connector.ExecCmd("systool", "-c", "fc_host", "-v")
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get FC hbas info: systool is not installed: %s, %v\n", out, err)
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	if out == "" {
		errMsg := fmt.Sprintf("No Fibre Channel support detected.\n")
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	lines := strings.Split(out, "\n")
	lines = lines[2:]
	hba := make(map[string]string)
	hbas := []map[string]string{}
	lastline := ""

	for _, line := range lines {
		line = strings.TrimSpace(line)
		// 2 newlines denotes a new hba port
		if line == "" && lastline == "" {
			if len(hba) > 0 {
				hbas = append(hbas, hba)
				hba = make(map[string]string)
			}
		} else {
			val := strings.Split(line, "=")
			if len(val) == 2 {
				key := strings.Replace(val[0], " ", "", -1)
				key = trimDoubleQuotesInText(key)

				val := strings.Replace(val[1], " ", "", -1)
				val = trimDoubleQuotesInText(val)

				hba[key] = val
			}
		}
		lastline = line
	}
	return hbas, nil
}

func removeSCSIDevice(path string) error {
	cmd := "echo 1 >" + path
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when remove scsi device: %s, %v\n", out, err)
		log.Printf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

func flushDeviceIO(device string) error {
	cmd := "blockdev --flushbufs " + device
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device info when detach volume: %s, %v\n", out, err)
		log.Printf(errMsg)
		return errors.New(errMsg)
	}
	return nil
}

func getDeviceInfo(devicePath string) (map[string]string, error) {
	cmd := "sg_scan " + devicePath
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		errMsg := fmt.Sprintf("Error occurred when get device info: %s, %v\n", out, err)
		log.Printf(errMsg)
		return nil, errors.New(errMsg)
	}

	devInfo := make(map[string]string)
	devInfo["device"] = devicePath

	line := strings.TrimSpace(out)

	info := strings.Split(line, " ")

	for _, v := range info {
		if strings.Contains(v, "=") {
			pair := strings.Split(v, "=")
			devInfo[pair[0]] = pair[1]
		}
		if strings.Contains(v, "scsi") {
			devInfo["host"] = strings.Replace(v, "scsi", "", -1)
		}
	}

	return devInfo, nil
}

func fcSupport() bool {
	var FcHostSYSFcPATH = "/sys/class/fc_host"
	return pathExists(FcHostSYSFcPATH)
}

func pathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}
	if os.IsNotExist(err) {
		return false
	}
	return false
}

func trimDoubleQuotesInText(str string) string {
	if strings.HasPrefix(str, "\"") && strings.HasSuffix(str, "\"") {
		return str[1 : len(str)-1]
	}
	return str
}

func getMultipathDevice(deviceWWN string) (string, error) {
	cmd := fmt.Sprintf("ls -l /dev/disk/by-id/ | grep %s", deviceWWN)
	out, err := connector.ExecCmd("/bin/bash", "-c", cmd)
	if err != nil {
		msg := fmt.Sprintf("No DM of wwn %s exist", deviceWWN)
		log.Println(msg)
		return "", nil
	}

	lines := strings.Split(strings.TrimSpace(out), "\n")
	for _, line := range lines {
		splits := strings.Split(line, "../../")
		if len(splits) == 2 {
			name := splits[1]
			if strings.HasPrefix(name, "dm") {
				return fmt.Sprintf("/dev/%s", name), nil
			}
		}
	}

	msg := fmt.Sprintf("No DM of wwn %s exist", deviceWWN)
	log.Println(msg)
	return "", nil
}
<|endoftext|>


<commit_before>package gobrake

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"

	tdigest "github.com/caio/go-tdigest"
)

const flushPeriod = 15 * time.Second

type routeKey struct {
	Method     string    `json:"method"`
	Route      string    `json:"route"`
	StatusCode int       `json:"statusCode"`
	Time       time.Time `json:"time"`
}

type routeStat struct {
	mu      sync.Mutex
	Count   int     `json:"count"`
	Sum     float64 `json:"sum"`
	Sumsq   float64 `json:"sumsq"`
	TDigest []byte  `json:"tdigest"`
	td      *tdigest.TDigest
}

func newRouteStat() *routeStat {
	return new(routeStat)
}

func (s *routeStat) Add(ms float64) error {
	if s.td == nil {
		td, err := tdigest.New(tdigest.Compression(20))
		if err != nil {
			return err
		}
		s.td = td
	}

	s.Count++
	s.Sum += ms
	s.Sumsq += ms * ms
	return s.td.Add(ms)
}

func (s *routeStat) Pack() error {
	err := s.td.Compress()
	if err != nil {
		return err
	}

	b, err := s.td.AsBytes()
	if err != nil {
		return err
	}
	s.TDigest = b

	return nil
}

type routeKeyStat struct {
	routeKey
	*routeStat
}

type routeFilter func(*RouteTrace) *RouteTrace

// routeStats aggregates information about requests and periodically sends
// collected data to Airbrake.
type routeStats struct {
	opt    *NotifierOptions
	apiURL string

	flushTimer *time.Timer
	addWG      *sync.WaitGroup

	mu sync.Mutex
	m  map[routeKey]*routeStat
}

func newRouteStats(opt *NotifierOptions) *routeStats {
	return &routeStats{
		opt: opt,
		apiURL: fmt.Sprintf("%s/api/v5/projects/%d/routes-stats",
			opt.Host, opt.ProjectId),
	}
}

func (s *routeStats) init() {
	if s.flushTimer == nil {
		s.flushTimer = time.AfterFunc(flushPeriod, s.Flush)
		s.addWG = new(sync.WaitGroup)
		s.m = make(map[routeKey]*routeStat)
	}
}

// Flush sends to Airbrake route stats.
func (s *routeStats) Flush() {
	s.mu.Lock()

	s.flushTimer = nil
	addWG := s.addWG
	s.addWG = nil
	m := s.m
	s.m = nil

	s.mu.Unlock()

	if m == nil {
		return
	}

	addWG.Wait()
	err := s.send(m)
	if err != nil {
		logger.Printf("routeStats.send failed: %s", err)
	}
}

type routesOut struct {
	Env    string         `json:"environment"`
	Routes []routeKeyStat `json:"routes"`
}

func (s *routeStats) send(m map[routeKey]*routeStat) error {
	var routes []routeKeyStat
	for k, v := range m {
		err := v.Pack()
		if err != nil {
			return err
		}

		routes = append(routes, routeKeyStat{
			routeKey:  k,
			routeStat: v,
		})
	}

	buf := buffers.Get().(*bytes.Buffer)
	defer buffers.Put(buf)
	buf.Reset()

	out := routesOut{
		Env:    s.opt.Environment,
		Routes: routes,
	}
	err := json.NewEncoder(buf).Encode(out)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", s.apiURL, buf)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", "Bearer "+s.opt.ProjectKey)
	req.Header.Set("Content-Type", "application/json")
	resp, err := s.opt.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	switch resp.StatusCode {
	case http.StatusUnauthorized:
		return errUnauthorized
	}

	err = fmt.Errorf("got unexpected response status=%q", resp.Status)
	return err
}

// Notify adds new route stats.
func (s *routeStats) Notify(c context.Context, req *RouteTrace) error {
	key := routeKey{
		Method:     req.Method,
		Route:      req.Route,
		StatusCode: req.StatusCode,
		Time:       req.Start.UTC().Truncate(time.Minute),
	}

	s.mu.Lock()
	s.init()
	stat, ok := s.m[key]
	if !ok {
		stat = &routeStat{}
		s.m[key] = stat
	}
	addWG := s.addWG
	addWG.Add(1)
	s.mu.Unlock()

	ms := durInMs(req.End.Sub(req.Start))

	stat.mu.Lock()
	err := stat.Add(ms)
	addWG.Done()
	stat.mu.Unlock()

	return err
}
<commit_msg>Use tdigest.LocalRandomNumberGenerator<commit_after>package gobrake

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"sync"
	"time"

	tdigest "github.com/caio/go-tdigest"
)

const flushPeriod = 15 * time.Second

type routeKey struct {
	Method     string    `json:"method"`
	Route      string    `json:"route"`
	StatusCode int       `json:"statusCode"`
	Time       time.Time `json:"time"`
}

type routeStat struct {
	mu      sync.Mutex
	Count   int     `json:"count"`
	Sum     float64 `json:"sum"`
	Sumsq   float64 `json:"sumsq"`
	TDigest []byte  `json:"tdigest"`
	td      *tdigest.TDigest
}

func newRouteStat() *routeStat {
	return new(routeStat)
}

func (s *routeStat) Add(ms float64) error {
	if s.td == nil {
		seed := time.Now().UnixNano()
		td, err := tdigest.New(
			tdigest.Compression(20), tdigest.LocalRandomNumberGenerator(seed))
		if err != nil {
			return err
		}
		s.td = td
	}

	s.Count++
	s.Sum += ms
	s.Sumsq += ms * ms
	return s.td.Add(ms)
}

func (s *routeStat) Pack() error {
	err := s.td.Compress()
	if err != nil {
		return err
	}

	b, err := s.td.AsBytes()
	if err != nil {
		return err
	}
	s.TDigest = b

	return nil
}

type routeKeyStat struct {
	routeKey
	*routeStat
}

type routeFilter func(*RouteTrace) *RouteTrace

// routeStats aggregates information about requests and periodically sends
// collected data to Airbrake.
type routeStats struct {
	opt    *NotifierOptions
	apiURL string

	flushTimer *time.Timer
	addWG      *sync.WaitGroup

	mu sync.Mutex
	m  map[routeKey]*routeStat
}

func newRouteStats(opt *NotifierOptions) *routeStats {
	return &routeStats{
		opt: opt,
		apiURL: fmt.Sprintf("%s/api/v5/projects/%d/routes-stats",
			opt.Host, opt.ProjectId),
	}
}

func (s *routeStats) init() {
	if s.flushTimer == nil {
		s.flushTimer = time.AfterFunc(flushPeriod, s.Flush)
		s.addWG = new(sync.WaitGroup)
		s.m = make(map[routeKey]*routeStat)
	}
}

// Flush sends to Airbrake route stats.
func (s *routeStats) Flush() {
	s.mu.Lock()

	s.flushTimer = nil
	addWG := s.addWG
	s.addWG = nil
	m := s.m
	s.m = nil

	s.mu.Unlock()

	if m == nil {
		return
	}

	addWG.Wait()
	err := s.send(m)
	if err != nil {
		logger.Printf("routeStats.send failed: %s", err)
	}
}

type routesOut struct {
	Env    string         `json:"environment"`
	Routes []routeKeyStat `json:"routes"`
}

func (s *routeStats) send(m map[routeKey]*routeStat) error {
	var routes []routeKeyStat
	for k, v := range m {
		err := v.Pack()
		if err != nil {
			return err
		}

		routes = append(routes, routeKeyStat{
			routeKey:  k,
			routeStat: v,
		})
	}

	buf := buffers.Get().(*bytes.Buffer)
	defer buffers.Put(buf)
	buf.Reset()

	out := routesOut{
		Env:    s.opt.Environment,
		Routes: routes,
	}
	err := json.NewEncoder(buf).Encode(out)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", s.apiURL, buf)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", "Bearer "+s.opt.ProjectKey)
	req.Header.Set("Content-Type", "application/json")
	resp, err := s.opt.HTTPClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	buf.Reset()
	_, err = buf.ReadFrom(resp.Body)
	if err != nil {
		return err
	}

	if resp.StatusCode >= 200 && resp.StatusCode < 300 {
		return nil
	}

	switch resp.StatusCode {
	case http.StatusUnauthorized:
		return errUnauthorized
	}

	err = fmt.Errorf("got unexpected response status=%q", resp.Status)
	return err
}

// Notify adds new route stats.
func (s *routeStats) Notify(c context.Context, req *RouteTrace) error {
	key := routeKey{
		Method:     req.Method,
		Route:      req.Route,
		StatusCode: req.StatusCode,
		Time:       req.Start.UTC().Truncate(time.Minute),
	}

	s.mu.Lock()
	s.init()
	stat, ok := s.m[key]
	if !ok {
		stat = &routeStat{}
		s.m[key] = stat
	}
	addWG := s.addWG
	addWG.Add(1)
	s.mu.Unlock()

	ms := durInMs(req.End.Sub(req.Start))

	stat.mu.Lock()
	err := stat.Add(ms)
	addWG.Done()
	stat.mu.Unlock()

	return err
}
<|endoftext|>


<commit_before>package badgerstore

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"

	"github.com/PaluMacil/dwn/database"

	"github.com/dgraph-io/badger"
)

type BadgerStore struct {
	bgr *badger.DB
}

func retry(originalOpts badger.Options) (*BadgerStore, error) {
	lockPath := filepath.Join(originalOpts.Dir, "LOCK")
	if err := os.Remove(lockPath); err != nil {
		return nil, fmt.Errorf(`removing "LOCK": %s`, err)
	}
	retryOpts := originalOpts
	retryOpts.Truncate = true
	bgr, err := badger.Open(retryOpts)
	return &BadgerStore{bgr: bgr}, err
}

func New(dir string) (*BadgerStore, error) {
	bgr, err := badger.Open(opts(dir))
	if err != nil {
		if strings.Contains(err.Error(), "LOCK") {
			log.Println("database locked, probably due to improper shutdown")
			if bgr, err := retry(opts(dir)); err == nil {
				log.Println("database unlocked, value log truncated")
				return bgr, nil
			}
			log.Println("could not unlock database:", err)

		}
		return nil, err
	}
	bs := &BadgerStore{bgr: bgr}
	return bs, nil
}

func (bs BadgerStore) Close() error {
	return bs.bgr.Close()
}

func (bs *BadgerStore) Get(obj database.Item) (database.Item, error) {
	var rawBytes []byte
	err := bs.bgr.View(func(txn *badger.Txn) error {
		item, err := txn.Get(obj.Key())
		if err != nil {
			return err
		}
		value, err := item.Value()
		if err != nil {
			return err
		}
		rawBytes = make([]byte, len(value))
		copy(rawBytes, value)
		return nil
	})
	if err != nil {
		return obj, fmt.Errorf(`getting "%s" (%T): %s`, string(obj.Key()), obj, err)
	}
	var buf bytes.Buffer
	_, err = buf.Write(rawBytes)
	if err != nil {
		return obj, err
	}
	dec := gob.NewDecoder(&buf)
	err = dec.Decode(&obj)
	if err != nil {
		return obj, err
	}
	return obj, nil
}

func (bs *BadgerStore) Set(obj database.Item) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(&obj)
	if err != nil {
		return err
	}
	return bs.bgr.Update(func(txn *badger.Txn) error {
		err := txn.Set(obj.Key(), buf.Bytes())
		if err != nil {
			return fmt.Errorf(`setting "%s" (%T): %s`, string(obj.Key()), obj, err)
		}
		return nil
	})
}

func (bs *BadgerStore) Delete(obj database.Item) error {
	return bs.bgr.Update(func(txn *badger.Txn) error {
		return txn.Delete(obj.Key())
	})
}

func (bs *BadgerStore) All(pfx []byte, out *[]database.Item, preload bool) error {
	err := bs.bgr.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(pfx); it.ValidForPrefix(pfx); it.Next() {
			item := it.Item()
			v, err := item.Value()
			if err != nil {
				return err
			}
			var buf bytes.Buffer
			var outItem database.Item
			_, err = buf.Write(v)
			if err != nil {
				return err
			}
			dec := gob.NewDecoder(&buf)
			err = dec.Decode(&outItem)
			if err != nil {
				return err
			}
			*out = append(*out, outItem)
		}
		return nil
	})
	return err
}

func (bs *BadgerStore) Count(pfx []byte) (int, error) {
	var items []database.Item
	err := bs.All(pfx, &items, false)
	return len(items), err
}
<commit_msg>run periodic badger garbage collection<commit_after>package badgerstore

import (
	"bytes"
	"encoding/gob"
	"fmt"
	"github.com/PaluMacil/dwn/database"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/dgraph-io/badger"
)

type BadgerStore struct {
	bgr      *badger.DB
	gcTicker *time.Ticker
}

func retry(originalOpts badger.Options) (*BadgerStore, error) {
	lockPath := filepath.Join(originalOpts.Dir, "LOCK")
	if err := os.Remove(lockPath); err != nil {
		return nil, fmt.Errorf(`removing "LOCK": %s`, err)
	}
	retryOpts := originalOpts
	retryOpts.Truncate = true
	bgr, err := badger.Open(retryOpts)
	return &BadgerStore{bgr: bgr}, err
}

func New(dir string) (*BadgerStore, error) {
	bgr, err := badger.Open(opts(dir))
	if err != nil {
		if strings.Contains(err.Error(), "LOCK") {
			log.Println("database locked, probably due to improper shutdown")
			if bgr, err := retry(opts(dir)); err == nil {
				log.Println("database unlocked, value log truncated")
				return bgr, nil
			}
			log.Println("could not unlock database:", err)

		}
		return nil, err
	}

	gcTicker := time.NewTicker(5 * time.Minute)
	bs := &BadgerStore{
		bgr:      bgr,
		gcTicker: gcTicker,
	}
	go func() {
		for range gcTicker.C {
			bs.runGC()
		}
	}()
	return bs, nil
}

func (bs *BadgerStore) runGC() {
	log.Println("Running GC...")
	var logFiles int
again:
	err := bs.bgr.RunValueLogGC(0.7)
	if err == nil {
		logFiles++
		goto again
	}
	log.Println(logFiles, "log files removed during GC.")
}

func (bs BadgerStore) Close() error {
	bs.gcTicker.Stop()
	bs.runGC()
	return bs.bgr.Close()
}

func (bs *BadgerStore) Get(obj database.Item) (database.Item, error) {
	var rawBytes []byte
	err := bs.bgr.View(func(txn *badger.Txn) error {
		item, err := txn.Get(obj.Key())
		if err != nil {
			return err
		}
		value, err := item.Value()
		if err != nil {
			return err
		}
		rawBytes = make([]byte, len(value))
		copy(rawBytes, value)
		return nil
	})
	if err != nil {
		return obj, fmt.Errorf(`getting "%s" (%T): %s`, string(obj.Key()), obj, err)
	}
	var buf bytes.Buffer
	_, err = buf.Write(rawBytes)
	if err != nil {
		return obj, err
	}
	dec := gob.NewDecoder(&buf)
	err = dec.Decode(&obj)
	if err != nil {
		return obj, err
	}
	return obj, nil
}

func (bs *BadgerStore) Set(obj database.Item) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	err := enc.Encode(&obj)
	if err != nil {
		return err
	}
	return bs.bgr.Update(func(txn *badger.Txn) error {
		err := txn.Set(obj.Key(), buf.Bytes())
		if err != nil {
			return fmt.Errorf(`setting "%s" (%T): %s`, string(obj.Key()), obj, err)
		}
		return nil
	})
}

func (bs *BadgerStore) Delete(obj database.Item) error {
	return bs.bgr.Update(func(txn *badger.Txn) error {
		return txn.Delete(obj.Key())
	})
}

func (bs *BadgerStore) All(pfx []byte, out *[]database.Item, preload bool) error {
	err := bs.bgr.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(pfx); it.ValidForPrefix(pfx); it.Next() {
			item := it.Item()
			v, err := item.Value()
			if err != nil {
				return err
			}
			var buf bytes.Buffer
			var outItem database.Item
			_, err = buf.Write(v)
			if err != nil {
				return err
			}
			dec := gob.NewDecoder(&buf)
			err = dec.Decode(&outItem)
			if err != nil {
				return err
			}
			*out = append(*out, outItem)
		}
		return nil
	})
	return err
}

func (bs *BadgerStore) Count(pfx []byte) (int, error) {
	var items []database.Item
	err := bs.All(pfx, &items, false)
	return len(items), err
}
<|endoftext|>


<commit_before>package atc

import "github.com/tedsuo/rata"

const (
	SaveConfig = "SaveConfig"
	GetConfig  = "GetConfig"

	GetBuild            = "GetBuild"
	GetBuildPlan        = "GetBuildPlan"
	CreateBuild         = "CreateBuild"
	ListBuilds          = "ListBuilds"
	BuildEvents         = "BuildEvents"
	BuildResources      = "BuildResources"
	AbortBuild          = "AbortBuild"
	GetBuildPreparation = "GetBuildPreparation"

	GetJob         = "GetJob"
	CreateJobBuild = "CreateJobBuild"
	ListAllJobs    = "ListAllJobs"
	ListJobs       = "ListJobs"
	ListJobBuilds  = "ListJobBuilds"
	ListJobInputs  = "ListJobInputs"
	GetJobBuild    = "GetJobBuild"
	PauseJob       = "PauseJob"
	UnpauseJob     = "UnpauseJob"
	GetVersionsDB  = "GetVersionsDB"
	JobBadge       = "JobBadge"
	MainJobBadge   = "MainJobBadge"

	ClearTaskCache          = "ClearTaskCache"
	ClearTaskCacheQueryPath = "CachePath"

	ListAllResources     = "ListAllResources"
	ListResources        = "ListResources"
	ListResourceTypes    = "ListResourceTypes"
	GetResource          = "GetResource"
	PauseResource        = "PauseResource"
	UnpauseResource      = "UnpauseResource"
	CheckResource        = "CheckResource"
	CheckResourceWebHook = "CheckResourceWebHook"

	ListResourceVersions          = "ListResourceVersions"
	GetResourceVersion            = "GetResourceVersion"
	EnableResourceVersion         = "EnableResourceVersion"
	DisableResourceVersion        = "DisableResourceVersion"
	ListBuildsWithVersionAsInput  = "ListBuildsWithVersionAsInput"
	ListBuildsWithVersionAsOutput = "ListBuildsWithVersionAsOutput"
	GetResourceCausality          = "GetResourceCausality"

	ListAllPipelines    = "ListAllPipelines"
	ListPipelines       = "ListPipelines"
	GetPipeline         = "GetPipeline"
	DeletePipeline      = "DeletePipeline"
	OrderPipelines      = "OrderPipelines"
	PausePipeline       = "PausePipeline"
	UnpausePipeline     = "UnpausePipeline"
	ExposePipeline      = "ExposePipeline"
	HidePipeline        = "HidePipeline"
	RenamePipeline      = "RenamePipeline"
	ListPipelineBuilds  = "ListPipelineBuilds"
	CreatePipelineBuild = "CreatePipelineBuild"
	PipelineBadge       = "PipelineBadge"

	RegisterWorker  = "RegisterWorker"
	LandWorker      = "LandWorker"
	RetireWorker    = "RetireWorker"
	PruneWorker     = "PruneWorker"
	HeartbeatWorker = "HeartbeatWorker"
	ListWorkers     = "ListWorkers"
	DeleteWorker    = "DeleteWorker"

	SetLogLevel = "SetLogLevel"
	GetLogLevel = "GetLogLevel"

	DownloadCLI  = "DownloadCLI"
	GetInfo      = "Info"
	GetInfoCreds = "InfoCreds"

	ListContainers           = "ListContainers"
	GetContainer             = "GetContainer"
	HijackContainer          = "HijackContainer"
	ListDestroyingContainers = "ListDestroyingContainers"
	ReportWorkerContainers   = "ReportWorkerContainers"

	ListVolumes           = "ListVolumes"
	ListDestroyingVolumes = "ListDestroyingVolumes"
	ReportWorkerVolumes   = "ReportWorkerVolumes"

	ListTeams      = "ListTeams"
	SetTeam        = "SetTeam"
	RenameTeam     = "RenameTeam"
	DestroyTeam    = "DestroyTeam"
	ListTeamBuilds = "ListTeamBuilds"

	SendInputToBuildPlan    = "SendInputToBuildPlan"
	ReadOutputFromBuildPlan = "ReadOutputFromBuildPlan"
)

var Routes = rata.Routes([]rata.Route{
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/config", Method: "PUT", Name: SaveConfig},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/config", Method: "GET", Name: GetConfig},

	{Path: "/api/v1/teams/:team_name/builds", Method: "POST", Name: CreateBuild},

	{Path: "/api/v1/builds", Method: "GET", Name: ListBuilds},
	{Path: "/api/v1/builds/:build_id", Method: "GET", Name: GetBuild},
	{Path: "/api/v1/builds/:build_id/plan", Method: "GET", Name: GetBuildPlan},
	{Path: "/api/v1/builds/:build_id/plan/:plan_id/input", Method: "PUT", Name: SendInputToBuildPlan},
	{Path: "/api/v1/builds/:build_id/plan/:plan_id/output", Method: "GET", Name: ReadOutputFromBuildPlan},
	{Path: "/api/v1/builds/:build_id/events", Method: "GET", Name: BuildEvents},
	{Path: "/api/v1/builds/:build_id/resources", Method: "GET", Name: BuildResources},
	{Path: "/api/v1/builds/:build_id/abort", Method: "PUT", Name: AbortBuild},
	{Path: "/api/v1/builds/:build_id/preparation", Method: "GET", Name: GetBuildPreparation},

	{Path: "/api/v1/jobs", Method: "GET", Name: ListAllJobs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs", Method: "GET", Name: ListJobs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name", Method: "GET", Name: GetJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", Method: "GET", Name: ListJobBuilds},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", Method: "POST", Name: CreateJobBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/inputs", Method: "GET", Name: ListJobInputs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds/:build_name", Method: "GET", Name: GetJobBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/pause", Method: "PUT", Name: PauseJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/unpause", Method: "PUT", Name: UnpauseJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/badge", Method: "GET", Name: JobBadge},
	{Path: "/api/v1/pipelines/:pipeline_name/jobs/:job_name/badge", Method: "GET", Name: MainJobBadge},

	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/tasks/:step_name/cache", Method: "DELETE", Name: ClearTaskCache},

	{Path: "/api/v1/pipelines", Method: "GET", Name: ListAllPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines", Method: "GET", Name: ListPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name", Method: "GET", Name: GetPipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name", Method: "DELETE", Name: DeletePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/ordering", Method: "PUT", Name: OrderPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/pause", Method: "PUT", Name: PausePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/unpause", Method: "PUT", Name: UnpausePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/expose", Method: "PUT", Name: ExposePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/hide", Method: "PUT", Name: HidePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/versions-db", Method: "GET", Name: GetVersionsDB},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/rename", Method: "PUT", Name: RenamePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/builds", Method: "GET", Name: ListPipelineBuilds},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/builds", Method: "POST", Name: CreatePipelineBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/badge", Method: "GET", Name: PipelineBadge},

	{Path: "/api/v1/resources", Method: "GET", Name: ListAllResources},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources", Method: "GET", Name: ListResources},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resource-types", Method: "GET", Name: ListResourceTypes},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name", Method: "GET", Name: GetResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/pause", Method: "PUT", Name: PauseResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/unpause", Method: "PUT", Name: UnpauseResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/check", Method: "POST", Name: CheckResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/check/webhook", Method: "POST", Name: CheckResourceWebHook},

	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions", Method: "GET", Name: ListResourceVersions},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id", Method: "GET", Name: GetResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/enable", Method: "PUT", Name: EnableResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/disable", Method: "PUT", Name: DisableResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/input_to", Method: "GET", Name: ListBuildsWithVersionAsInput},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/output_of", Method: "GET", Name: ListBuildsWithVersionAsOutput},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/causality", Method: "GET", Name: GetResourceCausality},

	{Path: "/api/v1/workers", Method: "GET", Name: ListWorkers},
	{Path: "/api/v1/workers", Method: "POST", Name: RegisterWorker},
	{Path: "/api/v1/workers/:worker_name/land", Method: "PUT", Name: LandWorker},
	{Path: "/api/v1/workers/:worker_name/retire", Method: "PUT", Name: RetireWorker},
	{Path: "/api/v1/workers/:worker_name/prune", Method: "PUT", Name: PruneWorker},
	{Path: "/api/v1/workers/:worker_name/heartbeat", Method: "PUT", Name: HeartbeatWorker},
	{Path: "/api/v1/workers/:worker_name", Method: "DELETE", Name: DeleteWorker},

	{Path: "/api/v1/log-level", Method: "GET", Name: GetLogLevel},
	{Path: "/api/v1/log-level", Method: "PUT", Name: SetLogLevel},

	{Path: "/api/v1/cli", Method: "GET", Name: DownloadCLI},
	{Path: "/api/v1/info", Method: "GET", Name: GetInfo},
	{Path: "/api/v1/info/creds", Method: "GET", Name: GetInfoCreds},

	{Path: "/api/v1/containers/destroying", Method: "GET", Name: ListDestroyingContainers},
	{Path: "/api/v1/containers/report", Method: "PUT", Name: ReportWorkerContainers},
	{Path: "/api/v1/teams/:team_name/containers", Method: "GET", Name: ListContainers},
	{Path: "/api/v1/teams/:team_name/containers/:id", Method: "GET", Name: GetContainer},
	{Path: "/api/v1/teams/:team_name/containers/:id/hijack", Method: "GET", Name: HijackContainer},

	{Path: "/api/v1/teams/:team_name/volumes", Method: "GET", Name: ListVolumes},
	{Path: "/api/v1/volumes/destroying", Method: "GET", Name: ListDestroyingVolumes},
	{Path: "/api/v1/volumes/report", Method: "PUT", Name: ReportWorkerVolumes},

	{Path: "/api/v1/teams", Method: "GET", Name: ListTeams},
	{Path: "/api/v1/teams/:team_name", Method: "PUT", Name: SetTeam},
	{Path: "/api/v1/teams/:team_name/rename", Method: "PUT", Name: RenameTeam},
	{Path: "/api/v1/teams/:team_name", Method: "DELETE", Name: DestroyTeam},
	{Path: "/api/v1/teams/:team_name/builds", Method: "GET", Name: ListTeamBuilds},
})
<commit_msg>snake-case cache_path query param for consistency<commit_after>package atc

import "github.com/tedsuo/rata"

const (
	SaveConfig = "SaveConfig"
	GetConfig  = "GetConfig"

	GetBuild            = "GetBuild"
	GetBuildPlan        = "GetBuildPlan"
	CreateBuild         = "CreateBuild"
	ListBuilds          = "ListBuilds"
	BuildEvents         = "BuildEvents"
	BuildResources      = "BuildResources"
	AbortBuild          = "AbortBuild"
	GetBuildPreparation = "GetBuildPreparation"

	GetJob         = "GetJob"
	CreateJobBuild = "CreateJobBuild"
	ListAllJobs    = "ListAllJobs"
	ListJobs       = "ListJobs"
	ListJobBuilds  = "ListJobBuilds"
	ListJobInputs  = "ListJobInputs"
	GetJobBuild    = "GetJobBuild"
	PauseJob       = "PauseJob"
	UnpauseJob     = "UnpauseJob"
	GetVersionsDB  = "GetVersionsDB"
	JobBadge       = "JobBadge"
	MainJobBadge   = "MainJobBadge"

	ClearTaskCache = "ClearTaskCache"

	ListAllResources     = "ListAllResources"
	ListResources        = "ListResources"
	ListResourceTypes    = "ListResourceTypes"
	GetResource          = "GetResource"
	PauseResource        = "PauseResource"
	UnpauseResource      = "UnpauseResource"
	CheckResource        = "CheckResource"
	CheckResourceWebHook = "CheckResourceWebHook"

	ListResourceVersions          = "ListResourceVersions"
	GetResourceVersion            = "GetResourceVersion"
	EnableResourceVersion         = "EnableResourceVersion"
	DisableResourceVersion        = "DisableResourceVersion"
	ListBuildsWithVersionAsInput  = "ListBuildsWithVersionAsInput"
	ListBuildsWithVersionAsOutput = "ListBuildsWithVersionAsOutput"
	GetResourceCausality          = "GetResourceCausality"

	ListAllPipelines    = "ListAllPipelines"
	ListPipelines       = "ListPipelines"
	GetPipeline         = "GetPipeline"
	DeletePipeline      = "DeletePipeline"
	OrderPipelines      = "OrderPipelines"
	PausePipeline       = "PausePipeline"
	UnpausePipeline     = "UnpausePipeline"
	ExposePipeline      = "ExposePipeline"
	HidePipeline        = "HidePipeline"
	RenamePipeline      = "RenamePipeline"
	ListPipelineBuilds  = "ListPipelineBuilds"
	CreatePipelineBuild = "CreatePipelineBuild"
	PipelineBadge       = "PipelineBadge"

	RegisterWorker  = "RegisterWorker"
	LandWorker      = "LandWorker"
	RetireWorker    = "RetireWorker"
	PruneWorker     = "PruneWorker"
	HeartbeatWorker = "HeartbeatWorker"
	ListWorkers     = "ListWorkers"
	DeleteWorker    = "DeleteWorker"

	SetLogLevel = "SetLogLevel"
	GetLogLevel = "GetLogLevel"

	DownloadCLI  = "DownloadCLI"
	GetInfo      = "Info"
	GetInfoCreds = "InfoCreds"

	ListContainers           = "ListContainers"
	GetContainer             = "GetContainer"
	HijackContainer          = "HijackContainer"
	ListDestroyingContainers = "ListDestroyingContainers"
	ReportWorkerContainers   = "ReportWorkerContainers"

	ListVolumes           = "ListVolumes"
	ListDestroyingVolumes = "ListDestroyingVolumes"
	ReportWorkerVolumes   = "ReportWorkerVolumes"

	ListTeams      = "ListTeams"
	SetTeam        = "SetTeam"
	RenameTeam     = "RenameTeam"
	DestroyTeam    = "DestroyTeam"
	ListTeamBuilds = "ListTeamBuilds"

	SendInputToBuildPlan    = "SendInputToBuildPlan"
	ReadOutputFromBuildPlan = "ReadOutputFromBuildPlan"
)

const (
	ClearTaskCacheQueryPath = "cache_path"
)

var Routes = rata.Routes([]rata.Route{
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/config", Method: "PUT", Name: SaveConfig},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/config", Method: "GET", Name: GetConfig},

	{Path: "/api/v1/teams/:team_name/builds", Method: "POST", Name: CreateBuild},

	{Path: "/api/v1/builds", Method: "GET", Name: ListBuilds},
	{Path: "/api/v1/builds/:build_id", Method: "GET", Name: GetBuild},
	{Path: "/api/v1/builds/:build_id/plan", Method: "GET", Name: GetBuildPlan},
	{Path: "/api/v1/builds/:build_id/plan/:plan_id/input", Method: "PUT", Name: SendInputToBuildPlan},
	{Path: "/api/v1/builds/:build_id/plan/:plan_id/output", Method: "GET", Name: ReadOutputFromBuildPlan},
	{Path: "/api/v1/builds/:build_id/events", Method: "GET", Name: BuildEvents},
	{Path: "/api/v1/builds/:build_id/resources", Method: "GET", Name: BuildResources},
	{Path: "/api/v1/builds/:build_id/abort", Method: "PUT", Name: AbortBuild},
	{Path: "/api/v1/builds/:build_id/preparation", Method: "GET", Name: GetBuildPreparation},

	{Path: "/api/v1/jobs", Method: "GET", Name: ListAllJobs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs", Method: "GET", Name: ListJobs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name", Method: "GET", Name: GetJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", Method: "GET", Name: ListJobBuilds},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds", Method: "POST", Name: CreateJobBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/inputs", Method: "GET", Name: ListJobInputs},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/builds/:build_name", Method: "GET", Name: GetJobBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/pause", Method: "PUT", Name: PauseJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/unpause", Method: "PUT", Name: UnpauseJob},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/badge", Method: "GET", Name: JobBadge},
	{Path: "/api/v1/pipelines/:pipeline_name/jobs/:job_name/badge", Method: "GET", Name: MainJobBadge},

	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/jobs/:job_name/tasks/:step_name/cache", Method: "DELETE", Name: ClearTaskCache},

	{Path: "/api/v1/pipelines", Method: "GET", Name: ListAllPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines", Method: "GET", Name: ListPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name", Method: "GET", Name: GetPipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name", Method: "DELETE", Name: DeletePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/ordering", Method: "PUT", Name: OrderPipelines},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/pause", Method: "PUT", Name: PausePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/unpause", Method: "PUT", Name: UnpausePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/expose", Method: "PUT", Name: ExposePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/hide", Method: "PUT", Name: HidePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/versions-db", Method: "GET", Name: GetVersionsDB},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/rename", Method: "PUT", Name: RenamePipeline},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/builds", Method: "GET", Name: ListPipelineBuilds},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/builds", Method: "POST", Name: CreatePipelineBuild},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/badge", Method: "GET", Name: PipelineBadge},

	{Path: "/api/v1/resources", Method: "GET", Name: ListAllResources},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources", Method: "GET", Name: ListResources},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resource-types", Method: "GET", Name: ListResourceTypes},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name", Method: "GET", Name: GetResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/pause", Method: "PUT", Name: PauseResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/unpause", Method: "PUT", Name: UnpauseResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/check", Method: "POST", Name: CheckResource},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/check/webhook", Method: "POST", Name: CheckResourceWebHook},

	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions", Method: "GET", Name: ListResourceVersions},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id", Method: "GET", Name: GetResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/enable", Method: "PUT", Name: EnableResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/disable", Method: "PUT", Name: DisableResourceVersion},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/input_to", Method: "GET", Name: ListBuildsWithVersionAsInput},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/output_of", Method: "GET", Name: ListBuildsWithVersionAsOutput},
	{Path: "/api/v1/teams/:team_name/pipelines/:pipeline_name/resources/:resource_name/versions/:resource_version_id/causality", Method: "GET", Name: GetResourceCausality},

	{Path: "/api/v1/workers", Method: "GET", Name: ListWorkers},
	{Path: "/api/v1/workers", Method: "POST", Name: RegisterWorker},
	{Path: "/api/v1/workers/:worker_name/land", Method: "PUT", Name: LandWorker},
	{Path: "/api/v1/workers/:worker_name/retire", Method: "PUT", Name: RetireWorker},
	{Path: "/api/v1/workers/:worker_name/prune", Method: "PUT", Name: PruneWorker},
	{Path: "/api/v1/workers/:worker_name/heartbeat", Method: "PUT", Name: HeartbeatWorker},
	{Path: "/api/v1/workers/:worker_name", Method: "DELETE", Name: DeleteWorker},

	{Path: "/api/v1/log-level", Method: "GET", Name: GetLogLevel},
	{Path: "/api/v1/log-level", Method: "PUT", Name: SetLogLevel},

	{Path: "/api/v1/cli", Method: "GET", Name: DownloadCLI},
	{Path: "/api/v1/info", Method: "GET", Name: GetInfo},
	{Path: "/api/v1/info/creds", Method: "GET", Name: GetInfoCreds},

	{Path: "/api/v1/containers/destroying", Method: "GET", Name: ListDestroyingContainers},
	{Path: "/api/v1/containers/report", Method: "PUT", Name: ReportWorkerContainers},
	{Path: "/api/v1/teams/:team_name/containers", Method: "GET", Name: ListContainers},
	{Path: "/api/v1/teams/:team_name/containers/:id", Method: "GET", Name: GetContainer},
	{Path: "/api/v1/teams/:team_name/containers/:id/hijack", Method: "GET", Name: HijackContainer},

	{Path: "/api/v1/teams/:team_name/volumes", Method: "GET", Name: ListVolumes},
	{Path: "/api/v1/volumes/destroying", Method: "GET", Name: ListDestroyingVolumes},
	{Path: "/api/v1/volumes/report", Method: "PUT", Name: ReportWorkerVolumes},

	{Path: "/api/v1/teams", Method: "GET", Name: ListTeams},
	{Path: "/api/v1/teams/:team_name", Method: "PUT", Name: SetTeam},
	{Path: "/api/v1/teams/:team_name/rename", Method: "PUT", Name: RenameTeam},
	{Path: "/api/v1/teams/:team_name", Method: "DELETE", Name: DestroyTeam},
	{Path: "/api/v1/teams/:team_name/builds", Method: "GET", Name: ListTeamBuilds},
})
<|endoftext|>