<commit_before>package ishell

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"runtime"
	"strings"
)

// Actions are actions that can be performed by a shell.
type Actions interface {
	// ReadLine reads a line from standard input.
	ReadLine() string
	// ReadLineErr is ReadLine but returns error as well
	ReadLineErr() (string, error)
	// ReadPassword reads password from standard input without echoing the characters.
	// Note that this only works as expected when the standard input is a terminal.
	ReadPassword() string
	// ReadPasswordErr is ReadPassword but returns error as well
	ReadPasswordErr() (string, error)
	// ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to
	// f and stops reading when f returns false.
	ReadMultiLinesFunc(f func(string) bool) string
	// ReadMultiLines reads multiple lines from standard input. It stops reading when terminator
	// is encountered at the end of the line. It returns the lines read including terminator.
	// For more control, use ReadMultiLinesFunc.
	ReadMultiLines(terminator string) string
	// Println prints to output and ends with newline character.
	Println(val ...interface{})
	// Print prints to output.
	Print(val ...interface{})
	// Printf prints to output using string format.
	Printf(format string, val ...interface{})
	// ShowPaged shows a paged text that is scrollable.
	// This leverages on "less" for unix and "more" for windows.
	ShowPaged(text string) error
	// MultiChoice presents options to the user.
	// returns the index of the selection or -1 if nothing is
	// selected.
	// text is displayed before the options.
	MultiChoice(options []string, text string) int
	// Checklist is similar to MultiChoice but user can choose multiple variants using Space.
	// init is initially selected options.
	Checklist(options []string, text string, init []int) []int
	// SetPrompt sets the prompt string. The string to be displayed before the cursor.
	SetPrompt(prompt string)
	// SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before
	// the cursor; starting from the second line of input.
	SetMultiPrompt(prompt string)
	// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
	// Defaults to true.
	ShowPrompt(show bool)
	// Cmds returns all the commands added to the shell.
	Cmds() []*Cmd
	// HelpText returns the computed help of top level commands.
	HelpText() string
	// ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd.
	ClearScreen() error
	// Stop stops the shell. This will stop the shell from auto reading inputs and calling
	// registered functions. A stopped shell is only inactive but totally functional.
	// Its functions can still be called and can be restarted.
	Stop()
}

type shellActionsImpl struct {
	*Shell
}

// ReadLine reads a line from standard input.
func (s *shellActionsImpl) ReadLine() string {
	line, _ := s.readLine()
	return line
}

func (s *shellActionsImpl) ReadLineErr() (string, error) {
	return s.readLine()
}

func (s *shellActionsImpl) ReadPassword() string {
	return s.reader.readPassword()
}

func (s *shellActionsImpl) ReadPasswordErr() (string, error) {
	return s.reader.readPasswordErr()
}

func (s *shellActionsImpl) ReadMultiLinesFunc(f func(string) bool) string {
	lines, _ := s.readMultiLinesFunc(f)
	return lines
}

func (s *shellActionsImpl) ReadMultiLines(terminator string) string {
	return s.ReadMultiLinesFunc(func(line string) bool {
		if strings.HasSuffix(strings.TrimSpace(line), terminator) {
			return false
		}
		return true
	})
}

func (s *shellActionsImpl) Println(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintln(s.writer, val...)
}

func (s *shellActionsImpl) Print(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprint(s.reader.buf, val...)
	fmt.Fprint(s.writer, val...)
}

func (s *shellActionsImpl) Printf(format string, val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintf(s.reader.buf, format, val...)
	fmt.Fprintf(s.writer, format, val...)
}

func (s *shellActionsImpl) MultiChoice(options []string, text string) int {
	choice := s.multiChoice(options, text, nil, false)
	return choice[0]
}
func (s *shellActionsImpl) Checklist(options []string, text string, init []int) []int {
	return s.multiChoice(options, text, init, true)
}
func (s *shellActionsImpl) SetPrompt(prompt string) {
	s.reader.prompt = prompt
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) SetMultiPrompt(prompt string) {
	s.reader.multiPrompt = prompt
}

func (s *shellActionsImpl) ShowPrompt(show bool) {
	s.reader.showPrompt = show
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) Cmds() []*Cmd {
	var cmds []*Cmd
	for _, cmd := range s.rootCmd.children {
		cmds = append(cmds, cmd)
	}
	return cmds
}

func (s *shellActionsImpl) ClearScreen() error {
	return clearScreen(s.Shell)
}

func (s *shellActionsImpl) ShowPaged(text string) error {
	return showPaged(s.Shell, text)
}

func (s *shellActionsImpl) ShowPagedReader(r io.Reader) error {
	return showPagedReader(s.Shell, r)
}

func (s *shellActionsImpl) Stop() {
	s.stop()
}

func (s *shellActionsImpl) HelpText() string {
	return s.rootCmd.HelpText()
}

func showPaged(s *Shell, text string) error {
	var cmd *exec.Cmd

	if s.pager == "" {
		if runtime.GOOS == "windows" {
			s.pager = "more"
		} else {
			s.pager = "less"
		}
	}

	cmd = exec.Command(s.pager, s.pagerArgs...)
	cmd.Stdout = s.writer
	cmd.Stderr = s.writer
	cmd.Stdin = bytes.NewBufferString(text)
	return cmd.Run()
}

func showPagedReader(s *Shell, r io.Reader) error {
	var cmd *exec.Cmd

	if s.pager == "" {
		if runtime.GOOS == "windows" {
			s.pager = "more"
		} else {
			s.pager = "less"
		}
	}

	cmd = exec.Command(s.pager, s.pagerArgs...)
	cmd.Stdout = s.writer
	cmd.Stderr = s.writer
	cmd.Stdin = r
	return cmd.Run()
}
<commit_msg>Added ShowPagedReader in Actions interface<commit_after>package ishell

import (
	"bytes"
	"fmt"
	"io"
	"os/exec"
	"runtime"
	"strings"
)

// Actions are actions that can be performed by a shell.
type Actions interface {
	// ReadLine reads a line from standard input.
	ReadLine() string
	// ReadLineErr is ReadLine but returns error as well
	ReadLineErr() (string, error)
	// ReadPassword reads password from standard input without echoing the characters.
	// Note that this only works as expected when the standard input is a terminal.
	ReadPassword() string
	// ReadPasswordErr is ReadPassword but returns error as well
	ReadPasswordErr() (string, error)
	// ReadMultiLinesFunc reads multiple lines from standard input. It passes each read line to
	// f and stops reading when f returns false.
	ReadMultiLinesFunc(f func(string) bool) string
	// ReadMultiLines reads multiple lines from standard input. It stops reading when terminator
	// is encountered at the end of the line. It returns the lines read including terminator.
	// For more control, use ReadMultiLinesFunc.
	ReadMultiLines(terminator string) string
	// Println prints to output and ends with newline character.
	Println(val ...interface{})
	// Print prints to output.
	Print(val ...interface{})
	// Printf prints to output using string format.
	Printf(format string, val ...interface{})
	// ShowPaged shows a paged text that is scrollable.
	// This leverages on "less" for unix and "more" for windows.
	ShowPaged(text string) error
	// ShowPagedReader shows a paged text that is scrollable, from a reader source.
	// This leverages on "less" for unix and "more" for windows.
	ShowPagedReader(text string) error
	// MultiChoice presents options to the user.
	// returns the index of the selection or -1 if nothing is
	// selected.
	// text is displayed before the options.
	MultiChoice(options []string, text string) int
	// Checklist is similar to MultiChoice but user can choose multiple variants using Space.
	// init is initially selected options.
	Checklist(options []string, text string, init []int) []int
	// SetPrompt sets the prompt string. The string to be displayed before the cursor.
	SetPrompt(prompt string)
	// SetMultiPrompt sets the prompt string used for multiple lines. The string to be displayed before
	// the cursor; starting from the second line of input.
	SetMultiPrompt(prompt string)
	// ShowPrompt sets whether prompt should show when requesting input for ReadLine and ReadPassword.
	// Defaults to true.
	ShowPrompt(show bool)
	// Cmds returns all the commands added to the shell.
	Cmds() []*Cmd
	// HelpText returns the computed help of top level commands.
	HelpText() string
	// ClearScreen clears the screen. Same behaviour as running 'clear' in unix terminal or 'cls' in windows cmd.
	ClearScreen() error
	// Stop stops the shell. This will stop the shell from auto reading inputs and calling
	// registered functions. A stopped shell is only inactive but totally functional.
	// Its functions can still be called and can be restarted.
	Stop()
}

type shellActionsImpl struct {
	*Shell
}

// ReadLine reads a line from standard input.
func (s *shellActionsImpl) ReadLine() string {
	line, _ := s.readLine()
	return line
}

func (s *shellActionsImpl) ReadLineErr() (string, error) {
	return s.readLine()
}

func (s *shellActionsImpl) ReadPassword() string {
	return s.reader.readPassword()
}

func (s *shellActionsImpl) ReadPasswordErr() (string, error) {
	return s.reader.readPasswordErr()
}

func (s *shellActionsImpl) ReadMultiLinesFunc(f func(string) bool) string {
	lines, _ := s.readMultiLinesFunc(f)
	return lines
}

func (s *shellActionsImpl) ReadMultiLines(terminator string) string {
	return s.ReadMultiLinesFunc(func(line string) bool {
		if strings.HasSuffix(strings.TrimSpace(line), terminator) {
			return false
		}
		return true
	})
}

func (s *shellActionsImpl) Println(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintln(s.writer, val...)
}

func (s *shellActionsImpl) Print(val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprint(s.reader.buf, val...)
	fmt.Fprint(s.writer, val...)
}

func (s *shellActionsImpl) Printf(format string, val ...interface{}) {
	s.reader.buf.Truncate(0)
	fmt.Fprintf(s.reader.buf, format, val...)
	fmt.Fprintf(s.writer, format, val...)
}

func (s *shellActionsImpl) MultiChoice(options []string, text string) int {
	choice := s.multiChoice(options, text, nil, false)
	return choice[0]
}
func (s *shellActionsImpl) Checklist(options []string, text string, init []int) []int {
	return s.multiChoice(options, text, init, true)
}
func (s *shellActionsImpl) SetPrompt(prompt string) {
	s.reader.prompt = prompt
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) SetMultiPrompt(prompt string) {
	s.reader.multiPrompt = prompt
}

func (s *shellActionsImpl) ShowPrompt(show bool) {
	s.reader.showPrompt = show
	s.reader.scanner.SetPrompt(s.reader.rlPrompt())
}

func (s *shellActionsImpl) Cmds() []*Cmd {
	var cmds []*Cmd
	for _, cmd := range s.rootCmd.children {
		cmds = append(cmds, cmd)
	}
	return cmds
}

func (s *shellActionsImpl) ClearScreen() error {
	return clearScreen(s.Shell)
}

func (s *shellActionsImpl) ShowPaged(text string) error {
	return showPaged(s.Shell, text)
}

func (s *shellActionsImpl) ShowPagedReader(r io.Reader) error {
	return showPagedReader(s.Shell, r)
}

func (s *shellActionsImpl) Stop() {
	s.stop()
}

func (s *shellActionsImpl) HelpText() string {
	return s.rootCmd.HelpText()
}

func showPaged(s *Shell, text string) error {
	var cmd *exec.Cmd

	if s.pager == "" {
		if runtime.GOOS == "windows" {
			s.pager = "more"
		} else {
			s.pager = "less"
		}
	}

	cmd = exec.Command(s.pager, s.pagerArgs...)
	cmd.Stdout = s.writer
	cmd.Stderr = s.writer
	cmd.Stdin = bytes.NewBufferString(text)
	return cmd.Run()
}

func showPagedReader(s *Shell, r io.Reader) error {
	var cmd *exec.Cmd

	if s.pager == "" {
		if runtime.GOOS == "windows" {
			s.pager = "more"
		} else {
			s.pager = "less"
		}
	}

	cmd = exec.Command(s.pager, s.pagerArgs...)
	cmd.Stdout = s.writer
	cmd.Stderr = s.writer
	cmd.Stdin = r
	return cmd.Run()
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative 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 adapter

import (
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"go.uber.org/zap"
	"knative.dev/eventing/pkg/adapter"
	kncetesting "knative.dev/eventing/pkg/kncloudevents/testing"
	"knative.dev/pkg/logging"
	pkgtesting "knative.dev/pkg/reconciler/testing"
	"knative.dev/pkg/source"

	"github.com/go-kivik/kivik/driver"
	"github.com/go-kivik/kivikmock"
)

type mockReporter struct {
	eventCount int
}

func (r *mockReporter) ReportEventCount(args *source.ReportArgs, responseCode int) error {
	r.eventCount += 1
	return nil
}

func TestNewAdaptor(t *testing.T) {
	ce := kncetesting.NewTestClient()

	testCases := map[string]struct {
		opt envConfig

		wantNamespace string
		wantDatabase  string
	}{
		"with source": {
			opt: envConfig{
				EventSource: "test-source",
				Database:    "mydb",
			},
			wantDatabase: "mydb",
		},
		"with namespace": {
			opt: envConfig{
				EnvConfig: adapter.EnvConfig{
					Namespace: "test-ns",
				},
				EventSource: "test-source",
				Database:    "mydb",
			},
			wantNamespace: "test-ns",
			wantDatabase:  "mydb",
		},
	}
	for n, tc := range testCases {
		t.Run(n, func(t *testing.T) {
			r := &mockReporter{}
			ctx, _ := pkgtesting.SetupFakeContext(t)
			c, mock := kivikmock.NewT(t)

			mock.ExpectDB()

			a := newAdapter(ctx, &tc.opt, ce, r, c.DSN(), "kivikmock")

			got, ok := a.(*couchDbAdapter)
			if !ok {
				t.Errorf("expected NewAdapter to return a *couchDbAdapter, but did not")
			}
			if diff := cmp.Diff(tc.opt.EventSource, got.source); diff != "" {
				t.Errorf("unexpected source diff (-want, +got) = %v", diff)
			}
			if diff := cmp.Diff(tc.wantNamespace, got.namespace); diff != "" {
				t.Errorf("unexpected namespace diff (-want, +got) = %v", diff)
			}
			if diff := cmp.Diff(tc.wantDatabase, got.couchDB.Name()); diff != "" {
				t.Errorf("unexpected namespace diff (-want, +got) = %v", diff)
			}
		})
	}
}

func TestReceiveEventPoll(t *testing.T) {
	ce := kncetesting.NewTestClient()

	env := envConfig{
		EnvConfig: adapter.EnvConfig{
			Namespace: "default",
		},
		EventSource: "test-source",
		Database:    "testdb",
	}
	r := &mockReporter{}
	ctx, _ := pkgtesting.SetupFakeContext(t)
	logger := zap.NewExample().Sugar()
	ctx = logging.WithLogger(ctx, logger)

	c, mock := kivikmock.NewT(t)

	mockDB := mock.NewDB()
	mock.ExpectDB().WithName("testdb").WillReturn(mockDB)
	mockDB.ExpectChanges().WillReturn(kivikmock.NewChanges().AddChange(&driver.Change{
		ID:      "anid",
		Seq:     "aseq",
		Changes: driver.ChangedRevs{"arev"},
	}))

	a := newAdapter(ctx, &env, ce, r, c.DSN(), "kivikmock").(*couchDbAdapter)

	stopCh := make(chan struct{})
	done := make(chan struct{})
	stopped := false
	go func() {
		a.Start(stopCh)
		stopped = true
		done <- struct{}{}
	}()

	time.Sleep(3 * time.Second) // Run at least onece

	validateSent(t, ce, `["arev"]`)

	if !stopped {
		stopCh <- struct{}{}
	}
	<-done
}

func validateSent(t *testing.T, ce *kncetesting.TestCloudEventsClient, wantData string) {
	if got := len(ce.Sent()); got != 1 {
		t.Errorf("Expected 1 event to be sent, got %d", got)
	}

	if got := ce.Sent()[0].Data; string(got.([]byte)) != wantData {
		t.Errorf("Expected %q event to be sent, got %q", wantData, string(got.([]byte)))
	}
}
<commit_msg>Fix for a race in a CouchDB source adapter test (#660)<commit_after>/*
Copyright 2019 The Knative 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 adapter

import (
	"testing"
	"context"

	"github.com/google/go-cmp/cmp"
	"go.uber.org/zap"
	cloudevents "github.com/cloudevents/sdk-go"
	"knative.dev/eventing/pkg/adapter"
	kncetesting "knative.dev/eventing/pkg/kncloudevents/testing"
	"knative.dev/pkg/logging"
	pkgtesting "knative.dev/pkg/reconciler/testing"
	"knative.dev/pkg/source"

	"github.com/go-kivik/kivik/driver"
	"github.com/go-kivik/kivikmock"
)

type mockReporter struct {
	eventCount int
}

func (r *mockReporter) ReportEventCount(args *source.ReportArgs, responseCode int) error {
	r.eventCount += 1
	return nil
}

func TestNewAdapter(t *testing.T) {
	ce := kncetesting.NewTestClient()

	testCases := map[string]struct {
		opt envConfig

		wantNamespace string
		wantDatabase  string
	}{
		"with source": {
			opt: envConfig{
				EventSource: "test-source",
				Database:    "mydb",
			},
			wantDatabase: "mydb",
		},
		"with namespace": {
			opt: envConfig{
				EnvConfig: adapter.EnvConfig{
					Namespace: "test-ns",
				},
				EventSource: "test-source",
				Database:    "mydb",
			},
			wantNamespace: "test-ns",
			wantDatabase:  "mydb",
		},
	}
	for n, tc := range testCases {
		t.Run(n, func(t *testing.T) {
			r := &mockReporter{}
			ctx, _ := pkgtesting.SetupFakeContext(t)
			c, mock := kivikmock.NewT(t)

			mock.ExpectDB()

			a := newAdapter(ctx, &tc.opt, ce, r, c.DSN(), "kivikmock")

			got, ok := a.(*couchDbAdapter)
			if !ok {
				t.Errorf("expected NewAdapter to return a *couchDbAdapter, but did not")
			}
			if diff := cmp.Diff(tc.opt.EventSource, got.source); diff != "" {
				t.Errorf("unexpected source diff (-want, +got) = %v", diff)
			}
			if diff := cmp.Diff(tc.wantNamespace, got.namespace); diff != "" {
				t.Errorf("unexpected namespace diff (-want, +got) = %v", diff)
			}
			if diff := cmp.Diff(tc.wantDatabase, got.couchDB.Name()); diff != "" {
				t.Errorf("unexpected namespace diff (-want, +got) = %v", diff)
			}
		})
	}
}

type adapterTestClient struct {
	*kncetesting.TestCloudEventsClient
	stopCh chan struct{}
}

var _ cloudevents.Client = (*adapterTestClient)(nil)

func newAdapterTestClient() *adapterTestClient {
	return &adapterTestClient{
		kncetesting.NewTestClient(),
		make(chan struct{}),
	}
}

func (c *adapterTestClient) Send(ctx context.Context, event cloudevents.Event) (context.Context, *cloudevents.Event, error) {
	retCtx, retEvent, retError := c.TestCloudEventsClient.Send(ctx, event)
	c.stopCh <- struct{}{}
	return retCtx, retEvent, retError
}

func TestReceiveEventPoll(t *testing.T) {
	ce := newAdapterTestClient()

	env := envConfig{
		EnvConfig: adapter.EnvConfig{
			Namespace: "default",
		},
		EventSource: "test-source",
		Database:    "testdb",
	}
	r := &mockReporter{}
	ctx, _ := pkgtesting.SetupFakeContext(t)
	logger := zap.NewExample().Sugar()
	ctx = logging.WithLogger(ctx, logger)

	c, mock := kivikmock.NewT(t)

	mockDB := mock.NewDB()
	mock.ExpectDB().WithName("testdb").WillReturn(mockDB)
	mockDB.ExpectChanges().WillReturn(kivikmock.NewChanges().AddChange(&driver.Change{
		ID:      "anid",
		Seq:     "aseq",
		Changes: driver.ChangedRevs{"arev"},
	}))

	a := newAdapter(ctx, &env, ce, r, c.DSN(), "kivikmock").(*couchDbAdapter)

	done := make(chan struct{})
	go func() {
		a.Start(ce.stopCh)
		done <- struct{}{}
	}()
	<- done

	validateSent(t, ce, `["arev"]`)
}

func validateSent(t *testing.T, ce *adapterTestClient, wantData string) {
	if got := len(ce.Sent()); got != 1 {
		t.Errorf("Expected 1 event to be sent, got %d", got)
	}

	if got := ce.Sent()[0].Data; string(got.([]byte)) != wantData {
		t.Errorf("Expected %q event to be sent, got %q", wantData, string(got.([]byte)))
	}
}
<|endoftext|>


<commit_before>package admin

import (
	"errors"
	"fmt"
	"jiacrontab/models"
	"jiacrontab/pkg/proto"

	"github.com/iwannay/log"

	"github.com/kataras/iris"
)

var (
	paramsError = errors.New("参数错误")
)

type Parameter interface {
	Verify(iris.Context) error
}

type JobReqParams struct {
	JobID uint   `json:"jobID"`
	Addr  string `json:"addr"`
}

func (p *JobReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.JobID == 0 || p.Addr == "" {
		return paramsError
	}
	return nil
}

type JobsReqParams struct {
	JobIDs []uint `json:"jobIDs"`
	Addr   string `json:"addr"`
}

func (p *JobsReqParams) verify(ctx iris.Context) error {
	if ctx != nil {
		if err := ctx.ReadJSON(p); err != nil {
			return paramsError
		}
	}

	if len(p.JobIDs) == 0 || p.Addr == "" {
		return paramsError
	}
	return nil
}

type EditJobReqParams struct {
	JobID           uint              `json:"jobID"`
	Addr            string            `json:"addr"`
	IsSync          bool              `json:"isSync"`
	Name            string            `json:"name"`
	Commands        [][]string        `json:"commands"`
	Timeout         int               `json:"timeout"`
	MaxConcurrent   uint              `json:"maxConcurrent"`
	ErrorMailNotify bool              `json:"errorMailNotify"`
	ErrorAPINotify  bool              `json:"errorAPINotify"`
	MailTo          []string          `json:"mailTo"`
	APITo           []string          `json:"APITo"`
	RetryNum        int               `json:"retryNum"`
	WorkDir         string            `json:"workDir"`
	WorkUser        string            `json:"workUser"`
	WorkEnv         []string          `json:"workEnv"`
	DependJobs      models.DependJobs `json:"dependJobs"`
	Month           string            `json:"month"`
	Weekday         string            `json:"weekday"`
	Day             string            `json:"day"`
	Hour            string            `json:"hour"`
	Minute          string            `json:"minute"`
	Second          string            `json:"second"`
	TimeoutTrigger  []string          `json:"timeoutTrigger"`
}

// TODO:验证参数
func (p *EditJobReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return err
	}
	ts := map[string]bool{
		proto.TimeoutTrigger_CallApi:   true,
		proto.TimeoutTrigger_SendEmail: true,
		proto.TimeoutTrigger_Kill:      true,
	}

	for _, v := range p.TimeoutTrigger {
		if !ts[v] {
			return fmt.Errorf("%s:%v", v, paramsError)
		}
	}

	if p.Month == "" {
		p.Month = "*"
	}

	if p.Weekday == "" {
		p.Weekday = "*"
	}

	if p.Day == "" {
		p.Day = "*"
	}

	if p.Hour == "" {
		p.Hour = "*"
	}

	if p.Minute == "" {
		p.Minute = "*"
	}

	if p.Second == "" {
		p.Second = "*"
	}

	return nil
}

type GetLogReqParams struct {
	Addr     string `json:"addr"`
	JobID    uint   `json:"jobID"`
	Date     string `json:"date"`
	Pattern  string `json:"pattern"`
	IsTail   bool   `json:"isTail"`
	Page     int    `json:"page"`
	Pagesize int    `json:"pagesize"`
}

func (p *GetLogReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	if p.Page == 0 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}

	return nil
}

type DeleteNodeReqParams struct {
	Addr    string `json:"addr" rule:"required,请填写addr"`
	GroupID uint   `json:"groupID"`
}

func (p *DeleteNodeReqParams) Verify(ctx iris.Context) error {
	return nil
}

type SendTestMailReqParams struct {
	MailTo string `json:"mailTo"`
}

func (p *SendTestMailReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.MailTo == "" {
		return paramsError
	}
	return nil
}

type SystemInfoReqParams struct {
	Addr string `json:"addr"`
}

func (p *SystemInfoReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}
	return nil
}

type GetJobListReqParams struct {
	Addr string `json:"addr"`
	PageReqParams
}

func (p *GetJobListReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type GetGroupListReqParams struct {
	PageReqParams
}

func (p *GetGroupListReqParams) Verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil {
		return paramsError
	}

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type ActionTaskReqParams struct {
	Action string `json:"action"`
	Addr   string `json:"addr"`
	JobIDs []uint `json:"jobIDs"`
}

func (p *ActionTaskReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" ||
		p.Action == "" || len(p.JobIDs) == 0 {
		return paramsError
	}
	return nil
}

type EditDaemonJobReqParams struct {
	Addr            string   `json:"addr" rule:"required,请填写addr"`
	JobID           uint     `json:"jobID"`
	Name            string   `json:"name" rule:"required,请填写name"`
	MailTo          []string `json:"mailTo"`
	APITo           []string `json:"APITo"`
	Commands        []string `json:"commands"  rule:"required,请填写commands"`
	WorkUser        string   `json:"workUser"`
	WorkEnv         []string `json:"workEnv"`
	WorkDir         string   `json:"workDir"`
	FailRestart     bool     `json:"failRestart"`
	ErrorMailNotify bool     `json:"errorMailNotify"`
	ErrorAPINotify  bool     `json:"errorAPINotify"`
}

func (p *EditDaemonJobReqParams) Verify(ctx iris.Context) error {
	return nil
}

type GetJobReqParams struct {
	JobID uint   `json:"jobID" rule:"required,请填写jobID"`
	Addr  string `json:"addr" rule:"required,请填写addr"`
}

func (p *GetJobReqParams) Verify(ctx iris.Context) error {
	return nil
}

type UserReqParams struct {
	Username string `json:"username" rule:"required,请输入用户名"`
	Passwd   string `json:"passwd" rule:"required,请输入密码"`
	GroupID  uint   `json:"groupID"`
	Root     bool   `json:"root"`
	Mail     string `json:"mail"`
}

func (p *UserReqParams) Verify(ctx iris.Context) error {
	return nil
}

type LoginReqParams struct {
	Username string `json:"username" rule:"required,请输入用户名"`
	Passwd   string `json:"passwd" rule:"required,请输入密码"`
	Remember bool   `json:"remember"`
}

func (p *LoginReqParams) Verify(ctx iris.Context) error {
	return nil
}

type PageReqParams struct {
	Page     int `json:"page"`
	Pagesize int `json:"pagesize"`
}

type GetNodeListReqParams struct {
	PageReqParams
	QueryGroupID uint `json:"queryGroupID"`
}

func (p *GetNodeListReqParams) Verify(ctx iris.Context) error {

	if p.Page == 0 {
		p.Page = 1
	}
	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type EditGroupReqParams struct {
	GroupID   uint   `json:"groupID"`
	GroupName string `json:"groupName"`
}

func (p *EditGroupReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.GroupName == "" {
		return paramsError
	}
	return nil
}

type SetGroupReqParams struct {
	TargetGroupID   uint   `json:"targetGroupID"`
	TargetGroupName string `json:"targetGroupName"`
	UserID          uint   `json:"userID" rule:"required,请填写用户ID"`
	Root            bool   `json:"root"`
}

func (p *SetGroupReqParams) Verify(ctx iris.Context) error {
	return nil
}

type ReadMoreReqParams struct {
	LastID   int    `json:"lastID"`
	Pagesize uint   `json:"pagesize"`
	Orderby  string `json:"orderby"`
}

func (p *ReadMoreReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil {
		return paramsError
	}

	if p.Pagesize == 0 {
		p.Pagesize = 50
	}

	if p.Orderby == "" {
		p.Orderby = "desc"
	}

	return nil
}

type GroupNodeReqParams struct {
	Addr            string `json:"addr"`
	TargetNodeName  string `json:"targetNodeName"`
	TargetGroupName string `json:"targetGroupName"`
	TargetGroupID   uint   `json:"targetGroupID"`
}

func (p *GroupNodeReqParams) Verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" ||
		(p.TargetGroupID == 0 && p.TargetGroupName == "") {
		return paramsError
	}
	return nil
}

type AuditJobReqParams struct {
	JobsReqParams
	JobType string `json:"jobType"`
}

func (p *AuditJobReqParams) verify(ctx iris.Context) error {

	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	log.Debugf("%+v", p)

	jobTypeMap := map[string]bool{
		"crontab": true,
		"daemon":  true,
	}

	if err := p.JobsReqParams.verify(nil); err != nil {
		return err
	}

	if jobTypeMap[p.JobType] == false {
		return paramsError
	}

	return nil
}

type GetUsersParams struct {
	PageReqParams
	IsAll        bool `json:"isAll"`
	QueryGroupID uint `json:"queryGroupID"`
}

func (p *GetUsersParams) Verify(ctx iris.Context) error {

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}
<commit_msg>update params<commit_after>package admin

import (
	"errors"
	"fmt"
	"jiacrontab/models"
	"jiacrontab/pkg/proto"

	"github.com/iwannay/log"

	"github.com/kataras/iris"
)

var (
	paramsError = errors.New("参数错误")
)

type Parameter interface {
	Verify(iris.Context) error
}

type JobReqParams struct {
	JobID uint   `json:"jobID"`
	Addr  string `json:"addr"`
}

func (p *JobReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.JobID == 0 || p.Addr == "" {
		return paramsError
	}
	return nil
}

type JobsReqParams struct {
	JobIDs []uint `json:"jobIDs"`
	Addr   string `json:"addr"`
}

func (p *JobsReqParams) verify(ctx iris.Context) error {
	if ctx != nil {
		if err := ctx.ReadJSON(p); err != nil {
			return paramsError
		}
	}

	if len(p.JobIDs) == 0 || p.Addr == "" {
		return paramsError
	}
	return nil
}

type EditJobReqParams struct {
	JobID           uint              `json:"jobID"`
	Addr            string            `json:"addr"`
	IsSync          bool              `json:"isSync"`
	Name            string            `json:"name"`
	Commands        [][]string        `json:"commands"`
	Timeout         int               `json:"timeout"`
	MaxConcurrent   uint              `json:"maxConcurrent"`
	ErrorMailNotify bool              `json:"errorMailNotify"`
	ErrorAPINotify  bool              `json:"errorAPINotify"`
	MailTo          []string          `json:"mailTo"`
	APITo           []string          `json:"APITo"`
	RetryNum        int               `json:"retryNum"`
	WorkDir         string            `json:"workDir"`
	WorkUser        string            `json:"workUser"`
	WorkEnv         []string          `json:"workEnv"`
	DependJobs      models.DependJobs `json:"dependJobs"`
	Month           string            `json:"month"`
	Weekday         string            `json:"weekday"`
	Day             string            `json:"day"`
	Hour            string            `json:"hour"`
	Minute          string            `json:"minute"`
	Second          string            `json:"second"`
	TimeoutTrigger  []string          `json:"timeoutTrigger"`
}

// TODO:验证参数
func (p *EditJobReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return err
	}
	ts := map[string]bool{
		proto.TimeoutTrigger_CallApi:   true,
		proto.TimeoutTrigger_SendEmail: true,
		proto.TimeoutTrigger_Kill:      true,
	}

	for _, v := range p.TimeoutTrigger {
		if !ts[v] {
			return fmt.Errorf("%s:%v", v, paramsError)
		}
	}

	if p.Month == "" {
		p.Month = "*"
	}

	if p.Weekday == "" {
		p.Weekday = "*"
	}

	if p.Day == "" {
		p.Day = "*"
	}

	if p.Hour == "" {
		p.Hour = "*"
	}

	if p.Minute == "" {
		p.Minute = "*"
	}

	if p.Second == "" {
		p.Second = "*"
	}

	return nil
}

type GetLogReqParams struct {
	Addr     string `json:"addr"`
	JobID    uint   `json:"jobID"`
	Date     string `json:"date"`
	Pattern  string `json:"pattern"`
	IsTail   bool   `json:"isTail"`
	Page     int    `json:"page"`
	Pagesize int    `json:"pagesize"`
}

func (p *GetLogReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	if p.Page == 0 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}

	return nil
}

type DeleteNodeReqParams struct {
	Addr    string `json:"addr" rule:"required,请填写addr"`
	GroupID uint   `json:"groupID"`
}

func (p *DeleteNodeReqParams) Verify(ctx iris.Context) error {
	return nil
}

type SendTestMailReqParams struct {
	MailTo string `json:"mailTo"`
}

func (p *SendTestMailReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.MailTo == "" {
		return paramsError
	}
	return nil
}

type SystemInfoReqParams struct {
	Addr string `json:"addr"`
}

func (p *SystemInfoReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}
	return nil
}

type GetJobListReqParams struct {
	Addr string `json:"addr"`
	PageReqParams
}

func (p *GetJobListReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type GetGroupListReqParams struct {
	PageReqParams
}

func (p *GetGroupListReqParams) Verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil {
		return paramsError
	}

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type ActionTaskReqParams struct {
	Action string `json:"action"`
	Addr   string `json:"addr"`
	JobIDs []uint `json:"jobIDs"`
}

func (p *ActionTaskReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" ||
		p.Action == "" || len(p.JobIDs) == 0 {
		return paramsError
	}
	return nil
}

type EditDaemonJobReqParams struct {
	Addr            string   `json:"addr" rule:"required,请填写addr"`
	JobID           uint     `json:"jobID"`
	Name            string   `json:"name" rule:"required,请填写name"`
	MailTo          []string `json:"mailTo"`
	APITo           []string `json:"APITo"`
	Commands        []string `json:"commands"  rule:"required,请填写commands"`
	WorkUser        string   `json:"workUser"`
	WorkEnv         []string `json:"workEnv"`
	WorkDir         string   `json:"workDir"`
	FailRestart     bool     `json:"failRestart"`
	ErrorMailNotify bool     `json:"errorMailNotify"`
	ErrorAPINotify  bool     `json:"errorAPINotify"`
}

func (p *EditDaemonJobReqParams) Verify(ctx iris.Context) error {
	return nil
}

type GetJobReqParams struct {
	JobID uint   `json:"jobID" rule:"required,请填写jobID"`
	Addr  string `json:"addr" rule:"required,请填写addr"`
}

func (p *GetJobReqParams) Verify(ctx iris.Context) error {
	return nil
}

type UserReqParams struct {
	Username string `json:"username" rule:"required,请输入用户名"`
	Passwd   string `json:"passwd" rule:"required,请输入密码"`
	GroupID  uint   `json:"groupID"`
	Root     bool   `json:"root"`
	Mail     string `json:"mail"`
}

func (p *UserReqParams) Verify(ctx iris.Context) error {
	return nil
}

type LoginReqParams struct {
	Username string `json:"username" rule:"required,请输入用户名"`
	Passwd   string `json:"passwd" rule:"required,请输入密码"`
	Remember bool   `json:"remember"`
}

func (p *LoginReqParams) Verify(ctx iris.Context) error {
	return nil
}

type PageReqParams struct {
	Page     int `json:"page"`
	Pagesize int `json:"pagesize"`
}

type GetNodeListReqParams struct {
	PageReqParams
	QueryGroupID uint `json:"queryGroupID"`
}

func (p *GetNodeListReqParams) Verify(ctx iris.Context) error {

	if p.Page == 0 {
		p.Page = 1
	}
	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}

type EditGroupReqParams struct {
	GroupID   uint   `json:"groupID"`
	GroupName string `json:"groupName"`
}

func (p *EditGroupReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil || p.GroupName == "" {
		return paramsError
	}
	return nil
}

type SetGroupReqParams struct {
	TargetGroupID   uint   `json:"targetGroupID"`
	TargetGroupName string `json:"targetGroupName"`
	UserID          uint   `json:"userID" rule:"required,请填写用户ID"`
	Root            bool   `json:"root"`
}

func (p *SetGroupReqParams) Verify(ctx iris.Context) error {
	return nil
}

type ReadMoreReqParams struct {
	LastID   int    `json:"lastID"`
	Pagesize uint   `json:"pagesize"`
	Orderby  string `json:"orderby"`
}

func (p *ReadMoreReqParams) verify(ctx iris.Context) error {
	if err := ctx.ReadJSON(p); err != nil {
		return paramsError
	}

	if p.Pagesize == 0 {
		p.Pagesize = 50
	}

	if p.Orderby == "" {
		p.Orderby = "desc"
	}

	return nil
}

type GroupNodeReqParams struct {
	Addr            string `json:"addr"`
	TargetNodeName  string `json:"targetNodeName"`
	TargetGroupName string `json:"targetGroupName"`
	TargetGroupID   uint   `json:"targetGroupID"`
}

func (p *GroupNodeReqParams) Verify(ctx iris.Context) error {
	return nil
}

type AuditJobReqParams struct {
	JobsReqParams
	JobType string `json:"jobType"`
}

func (p *AuditJobReqParams) verify(ctx iris.Context) error {

	if err := ctx.ReadJSON(p); err != nil || p.Addr == "" {
		return paramsError
	}

	log.Debugf("%+v", p)

	jobTypeMap := map[string]bool{
		"crontab": true,
		"daemon":  true,
	}

	if err := p.JobsReqParams.verify(nil); err != nil {
		return err
	}

	if jobTypeMap[p.JobType] == false {
		return paramsError
	}

	return nil
}

type GetUsersParams struct {
	PageReqParams
	IsAll        bool `json:"isAll"`
	QueryGroupID uint `json:"queryGroupID"`
}

func (p *GetUsersParams) Verify(ctx iris.Context) error {

	if p.Page <= 1 {
		p.Page = 1
	}

	if p.Pagesize <= 0 {
		p.Pagesize = 50
	}
	return nil
}
<|endoftext|>


<commit_before>package sgsimulator

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

type SGSimulator struct{}

type BulkDocsResponse struct {
	Name    string
	Hobbies []string
}

func HomeHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("HomeHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator\n"))
}

func DoNothingHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("DoNothingHandler called with req: %+v", req)
	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func BulkDocsHandler(w http.ResponseWriter, req *http.Request) {
	log.Printf("BulkDocsHandler called with req: %+v", req)

	bulkDocsResponseSlice := []map[string]string{}
	bulkDocResponse := map[string]string{
		"id":  "1",
		"rev": "1-34243",
	}
	bulkDocsResponseSlice = append(bulkDocsResponseSlice, bulkDocResponse)
	js, err := json.Marshal(bulkDocsResponseSlice)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)

	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func NewSGSimulator() *SGSimulator {
	return &SGSimulator{}
}

func (sg *SGSimulator) Run() {

	// TODO: parameterize via CLI
	dbName := "db"
	port := 8000

	r := mux.NewRouter()
	r.HandleFunc("/", DoNothingHandler)
	dbRouter := r.PathPrefix(fmt.Sprintf("/%v", dbName)).Subrouter()
	dbRouter.Path("/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_user/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_bulk_docs").HandlerFunc(BulkDocsHandler)

	http.Handle("/", r)

	srv := &http.Server{
		Handler:      r,
		Addr:         fmt.Sprintf("127.0.0.1:%d", port),
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Printf("Listening on %v", srv.Addr)

	log.Fatal(srv.ListenAndServe())

}
<commit_msg>Remove logging<commit_after>package sgsimulator

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

type SGSimulator struct{}

type BulkDocsResponse struct {
	Name    string
	Hobbies []string
}

func HomeHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("Sync Gateway Simulator\n"))
}

func DoNothingHandler(w http.ResponseWriter, req *http.Request) {
	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func BulkDocsHandler(w http.ResponseWriter, req *http.Request) {
	bulkDocsResponseSlice := []map[string]string{}
	bulkDocResponse := map[string]string{
		"id":  "1",
		"rev": "1-34243",
	}
	bulkDocsResponseSlice = append(bulkDocsResponseSlice, bulkDocResponse)
	js, err := json.Marshal(bulkDocsResponseSlice)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)

	w.Write([]byte("Sync Gateway Simulator DB\n"))
}

func NewSGSimulator() *SGSimulator {
	return &SGSimulator{}
}

func (sg *SGSimulator) Run() {

	// TODO: parameterize via CLI
	dbName := "db"
	port := 8000

	r := mux.NewRouter()
	r.HandleFunc("/", DoNothingHandler)
	dbRouter := r.PathPrefix(fmt.Sprintf("/%v", dbName)).Subrouter()
	dbRouter.Path("/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_user/").HandlerFunc(DoNothingHandler)
	dbRouter.Path("/_bulk_docs").HandlerFunc(BulkDocsHandler)

	http.Handle("/", r)

	srv := &http.Server{
		Handler:      r,
		Addr:         fmt.Sprintf("127.0.0.1:%d", port),
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Printf("Listening on %v", srv.Addr)

	log.Fatal(srv.ListenAndServe())

}
<|endoftext|>


<commit_before>package data_types

type SoftLayer_Location struct {
	Id       int    `json:"id"`
	LongName string `json:"longName"`
	name     string `json:"name"`
}
<commit_msg>export name since it needs to be JSON parseable<commit_after>package data_types

type SoftLayer_Location struct {
	Id       int    `json:"id"`
	LongName string `json:"longName"`
	Name     string `json:"name"`
}
<|endoftext|>


<commit_before>// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
//
// Note:
// 1. It needs manually import: _ "github.com/denisenkom/go-mssqldb"
// 2. It does not support Save/Replace features.
// 3. It does not support LastInsertId.

package gdb

import (
	"database/sql"
	"errors"
	"fmt"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/text/gstr"
	"strconv"
	"strings"

	"github.com/gogf/gf/text/gregex"
)

// DriverMssql is the driver for SQL server database.
type DriverMssql struct {
	*Core
}

// New creates and returns a database object for SQL server.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
	return &DriverMssql{
		Core: core,
	}, nil
}

// Open creates and returns a underlying sql.DB object for mssql.
func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
	source := ""
	if config.LinkInfo != "" {
		source = config.LinkInfo
	} else {
		source = fmt.Sprintf(
			"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
			config.User, config.Pass, config.Host, config.Port, config.Name,
		)
	}
	intlog.Printf("Open: %s", source)
	if db, err := sql.Open("sqlserver", source); err == nil {
		return db, nil
	} else {
		return nil, err
	}
}

// GetChars returns the security char for this type of database.
func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
	return "\"", "\""
}

// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
	var index int
	// Convert place holder char '?' to string "@px".
	str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
		index++
		return fmt.Sprintf("@p%d", index)
	})
	str, _ = gregex.ReplaceString("\"", "", str)
	return d.parseSql(str), args
}

// parseSql does some replacement of the sql before commits it to underlying driver,
// for support of microsoft sql server.
func (d *DriverMssql) parseSql(sql string) string {
	// SELECT * FROM USER WHERE ID=1 LIMIT 1
	if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 {
		return fmt.Sprintf(`SELECT TOP 1 %s`, m[1])
	}
	// SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
	patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
	if gregex.IsMatchString(patten, sql) == false {
		return sql
	}
	res, err := gregex.MatchAllString(patten, sql)
	if err != nil {
		return ""
	}
	index := 0
	keyword := strings.TrimSpace(res[index][0])
	keyword = strings.ToUpper(keyword)
	index++
	switch keyword {
	case "SELECT":
		// LIMIT statement checks.
		if len(res) < 2 ||
			(strings.HasPrefix(res[index][0], "LIMIT") == false &&
				strings.HasPrefix(res[index][0], "limit") == false) {
			break
		}
		if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
			break
		}
		// ORDER BY statement checks.
		selectStr := ""
		orderStr := ""
		haveOrder := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
		if haveOrder {
			queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
			if len(queryExpr) != 4 ||
				strings.EqualFold(queryExpr[1], "SELECT") == false ||
				strings.EqualFold(queryExpr[3], "ORDER BY") == false {
				break
			}
			selectStr = queryExpr[2]
			orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
			if len(orderExpr) != 4 ||
				strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
				strings.EqualFold(orderExpr[3], "LIMIT") == false {
				break
			}
			orderStr = orderExpr[2]
		} else {
			queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
			if len(queryExpr) != 4 ||
				strings.EqualFold(queryExpr[1], "SELECT") == false ||
				strings.EqualFold(queryExpr[3], "LIMIT") == false {
				break
			}
			selectStr = queryExpr[2]
		}
		first, limit := 0, 0
		for i := 1; i < len(res[index]); i++ {
			if len(strings.TrimSpace(res[index][i])) == 0 {
				continue
			}

			if strings.HasPrefix(res[index][i], "LIMIT") ||
				strings.HasPrefix(res[index][i], "limit") {
				first, _ = strconv.Atoi(res[index][i+1])
				limit, _ = strconv.Atoi(res[index][i+2])
				break
			}
		}
		if haveOrder {
			sql = fmt.Sprintf(
				"SELECT * FROM "+
					"(SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ "+
					"WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d",
				orderStr, selectStr, first, first+limit,
			)
		} else {
			if first == 0 {
				first = limit
			}
			sql = fmt.Sprintf(
				"SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ",
				limit, first+limit, selectStr,
			)
		}
	default:
	}
	return sql
}

// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
	var result Result
	link, err := d.DB.GetSlave(schema...)
	if err != nil {
		return nil, err
	}

	result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
	if err != nil {
		return
	}
	for _, m := range result {
		for _, v := range m {
			tables = append(tables, v.String())
		}
	}
	return
}

// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
	charL, charR := d.GetChars()
	table = gstr.Trim(table, charL+charR)
	if gstr.Contains(table, " ") {
		return nil, errors.New("function TableFields supports only single table operations")
	}
	checkSchema := d.DB.GetSchema()
	if len(schema) > 0 && schema[0] != "" {
		checkSchema = schema[0]
	}
	v := d.DB.GetCache().GetOrSetFunc(
		fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
			var result Result
			var link *sql.DB
			link, err = d.DB.GetSlave(checkSchema)
			if err != nil {
				return nil
			}
			result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
			SELECT c.name as FIELD, CASE t.name
				WHEN 'numeric' THEN t.name + '(' + convert(varchar(20),c.xprec) + ',' + convert(varchar(20),c.xscale) + ')'
				WHEN 'char' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
				WHEN 'varchar' THEN t.name + '(' + convert(varchar(20),c.length)+ ')'
				ELSE t.name + '(' + convert(varchar(20),c.length)+ ')' END as TYPE
			FROM systypes t,syscolumns c WHERE t.xtype=c.xtype
			AND c.id = (SELECT id FROM sysobjects WHERE name='%s')
			ORDER BY c.colid`, strings.ToUpper(table)))
			if err != nil {
				return nil
			}
			fields = make(map[string]*TableField)
			for i, m := range result {
				fields[strings.ToLower(m["FIELD"].String())] = &TableField{
					Index: i,
					Name:  strings.ToLower(m["FIELD"].String()),
					Type:  strings.ToLower(m["TYPE"].String()),
				}
			}
			return fields
		}, 0)
	if err == nil {
		fields = v.(map[string]*TableField)
	}
	return
}
<commit_msg>improve mssql TableFields func<commit_after>// Copyright 2017 gf Author(https://github.com/gogf/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://github.com/gogf/gf.
//
// Note:
// 1. It needs manually import: _ "github.com/denisenkom/go-mssqldb"
// 2. It does not support Save/Replace features.
// 3. It does not support LastInsertId.

package gdb

import (
	"database/sql"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/text/gstr"

	"github.com/gogf/gf/text/gregex"
)

// DriverMssql is the driver for SQL server database.
type DriverMssql struct {
	*Core
}

// New creates and returns a database object for SQL server.
// It implements the interface of gdb.Driver for extra database driver installation.
func (d *DriverMssql) New(core *Core, node *ConfigNode) (DB, error) {
	return &DriverMssql{
		Core: core,
	}, nil
}

// Open creates and returns a underlying sql.DB object for mssql.
func (d *DriverMssql) Open(config *ConfigNode) (*sql.DB, error) {
	source := ""
	if config.LinkInfo != "" {
		source = config.LinkInfo
	} else {
		source = fmt.Sprintf(
			"user id=%s;password=%s;server=%s;port=%s;database=%s;encrypt=disable",
			config.User, config.Pass, config.Host, config.Port, config.Name,
		)
	}
	intlog.Printf("Open: %s", source)
	if db, err := sql.Open("sqlserver", source); err == nil {
		return db, nil
	} else {
		return nil, err
	}
}

// GetChars returns the security char for this type of database.
func (d *DriverMssql) GetChars() (charLeft string, charRight string) {
	return "\"", "\""
}

// HandleSqlBeforeCommit deals with the sql string before commits it to underlying sql driver.
func (d *DriverMssql) HandleSqlBeforeCommit(link Link, sql string, args []interface{}) (string, []interface{}) {
	var index int
	// Convert place holder char '?' to string "@px".
	str, _ := gregex.ReplaceStringFunc("\\?", sql, func(s string) string {
		index++
		return fmt.Sprintf("@p%d", index)
	})
	str, _ = gregex.ReplaceString("\"", "", str)
	return d.parseSql(str), args
}

// parseSql does some replacement of the sql before commits it to underlying driver,
// for support of microsoft sql server.
func (d *DriverMssql) parseSql(sql string) string {
	// SELECT * FROM USER WHERE ID=1 LIMIT 1
	if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 {
		return fmt.Sprintf(`SELECT TOP 1 %s`, m[1])
	}
	// SELECT * FROM USER WHERE AGE>18 ORDER BY ID DESC LIMIT 100, 200
	patten := `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,\s*(\d+))`
	if gregex.IsMatchString(patten, sql) == false {
		return sql
	}
	res, err := gregex.MatchAllString(patten, sql)
	if err != nil {
		return ""
	}
	index := 0
	keyword := strings.TrimSpace(res[index][0])
	keyword = strings.ToUpper(keyword)
	index++
	switch keyword {
	case "SELECT":
		// LIMIT statement checks.
		if len(res) < 2 ||
			(strings.HasPrefix(res[index][0], "LIMIT") == false &&
				strings.HasPrefix(res[index][0], "limit") == false) {
			break
		}
		if gregex.IsMatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql) == false {
			break
		}
		// ORDER BY statement checks.
		selectStr := ""
		orderStr := ""
		haveOrder := gregex.IsMatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
		if haveOrder {
			queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)ORDER BY)", sql)
			if len(queryExpr) != 4 ||
				strings.EqualFold(queryExpr[1], "SELECT") == false ||
				strings.EqualFold(queryExpr[3], "ORDER BY") == false {
				break
			}
			selectStr = queryExpr[2]
			orderExpr, _ := gregex.MatchString("((?i)ORDER BY)(.+)((?i)LIMIT)", sql)
			if len(orderExpr) != 4 ||
				strings.EqualFold(orderExpr[1], "ORDER BY") == false ||
				strings.EqualFold(orderExpr[3], "LIMIT") == false {
				break
			}
			orderStr = orderExpr[2]
		} else {
			queryExpr, _ := gregex.MatchString("((?i)SELECT)(.+)((?i)LIMIT)", sql)
			if len(queryExpr) != 4 ||
				strings.EqualFold(queryExpr[1], "SELECT") == false ||
				strings.EqualFold(queryExpr[3], "LIMIT") == false {
				break
			}
			selectStr = queryExpr[2]
		}
		first, limit := 0, 0
		for i := 1; i < len(res[index]); i++ {
			if len(strings.TrimSpace(res[index][i])) == 0 {
				continue
			}

			if strings.HasPrefix(res[index][i], "LIMIT") ||
				strings.HasPrefix(res[index][i], "limit") {
				first, _ = strconv.Atoi(res[index][i+1])
				limit, _ = strconv.Atoi(res[index][i+2])
				break
			}
		}
		if haveOrder {
			sql = fmt.Sprintf(
				"SELECT * FROM "+
					"(SELECT ROW_NUMBER() OVER (ORDER BY %s) as ROWNUMBER_, %s ) as TMP_ "+
					"WHERE TMP_.ROWNUMBER_ > %d AND TMP_.ROWNUMBER_ <= %d",
				orderStr, selectStr, first, first+limit,
			)
		} else {
			if first == 0 {
				first = limit
			}
			sql = fmt.Sprintf(
				"SELECT * FROM (SELECT TOP %d * FROM (SELECT TOP %d %s) as TMP1_ ) as TMP2_ ",
				limit, first+limit, selectStr,
			)
		}
	default:
	}
	return sql
}

// Tables retrieves and returns the tables of current schema.
// It's mainly used in cli tool chain for automatically generating the models.
func (d *DriverMssql) Tables(schema ...string) (tables []string, err error) {
	var result Result
	link, err := d.DB.GetSlave(schema...)
	if err != nil {
		return nil, err
	}

	result, err = d.DB.DoGetAll(link, `SELECT NAME FROM SYSOBJECTS WHERE XTYPE='U' AND STATUS >= 0 ORDER BY NAME`)
	if err != nil {
		return
	}
	for _, m := range result {
		for _, v := range m {
			tables = append(tables, v.String())
		}
	}
	return
}

// TableFields retrieves and returns the fields information of specified table of current schema.
func (d *DriverMssql) TableFields(table string, schema ...string) (fields map[string]*TableField, err error) {
	charL, charR := d.GetChars()
	table = gstr.Trim(table, charL+charR)
	if gstr.Contains(table, " ") {
		return nil, errors.New("function TableFields supports only single table operations")
	}
	checkSchema := d.DB.GetSchema()
	if len(schema) > 0 && schema[0] != "" {
		checkSchema = schema[0]
	}
	v := d.DB.GetCache().GetOrSetFunc(
		fmt.Sprintf(`mssql_table_fields_%s_%s`, table, checkSchema), func() interface{} {
			var result Result
			var link *sql.DB
			link, err = d.DB.GetSlave(checkSchema)
			if err != nil {
				return nil
			}
			result, err = d.DB.DoGetAll(link, fmt.Sprintf(`
			SELECT a.name Field,
	CASE b.name
		WHEN 'datetime' THEN 'datetime'
		WHEN 'numeric' THEN b.name + '(' + convert(varchar(20),a.xprec) + ',' + convert(varchar(20),a.xscale) + ')'
		WHEN 'char' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
		WHEN 'varchar' THEN b.name + '(' + convert(varchar(20),a.length)+ ')'
		ELSE b.name + '(' + convert(varchar(20),a.length)+ ')' END as TYPE,
	case when a.isnullable=1 then 'YES'else 'NO' end as [Null],
	case when exists(SELECT 1 FROM sysobjects where xtype='PK' and name in (
	  SELECT name FROM sysindexes WHERE indid in(
	   SELECT indid FROM sysindexkeys WHERE id = a.id AND colid=a.colid
	   ))) then 'PRI' else '' end AS [Key],
	case when COLUMNPROPERTY(a.id,a.name,'IsIdentity')=1 then 'auto_increment'else '' end Extra,
	isnull(e.text,'') as [Default],
	isnull(g.[value],'') AS [Comment]
	FROM syscolumns a
	left join systypes b on a.xtype=b.xusertype
	inner join sysobjects d on a.id=d.id and d.xtype='U' and d.name<>'dtproperties'
	left join syscomments e on a.cdefault=e.id
	left join sys.extended_properties g on a.id=g.major_id and a.colid=g.minor_id
	left join sys.extended_properties f on d.id=f.major_id and f.minor_id =0
	where d.name='%s'
	order by a.id,a.colorder`, strings.ToUpper(table)))
			if err != nil {
				return nil
			}
			fields = make(map[string]*TableField)
			for i, m := range result {
				fields[strings.ToLower(m["FIELD"].String())] = &TableField{
					Index:   i,
					Name:    strings.ToLower(m["FIELD"].String()),
					Type:    strings.ToLower(m["TYPE"].String()),
					Null:    m["Null"].Bool(),
					Key:     m["Key"].String(),
					Default: m["Default"].Val(),
					Extra:   m["Extra"].String(),
					Comment: m["Comment"].String(),
				}
			}
			return fields
		}, 0)
	if err == nil {
		fields = v.(map[string]*TableField)
	}
	return
}
<|endoftext|>


<commit_before>// Package chunker provides a way to chunk an input into uploadable-size byte slices.
package chunker

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
	"github.com/bazelbuild/remote-apis-sdks/go/pkg/reader"
	"github.com/golang/protobuf/proto"
)

// DefaultChunkSize is the default chunk size for ByteStream.Write RPCs.
const DefaultChunkSize = 1024 * 1024

// IOBufferSize regulates how many bytes at a time the Chunker will read from a file source.
var IOBufferSize = 10 * 1024 * 1024

// ErrEOF is returned when Next is called when HasNext is false.
var ErrEOF = errors.New("ErrEOF")

// Chunker can be used to chunk an input into uploadable-size byte slices.
// A single Chunker is NOT thread-safe; it should be used by a single uploader thread.
type Chunker struct {
	chunkSize int
	r         reader.ReadSeeker
	// An optional cache of the full data. It will be present in these cases:
	// * The Chunker was initialized from a []byte.
	// * Chunker.FullData was called at least once.
	// * Next() was called and the read was less than IOBufferSize.
	// Once contents are initialized, they are immutable.
	contents []byte
	// The digest carried here is for easy of data access *only*. It is never
	// checked anywhere in the Chunker logic.
	digest     digest.Digest
	offset     int64
	reachedEOF bool
	path       string
}

// NewFromBlob initializes a Chunker from the provided bytes buffer.
func NewFromBlob(blob []byte, chunkSize int) *Chunker {
	if chunkSize < 1 {
		chunkSize = DefaultChunkSize
	}
	contents := make([]byte, len(blob))
	copy(contents, blob)
	return &Chunker{
		contents:  contents,
		chunkSize: chunkSize,
		digest:    digest.NewFromBlob(blob),
	}
}

// NewFromFile initializes a Chunker from the provided file.
// The provided Digest does NOT have to match the contents of the file! It is
// for informational purposes only. Chunker won't check Digest information at
// any time. This means that late errors due to mismatch of digest and
// contents are possible.
func NewFromFile(path string, dg digest.Digest, chunkSize int) *Chunker {
	if chunkSize < 1 {
		chunkSize = DefaultChunkSize
	}
	if chunkSize > IOBufferSize {
		chunkSize = IOBufferSize
	}
	return &Chunker{
		r:         reader.NewFileReadSeeker(path, IOBufferSize),
		chunkSize: chunkSize,
		digest:    dg,
		path:      path,
	}
}

// NewFromProto initializes a Chunker from the marshalled proto message.
func NewFromProto(msg proto.Message, chunkSize int) (*Chunker, error) {
	blob, err := proto.Marshal(msg)
	if err != nil {
		return nil, err
	}
	return NewFromBlob(blob, chunkSize), nil
}

// String returns an identifiable representation of the Chunker.
func (c *Chunker) String() string {
	size := fmt.Sprintf("<%d bytes>", c.digest.Size)
	if c.path == "" {
		return size
	}
	return fmt.Sprintf("%s: %s", size, c.path)
}

// Digest returns the digest of the full data of this chunker.
func (c *Chunker) Digest() digest.Digest {
	return c.digest
}

// Offset returns the current Chunker offset.
func (c *Chunker) Offset() int64 {
	return c.offset
}

// ChunkSize returns the maximum size of each chunk.
func (c *Chunker) ChunkSize() int {
	return c.chunkSize
}

// Reset the Chunker state to when it was newly constructed.
// Useful for upload retries.
// TODO(olaola): implement Seek(offset) when we have resumable uploads.
func (c *Chunker) Reset() {
	if c.r != nil {
		c.r.SeekOffset(0)
	}
	c.offset = 0
	c.reachedEOF = false
}

// FullData returns the overall (non-chunked) underlying data. The Chunker is Reset.
// It is supposed to be used for batch uploading small inputs.
func (c *Chunker) FullData() ([]byte, error) {
	c.Reset()
	if c.contents != nil {
		return c.contents, nil
	}
	var err error
	if !c.r.IsInitialized() {
		err = c.r.Initialize()
	}
	if err != nil {
		return nil, err
	}
	// Cache contents so that the next call to FullData() doesn't result in file read.
	c.contents, err = ioutil.ReadAll(c.r)
	return c.contents, err
}

// HasNext returns whether a subsequent call to Next will return a valid chunk. Always true for a
// newly created Chunker.
func (c *Chunker) HasNext() bool {
	return !c.reachedEOF
}

// Chunk is a piece of a byte[] blob suitable for being uploaded.
type Chunk struct {
	Offset int64
	Data   []byte
}

// Next returns the next chunk of data or error. ErrEOF is returned if and only if HasNext is false.
// Chunk.Data will be empty if and only if the full underlying data is empty (in which case it will
// be the only chunk returned). Chunk.Digest will only be filled for the first chunk.
func (c *Chunker) Next() (*Chunk, error) {
	if !c.HasNext() {
		return nil, ErrEOF
	}
	if c.digest.Size == 0 {
		c.reachedEOF = true
		return &Chunk{}, nil
	}

	var data []byte
	if c.contents != nil {
		// As long as we have data in memory, it's much more efficient to return
		// a view slice than to copy it around. Contents are immutable so it's okay
		// to return the slice.
		endRead := int(c.offset) + c.chunkSize
		if endRead >= len(c.contents) {
			endRead = len(c.contents)
			c.reachedEOF = true
		}
		data = c.contents[c.offset:endRead]
	} else {
		if !c.r.IsInitialized() {
			err := c.r.Initialize()
			if err != nil {
				return nil, err
			}
		}

		// We don't need to check the amount of bytes read, as ReadFull will yell if
		// it's diff than len(data).
		data = make([]byte, c.chunkSize)
		n, err := io.ReadFull(c.r, data)
		data = data[:n]
		// Cache the contents to avoid further IO for small files.
		if err == io.ErrUnexpectedEOF || err == io.EOF {
			if c.offset == 0 {
				c.contents = data
			}
			c.reachedEOF = true
		} else if err != nil {
			return nil, err
		}
	}

	res := &Chunk{
		Offset: c.offset,
		Data:   data,
	}
	c.offset += int64(len(data))
	return res, nil
}
<commit_msg>Closing the Chunker file in the end of a read. (#244)<commit_after>// Package chunker provides a way to chunk an input into uploadable-size byte slices.
package chunker

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"

	"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
	"github.com/bazelbuild/remote-apis-sdks/go/pkg/reader"
	"github.com/golang/protobuf/proto"
)

// DefaultChunkSize is the default chunk size for ByteStream.Write RPCs.
const DefaultChunkSize = 1024 * 1024

// IOBufferSize regulates how many bytes at a time the Chunker will read from a file source.
var IOBufferSize = 10 * 1024 * 1024

// ErrEOF is returned when Next is called when HasNext is false.
var ErrEOF = errors.New("ErrEOF")

// Chunker can be used to chunk an input into uploadable-size byte slices.
// A single Chunker is NOT thread-safe; it should be used by a single uploader thread.
type Chunker struct {
	chunkSize int
	r         reader.ReadSeeker
	// An optional cache of the full data. It will be present in these cases:
	// * The Chunker was initialized from a []byte.
	// * Chunker.FullData was called at least once.
	// * Next() was called and the read was less than IOBufferSize.
	// Once contents are initialized, they are immutable.
	contents []byte
	// The digest carried here is for easy of data access *only*. It is never
	// checked anywhere in the Chunker logic.
	digest     digest.Digest
	offset     int64
	reachedEOF bool
	path       string
}

// NewFromBlob initializes a Chunker from the provided bytes buffer.
func NewFromBlob(blob []byte, chunkSize int) *Chunker {
	if chunkSize < 1 {
		chunkSize = DefaultChunkSize
	}
	contents := make([]byte, len(blob))
	copy(contents, blob)
	return &Chunker{
		contents:  contents,
		chunkSize: chunkSize,
		digest:    digest.NewFromBlob(blob),
	}
}

// NewFromFile initializes a Chunker from the provided file.
// The provided Digest does NOT have to match the contents of the file! It is
// for informational purposes only. Chunker won't check Digest information at
// any time. This means that late errors due to mismatch of digest and
// contents are possible.
func NewFromFile(path string, dg digest.Digest, chunkSize int) *Chunker {
	if chunkSize < 1 {
		chunkSize = DefaultChunkSize
	}
	if chunkSize > IOBufferSize {
		chunkSize = IOBufferSize
	}
	return &Chunker{
		r:         reader.NewFileReadSeeker(path, IOBufferSize),
		chunkSize: chunkSize,
		digest:    dg,
		path:      path,
	}
}

// NewFromProto initializes a Chunker from the marshalled proto message.
func NewFromProto(msg proto.Message, chunkSize int) (*Chunker, error) {
	blob, err := proto.Marshal(msg)
	if err != nil {
		return nil, err
	}
	return NewFromBlob(blob, chunkSize), nil
}

// String returns an identifiable representation of the Chunker.
func (c *Chunker) String() string {
	size := fmt.Sprintf("<%d bytes>", c.digest.Size)
	if c.path == "" {
		return size
	}
	return fmt.Sprintf("%s: %s", size, c.path)
}

// Digest returns the digest of the full data of this chunker.
func (c *Chunker) Digest() digest.Digest {
	return c.digest
}

// Offset returns the current Chunker offset.
func (c *Chunker) Offset() int64 {
	return c.offset
}

// ChunkSize returns the maximum size of each chunk.
func (c *Chunker) ChunkSize() int {
	return c.chunkSize
}

// Reset the Chunker state to when it was newly constructed.
// Useful for upload retries.
// TODO(olaola): implement Seek(offset) when we have resumable uploads.
func (c *Chunker) Reset() {
	if c.r != nil {
		c.r.SeekOffset(0)
	}
	c.offset = 0
	c.reachedEOF = false
}

// FullData returns the overall (non-chunked) underlying data. The Chunker is Reset.
// It is supposed to be used for batch uploading small inputs.
func (c *Chunker) FullData() ([]byte, error) {
	c.Reset()
	if c.contents != nil {
		return c.contents, nil
	}
	var err error
	if !c.r.IsInitialized() {
		err = c.r.Initialize()
	}
	if err != nil {
		c.r.Close() // Free file handle in case of error.
		return nil, err
	}
	// Cache contents so that the next call to FullData() doesn't result in file read.
	c.contents, err = ioutil.ReadAll(c.r)
	c.r.Close()
	return c.contents, err
}

// HasNext returns whether a subsequent call to Next will return a valid chunk. Always true for a
// newly created Chunker.
func (c *Chunker) HasNext() bool {
	return !c.reachedEOF
}

// Chunk is a piece of a byte[] blob suitable for being uploaded.
type Chunk struct {
	Offset int64
	Data   []byte
}

// Next returns the next chunk of data or error. ErrEOF is returned if and only if HasNext is false.
// Chunk.Data will be empty if and only if the full underlying data is empty (in which case it will
// be the only chunk returned). Chunk.Digest will only be filled for the first chunk.
func (c *Chunker) Next() (*Chunk, error) {
	if !c.HasNext() {
		return nil, ErrEOF
	}
	if c.digest.Size == 0 {
		c.reachedEOF = true
		return &Chunk{}, nil
	}

	var data []byte
	if c.contents != nil {
		// As long as we have data in memory, it's much more efficient to return
		// a view slice than to copy it around. Contents are immutable so it's okay
		// to return the slice.
		endRead := int(c.offset) + c.chunkSize
		if endRead >= len(c.contents) {
			endRead = len(c.contents)
			c.reachedEOF = true
		}
		data = c.contents[c.offset:endRead]
	} else {
		if !c.r.IsInitialized() {
			err := c.r.Initialize()
			if err != nil {
				return nil, err
			}
		}

		// We don't need to check the amount of bytes read, as ReadFull will yell if
		// it's diff than len(data).
		data = make([]byte, c.chunkSize)
		n, err := io.ReadFull(c.r, data)
		data = data[:n]
		// Cache the contents to avoid further IO for small files.
		if err == io.ErrUnexpectedEOF || err == io.EOF {
			if c.offset == 0 {
				c.contents = data
			}
			c.reachedEOF = true
			c.r.Close()
		} else if err != nil {
			c.r.Close() // Free the file handle in case of error.
			return nil, err
		}
	}

	res := &Chunk{
		Offset: c.offset,
		Data:   data,
	}
	c.offset += int64(len(data))
	return res, nil
}
<|endoftext|>


<commit_before>package scaleway

import "fmt"

// ServersService handles communication with the servers related
// methods of the Scaleway API.
//
// Scaleway API docs: https://developer.scaleway.com/#servers
type ServersService struct {
	client *Client
}

// Server represents a Scaleway server.
type Server struct {
	ID              string             `json:"id,omitempty"`
	BootScript      string             `json:"bootscript,omitempty"`
	DynamicPublicIP bool               `json:"dynamic_public_ip,omitempty"`
	Image           *Image             `json:"image,omitempty"`
	Name            string             `json:"name,omitempty"`
	Organization    string             `json:"organization,omitempty"`
	PrivateIP       string             `json:"private_ip,omitempty"`
	PublicIP        string             `json:"public_ip,omitempty"`
	State           string             `json:"state,omitempty"`
	Tags            []string           `json:"tags,omitempty"`
	Volumes         map[string]*Volume `json:"volumes,omitempty`
}

// ServerRequest represents a request to create a server.
type ServerRequest struct {
	Organization string   `json:"organization"`
	Name         string   `json:"name"`
	Image        string   `json:"image"`
	Tags         []string `json:"tags"`
}

// serverResponse represents a Scaleway server creation response.
type serverResponse struct {
	Server *Server `json:"server"`
}

// serverListResponse represents a Scaleway servers list response.
type serverListResponse struct {
	Servers []*Server `json:"servers"`
}

// Create creates images.
func (s *ServersService) Create(sr *ServerRequest) (*Server, *Response, error) {
	u := fmt.Sprintf("/servers")
	req, err := s.client.NewRequestAccount("POST", u, sr)
	if err != nil {
		return nil, nil, err
	}

	server := new(serverResponse)
	resp, err := s.client.Do(req, server)
	if err != nil {
		return nil, nil, err
	}
	return server.Server, resp, nil
}

// List returns a list of all servers associate to your account.
func (s *ServersService) List() ([]*Server, *Response, error) {
	return s.listServers()
}

func (s *ServersService) listServers() ([]*Server, *Response, error) {
	u := fmt.Sprintf("/servers")
	req, err := s.client.NewRequestAccount("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	servers := new(serverListResponse)
	resp, err := s.client.Do(req, servers)
	if err != nil {
		return nil, nil, err
	}
	return servers.Servers, resp, nil
}
<commit_msg>Fix field tag error<commit_after>package scaleway

import "fmt"

// ServersService handles communication with the servers related
// methods of the Scaleway API.
//
// Scaleway API docs: https://developer.scaleway.com/#servers
type ServersService struct {
	client *Client
}

// Server represents a Scaleway server.
type Server struct {
	ID              string             `json:"id,omitempty"`
	BootScript      string             `json:"bootscript,omitempty"`
	DynamicPublicIP bool               `json:"dynamic_public_ip,omitempty"`
	Image           *Image             `json:"image,omitempty"`
	Name            string             `json:"name,omitempty"`
	Organization    string             `json:"organization,omitempty"`
	PrivateIP       string             `json:"private_ip,omitempty"`
	PublicIP        string             `json:"public_ip,omitempty"`
	State           string             `json:"state,omitempty"`
	Tags            []string           `json:"tags,omitempty"`
	Volumes         map[string]*Volume `json:"volumes,omitempty"`
}

// ServerRequest represents a request to create a server.
type ServerRequest struct {
	Organization string   `json:"organization"`
	Name         string   `json:"name"`
	Image        string   `json:"image"`
	Tags         []string `json:"tags"`
}

// serverResponse represents a Scaleway server creation response.
type serverResponse struct {
	Server *Server `json:"server"`
}

// serverListResponse represents a Scaleway servers list response.
type serverListResponse struct {
	Servers []*Server `json:"servers"`
}

// Create creates images.
func (s *ServersService) Create(sr *ServerRequest) (*Server, *Response, error) {
	u := fmt.Sprintf("/servers")
	req, err := s.client.NewRequestAccount("POST", u, sr)
	if err != nil {
		return nil, nil, err
	}

	server := new(serverResponse)
	resp, err := s.client.Do(req, server)
	if err != nil {
		return nil, nil, err
	}
	return server.Server, resp, nil
}

// List returns a list of all servers associate to your account.
func (s *ServersService) List() ([]*Server, *Response, error) {
	return s.listServers()
}

func (s *ServersService) listServers() ([]*Server, *Response, error) {
	u := fmt.Sprintf("/servers")
	req, err := s.client.NewRequestAccount("GET", u, nil)
	if err != nil {
		return nil, nil, err
	}

	servers := new(serverListResponse)
	resp, err := s.client.Do(req, servers)
	if err != nil {
		return nil, nil, err
	}
	return servers.Servers, resp, nil
}
<|endoftext|>


<commit_before>package virt

import (
	"errors"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"labix.org/v2/mgo/bson"
)

func (vm *VM) Start() error {
	if out, err := exec.Command("/usr/bin/lxc-start", "--name", vm.String(), "--daemon").CombinedOutput(); err != nil {
		return commandError("lxc-start failed.", err, out)
	}
	return vm.WaitForState("RUNNING", time.Second)
}

func (vm *VM) Stop() error {
	if out, err := exec.Command("/usr/bin/lxc-stop", "--name", vm.String()).CombinedOutput(); err != nil {
		return commandError("lxc-stop failed.", err, out)
	}
	return vm.WaitForState("STOPPED", time.Second)
}

func (vm *VM) Shutdown() error {
	if out, err := exec.Command("/usr/bin/lxc-shutdown", "--name", vm.String()).CombinedOutput(); err != nil {
		if vm.GetState() != "STOPPED" {
			return commandError("lxc-shutdown failed.", err, out)
		}
	}
	vm.WaitForState("STOPPED", 5*time.Second) // may time out, then vm is force stopped
	return vm.Stop()
}

func (vm *VM) AttachCommand(uid int, tty string, command ...string) *exec.Cmd {
	args := []string{"--name", vm.String()}
	if tty != "" {
		args = append(args, "--tty", tty)
	}
	args = append(args, "--", "/usr/bin/sudo", "-i", "-u", "#"+strconv.Itoa(uid), "--")
	args = append(args, command...)
	cmd := exec.Command("/usr/bin/lxc-attach", args...)
	cmd.Env = []string{"TERM=xterm-256color"}
	return cmd
}

func GetVMState(vmId bson.ObjectId) string {
	out, err := exec.Command("/usr/bin/lxc-info", "--name", VMName(vmId), "--state").CombinedOutput()
	if err != nil {
		lastError = commandError("lxc-info failed ", err, out)
		return "UNKNOWN"
	}
	return strings.TrimSpace(string(out)[6:])
}

func (vm *VM) GetState() string {
	return GetVMState(vm.Id)
}

func (vm *VM) WaitForState(state string, timeout time.Duration) error {
	tryUntil := time.Now().Add(timeout)
	for vm.GetState() != state {
		if time.Now().After(tryUntil) {
			return errors.New("Timeout while waiting for VM state.")
		}
		time.Sleep(time.Second / 10)
	}
	return nil
}

func (vm *VM) SendMessageToVMUsers(message string) error {
	cmd := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(), "--", "/usr/bin/wall", "--nobanner")
	cmd.Stdin = strings.NewReader(message)
	if out, err := cmd.CombinedOutput(); err != nil {
		return commandError("wall failed.", err, out)
	}
	return nil
}

func (vm *VM) WaitForNetwork() error {
	timeout := time.Second * 5
	isNetworkUp := func() bool {
		// neglect error because it's not important and we also going to timeout
		out, _ := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(),
			"--", "/bin/cat", "/sys/class/net/eth0/operstate").CombinedOutput()

		if strings.TrimSpace(string(out)) == "up" {
			if out, err := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(),
				"--", "/bin/stat", "/usr/bin/screen").CombinedOutput(); err != nil {
				return false
			} else {
				return true
			}
			return false
		}
	}
	tryUntil := time.Now().Add(timeout)
	for {
		if up := isNetworkUp(); up {
			return nil
		}

		if time.Now().After(tryUntil) {
			return errors.New("Timeout while waiting for VM Network state.")
		}

		time.Sleep(time.Millisecond * 100)
	}
}
<commit_msg>Location is usr/bin<commit_after>package virt

import (
	"errors"
	"os/exec"
	"strconv"
	"strings"
	"time"

	"labix.org/v2/mgo/bson"
)

func (vm *VM) Start() error {
	if out, err := exec.Command("/usr/bin/lxc-start", "--name", vm.String(), "--daemon").CombinedOutput(); err != nil {
		return commandError("lxc-start failed.", err, out)
	}
	return vm.WaitForState("RUNNING", time.Second)
}

func (vm *VM) Stop() error {
	if out, err := exec.Command("/usr/bin/lxc-stop", "--name", vm.String()).CombinedOutput(); err != nil {
		return commandError("lxc-stop failed.", err, out)
	}
	return vm.WaitForState("STOPPED", time.Second)
}

func (vm *VM) Shutdown() error {
	if out, err := exec.Command("/usr/bin/lxc-shutdown", "--name", vm.String()).CombinedOutput(); err != nil {
		if vm.GetState() != "STOPPED" {
			return commandError("lxc-shutdown failed.", err, out)
		}
	}
	vm.WaitForState("STOPPED", 5*time.Second) // may time out, then vm is force stopped
	return vm.Stop()
}

func (vm *VM) AttachCommand(uid int, tty string, command ...string) *exec.Cmd {
	args := []string{"--name", vm.String()}
	if tty != "" {
		args = append(args, "--tty", tty)
	}
	args = append(args, "--", "/usr/bin/sudo", "-i", "-u", "#"+strconv.Itoa(uid), "--")
	args = append(args, command...)
	cmd := exec.Command("/usr/bin/lxc-attach", args...)
	cmd.Env = []string{"TERM=xterm-256color"}
	return cmd
}

func GetVMState(vmId bson.ObjectId) string {
	out, err := exec.Command("/usr/bin/lxc-info", "--name", VMName(vmId), "--state").CombinedOutput()
	if err != nil {
		lastError = commandError("lxc-info failed ", err, out)
		return "UNKNOWN"
	}
	return strings.TrimSpace(string(out)[6:])
}

func (vm *VM) GetState() string {
	return GetVMState(vm.Id)
}

func (vm *VM) WaitForState(state string, timeout time.Duration) error {
	tryUntil := time.Now().Add(timeout)
	for vm.GetState() != state {
		if time.Now().After(tryUntil) {
			return errors.New("Timeout while waiting for VM state.")
		}
		time.Sleep(time.Second / 10)
	}
	return nil
}

func (vm *VM) SendMessageToVMUsers(message string) error {
	cmd := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(), "--", "/usr/bin/wall", "--nobanner")
	cmd.Stdin = strings.NewReader(message)
	if out, err := cmd.CombinedOutput(); err != nil {
		return commandError("wall failed.", err, out)
	}
	return nil
}

func (vm *VM) WaitForNetwork() error {
	timeout := time.Second * 5
	isNetworkUp := func() bool {
		// neglect error because it's not important and we also going to timeout
		out, _ := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(),
			"--", "/bin/cat", "/sys/class/net/eth0/operstate").CombinedOutput()

		if strings.TrimSpace(string(out)) == "up" {
			if out, err := exec.Command("/usr/bin/lxc-attach", "--name", vm.String(),
				"--", "/usr/bin/stat", "/usr/bin/screen").CombinedOutput(); err != nil {
				return false
			} else {
				return true
			}
			return false
		}
	}
	tryUntil := time.Now().Add(timeout)
	for {
		if up := isNetworkUp(); up {
			return nil
		}

		if time.Now().After(tryUntil) {
			return errors.New("Timeout while waiting for VM Network state.")
		}

		time.Sleep(time.Millisecond * 100)
	}
}
<|endoftext|>


<commit_before>package watchf

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/parkghost/watchf/config"

	log "github.com/Sirupsen/logrus"
	"golang.org/x/net/context"
	"gopkg.in/fsnotify.v1"
)

type Handler interface {
	Handle(context.Context, fsnotify.Event)
}

type WatchService struct {
	path      string
	recursive bool
	excludeRE *regexp.Regexp

	watcher *fsnotify.Watcher
	flags   fsnotify.Op

	handler  Handler
	ctx      context.Context
	cancelFn context.CancelFunc
}

func New(ctx context.Context, cfg *config.Config, path string, handler Handler) (*WatchService, error) {
	ws := new(WatchService)
	ws.path = path
	ws.recursive = cfg.Recursive
	ws.excludeRE = cfg.ExcludePattern.Regexp
	ws.flags = flags(cfg.Events)
	ws.handler = handler
	ws.ctx, ws.cancelFn = context.WithCancel(ctx)

	return ws, nil
}

const allOps = fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename | fsnotify.Chmod

func flags(events []string) fsnotify.Op {
	var flags fsnotify.Op
	for _, event := range events {
		switch event {
		case "create":
			flags |= fsnotify.Create
		case "write":
			flags |= fsnotify.Write
		case "remove":
			flags |= fsnotify.Remove
		case "rename":
			flags |= fsnotify.Rename
		case "chmod":
			flags |= fsnotify.Chmod
		case "all":
			flags = allOps
		default:
			panic("invalid event:" + event)
		}
	}

	return flags
}

func (ws *WatchService) Start() error {
	log.Debug("starting WatchService")
	select {
	case <-ws.ctx.Done():
		return errors.New("WatchService closed")
	default:
	}

	err := ws.init()
	if err != nil {
		return fmt.Errorf("inital error: %s", err)
	}

	go ws.run()

	return nil
}

func (ws *WatchService) init() error {
	var err error
	ws.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	if ws.recursive {
		err = ws.addSubFolders()
		if err != nil {
			return err
		}
		return nil
	}

	log.Debugf("watching: %s", ws.path)
	err = ws.watcher.Add(ws.path)
	if err != nil {
		return err
	}

	return nil
}

func (ws *WatchService) addSubFolders() error {
	return filepath.Walk(ws.path, func(path string, info os.FileInfo, errPath error) error {
		if info.IsDir() {
			if errPath != nil {
				log.WithField("error", errPath).Debugf("skipped dir %s", path)
				return filepath.SkipDir
			}

			if path != "." && ws.excludeRE.MatchString(path) {
				log.Debugf("skipped dir %s", path)
				return filepath.SkipDir
			}

			log.Debugf("watching: %s", path)
			err := ws.watcher.Add(path)
			if err != nil {
				return err
			}
		}

		return nil
	})
}

func (ws *WatchService) run() {
	for {
		select {
		case <-ws.ctx.Done():
			return
		case evt, ok := <-ws.watcher.Events:
			if !ok {
				return
			}
			ws.dispatch(evt)
		case err := <-ws.watcher.Errors:
			if err != nil {
				log.Fatalf("watcher err: %s", err)
			}
			return
		}
	}
}

func (ws *WatchService) dispatch(evt fsnotify.Event) {
	if ws.flags&evt.Op == 0 {
		log.Debugf("skipped event: %s %s", opName(evt.Op), evt.Name)
		return
	}
	log.Infof("New event: %s %s", opName(evt.Op), evt.Name)

	ws.handler.Handle(ws.ctx, evt)
}

func (ws *WatchService) Stop() error {
	log.Debug("stopping WatchService")
	ws.cancelFn()

	return ws.watcher.Close()
}

type limitedHandler struct {
	includeRE *regexp.Regexp
	excludeRE *regexp.Regexp
	interval  time.Duration
	commands  []string

	nextExec time.Time
}

func (h *limitedHandler) Handle(ctx context.Context, evt fsnotify.Event) {
	if time.Now().Before(h.nextExec) {
		since := time.Now().Sub(h.nextExec.Add(-h.interval))
		log.WithField("since", since).Debugf("limited event: %s %s", opName(evt.Op), evt.Name)
		return
	}

	filename := filepath.Base(evt.Name)
	included := h.includeRE.MatchString(filename)
	log.WithField("match", included).Debugf("check includePattern %s ~= %s ", h.includeRE, filename)
	if !included {
		return
	}

	excluded := h.excludeRE.MatchString(filename)
	log.WithField("match", excluded).Debugf("check excludePattern %s ~= %s ", h.excludeRE, filename)
	if excluded {
		return
	}

	h.runCmd(ctx, evt)

	h.nextExec = time.Now().Add(h.interval)
}

func (h *limitedHandler) runCmd(ctx context.Context, evt fsnotify.Event) {
	actions := make([]Action, 0, len(h.commands))
	for _, cmd := range h.commands {
		actions = append(actions, Action(cmdAction{cmd, evt}))
	}
	log.Debugf("actions: %v", strings.Join(h.commands, " > "))

	runner := &BasicRunner{ctx}
	runner.Run(actions...)
}

func NewLimitedHandler(cfg *config.Config) Handler {
	h := new(limitedHandler)
	h.includeRE = cfg.IncludePattern.Regexp
	h.excludeRE = cfg.ExcludePattern.Regexp
	h.commands = cfg.Commands
	h.interval = cfg.Interval

	return h
}
<commit_msg>export Service interface<commit_after>package watchf

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/parkghost/watchf/config"

	log "github.com/Sirupsen/logrus"
	"golang.org/x/net/context"
	"gopkg.in/fsnotify.v1"
)

type Handler interface {
	Handle(context.Context, fsnotify.Event)
}

type Service interface {
	Start() error
	Stop() error
}

type WatchService struct {
	path      string
	recursive bool
	excludeRE *regexp.Regexp

	watcher *fsnotify.Watcher
	flags   fsnotify.Op

	handler  Handler
	ctx      context.Context
	cancelFn context.CancelFunc
}

func New(ctx context.Context, cfg *config.Config, path string, handler Handler) (Service, error) {
	ws := new(WatchService)
	ws.path = path
	ws.recursive = cfg.Recursive
	ws.excludeRE = cfg.ExcludePattern.Regexp
	ws.flags = flags(cfg.Events)
	ws.handler = handler
	ws.ctx, ws.cancelFn = context.WithCancel(ctx)

	return ws, nil
}

const allOps = fsnotify.Create | fsnotify.Write | fsnotify.Remove | fsnotify.Rename | fsnotify.Chmod

func flags(events []string) fsnotify.Op {
	var flags fsnotify.Op
	for _, event := range events {
		switch event {
		case "create":
			flags |= fsnotify.Create
		case "write":
			flags |= fsnotify.Write
		case "remove":
			flags |= fsnotify.Remove
		case "rename":
			flags |= fsnotify.Rename
		case "chmod":
			flags |= fsnotify.Chmod
		case "all":
			flags = allOps
		default:
			panic("invalid event:" + event)
		}
	}

	return flags
}

func (ws *WatchService) Start() error {
	log.Debug("starting WatchService")
	select {
	case <-ws.ctx.Done():
		return errors.New("WatchService closed")
	default:
	}

	err := ws.init()
	if err != nil {
		return fmt.Errorf("inital error: %s", err)
	}

	go ws.run()

	return nil
}

func (ws *WatchService) init() error {
	var err error
	ws.watcher, err = fsnotify.NewWatcher()
	if err != nil {
		return err
	}

	if ws.recursive {
		err = ws.addSubFolders()
		if err != nil {
			return err
		}
		return nil
	}

	log.Debugf("watching: %s", ws.path)
	err = ws.watcher.Add(ws.path)
	if err != nil {
		return err
	}

	return nil
}

func (ws *WatchService) addSubFolders() error {
	return filepath.Walk(ws.path, func(path string, info os.FileInfo, errPath error) error {
		if info.IsDir() {
			if errPath != nil {
				log.WithField("error", errPath).Debugf("skipped dir %s", path)
				return filepath.SkipDir
			}

			if path != "." && ws.excludeRE.MatchString(path) {
				log.Debugf("skipped dir %s", path)
				return filepath.SkipDir
			}

			log.Debugf("watching: %s", path)
			err := ws.watcher.Add(path)
			if err != nil {
				return err
			}
		}

		return nil
	})
}

func (ws *WatchService) run() {
	for {
		select {
		case <-ws.ctx.Done():
			return
		case evt, ok := <-ws.watcher.Events:
			if !ok {
				return
			}
			ws.dispatch(evt)
		case err := <-ws.watcher.Errors:
			if err != nil {
				log.Fatalf("watcher err: %s", err)
			}
			return
		}
	}
}

func (ws *WatchService) dispatch(evt fsnotify.Event) {
	if ws.flags&evt.Op == 0 {
		log.Debugf("skipped event: %s %s", opName(evt.Op), evt.Name)
		return
	}
	log.Infof("New event: %s %s", opName(evt.Op), evt.Name)

	ws.handler.Handle(ws.ctx, evt)
}

func (ws *WatchService) Stop() error {
	log.Debug("stopping WatchService")
	ws.cancelFn()

	return ws.watcher.Close()
}

type limitedHandler struct {
	includeRE *regexp.Regexp
	excludeRE *regexp.Regexp
	interval  time.Duration
	commands  []string

	nextExec time.Time
}

func (h *limitedHandler) Handle(ctx context.Context, evt fsnotify.Event) {
	if time.Now().Before(h.nextExec) {
		since := time.Now().Sub(h.nextExec.Add(-h.interval))
		log.WithField("since", since).Debugf("limited event: %s %s", opName(evt.Op), evt.Name)
		return
	}

	filename := filepath.Base(evt.Name)
	included := h.includeRE.MatchString(filename)
	log.WithField("match", included).Debugf("check includePattern %s ~= %s ", h.includeRE, filename)
	if !included {
		return
	}

	excluded := h.excludeRE.MatchString(filename)
	log.WithField("match", excluded).Debugf("check excludePattern %s ~= %s ", h.excludeRE, filename)
	if excluded {
		return
	}

	h.runCmd(ctx, evt)

	h.nextExec = time.Now().Add(h.interval)
}

func (h *limitedHandler) runCmd(ctx context.Context, evt fsnotify.Event) {
	actions := make([]Action, 0, len(h.commands))
	for _, cmd := range h.commands {
		actions = append(actions, Action(cmdAction{cmd, evt}))
	}
	log.Debugf("actions: %v", strings.Join(h.commands, " > "))

	runner := &BasicRunner{ctx}
	runner.Run(actions...)
}

func NewLimitedHandler(cfg *config.Config) Handler {
	h := new(limitedHandler)
	h.includeRE = cfg.IncludePattern.Regexp
	h.excludeRE = cfg.ExcludePattern.Regexp
	h.commands = cfg.Commands
	h.interval = cfg.Interval

	return h
}
<|endoftext|>


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

// Package streamlog provides a non-blocking message broadcaster.
package streamlog

import (
	"io"
	"net/http"
	"net/url"
	"sync"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/acl"
	"github.com/youtube/vitess/go/stats"
)

var (
	internalDropCount = stats.NewCounters("StreamlogInternallyDroppedMessages")
	deliveryDropCount = stats.NewMultiCounters("StreamlogDeliveryDroppedMessages", []string{"Log", "Subscriber"})
)

type subscriber struct {
	name string
}

// StreamLogger is a non-blocking broadcaster of messages.
// Subscribers can use channels or HTTP.
type StreamLogger struct {
	name       string
	dataQueue  chan interface{}
	mu         sync.Mutex
	subscribed map[chan interface{}]subscriber
}

// New returns a new StreamLogger with a buffer that can contain size
// messages. Any messages sent to it will be available at url.
func New(name string, size int) *StreamLogger {
	logger := &StreamLogger{
		name:       name,
		dataQueue:  make(chan interface{}, size),
		subscribed: make(map[chan interface{}]subscriber),
	}
	go logger.stream()
	return logger
}

// Send sends message to all the writers subscribed to logger. Calling
// Send does not block.
func (logger *StreamLogger) Send(message interface{}) {
	select {
	case logger.dataQueue <- message:
	default:
		internalDropCount.Add(logger.name, 1)
	}
}

// Subscribe returns a channel which can be used to listen
// for messages.
func (logger *StreamLogger) Subscribe(name string) chan interface{} {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	ch := make(chan interface{}, 1)
	logger.subscribed[ch] = subscriber{name: name}
	return ch
}

// Unsubscribe removes the channel from the subscription.
func (logger *StreamLogger) Unsubscribe(ch chan interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	delete(logger.subscribed, ch)
}

// stream sends messages sent to logger to all of its subscribed
// writers. This method should be called in a goroutine.
func (logger *StreamLogger) stream() {
	for message := range logger.dataQueue {
		logger.transmit(message)
	}
}

func (logger *StreamLogger) transmit(message interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	for ch, sub := range logger.subscribed {
		select {
		case ch <- message:
		default:
			deliveryDropCount.Add([]string{logger.name, sub.name}, 1)
		}
	}
}

// Name returns the name of StreamLogger.
func (logger *StreamLogger) Name() string {
	return logger.name
}

// ServeLogs registers the URL on which messages will be broadcast.
// It is safe to register multiple URLs for the same StreamLogger.
func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) {
	http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
		if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
			acl.SendError(w, err)
			return
		}
		if err := r.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		ch := logger.Subscribe("ServeLogs")
		defer logger.Unsubscribe(ch)

		// Notify client that we're set up. Helpful to distinguish low-traffic streams from connection issues.
		w.WriteHeader(http.StatusOK)
		w.(http.Flusher).Flush()

		for message := range ch {
			if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil {
				return
			}
			w.(http.Flusher).Flush()
		}
	})
	log.Infof("Streaming logs from %s at %v.", logger.Name(), url)
}
<commit_msg>Add positive-result counters to streamlog.<commit_after>// Copyright 2012, Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package streamlog provides a non-blocking message broadcaster.
package streamlog

import (
	"io"
	"net/http"
	"net/url"
	"sync"

	log "github.com/golang/glog"
	"github.com/youtube/vitess/go/acl"
	"github.com/youtube/vitess/go/stats"
)

var (
	sendCount         = stats.NewCounters("StreamlogSend")
	internalDropCount = stats.NewCounters("StreamlogInternallyDroppedMessages")
	deliveredCount    = stats.NewMultiCounters("StreamlogDelivered", []string{"Log", "Subscriber"})
	deliveryDropCount = stats.NewMultiCounters("StreamlogDeliveryDroppedMessages", []string{"Log", "Subscriber"})
)

type subscriber struct {
	name string
}

// StreamLogger is a non-blocking broadcaster of messages.
// Subscribers can use channels or HTTP.
type StreamLogger struct {
	name       string
	dataQueue  chan interface{}
	mu         sync.Mutex
	subscribed map[chan interface{}]subscriber
}

// New returns a new StreamLogger with a buffer that can contain size
// messages. Any messages sent to it will be available at url.
func New(name string, size int) *StreamLogger {
	logger := &StreamLogger{
		name:       name,
		dataQueue:  make(chan interface{}, size),
		subscribed: make(map[chan interface{}]subscriber),
	}
	go logger.stream()
	return logger
}

// Send sends message to all the writers subscribed to logger. Calling
// Send does not block.
func (logger *StreamLogger) Send(message interface{}) {
	sendCount.Add(logger.name, 1)
	select {
	case logger.dataQueue <- message:
	default:
		internalDropCount.Add(logger.name, 1)
	}
}

// Subscribe returns a channel which can be used to listen
// for messages.
func (logger *StreamLogger) Subscribe(name string) chan interface{} {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	ch := make(chan interface{}, 1)
	logger.subscribed[ch] = subscriber{name: name}
	return ch
}

// Unsubscribe removes the channel from the subscription.
func (logger *StreamLogger) Unsubscribe(ch chan interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	delete(logger.subscribed, ch)
}

// stream sends messages sent to logger to all of its subscribed
// writers. This method should be called in a goroutine.
func (logger *StreamLogger) stream() {
	for message := range logger.dataQueue {
		logger.transmit(message)
	}
}

func (logger *StreamLogger) transmit(message interface{}) {
	logger.mu.Lock()
	defer logger.mu.Unlock()

	for ch, sub := range logger.subscribed {
		select {
		case ch <- message:
			deliveredCount.Add([]string{logger.name, sub.name}, 1)
		default:
			deliveryDropCount.Add([]string{logger.name, sub.name}, 1)
		}
	}
}

// Name returns the name of StreamLogger.
func (logger *StreamLogger) Name() string {
	return logger.name
}

// ServeLogs registers the URL on which messages will be broadcast.
// It is safe to register multiple URLs for the same StreamLogger.
func (logger *StreamLogger) ServeLogs(url string, messageFmt func(url.Values, interface{}) string) {
	http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
		if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil {
			acl.SendError(w, err)
			return
		}
		if err := r.ParseForm(); err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
		}
		ch := logger.Subscribe("ServeLogs")
		defer logger.Unsubscribe(ch)

		// Notify client that we're set up. Helpful to distinguish low-traffic streams from connection issues.
		w.WriteHeader(http.StatusOK)
		w.(http.Flusher).Flush()

		for message := range ch {
			if _, err := io.WriteString(w, messageFmt(r.Form, message)); err != nil {
				return
			}
			w.(http.Flusher).Flush()
		}
	})
	log.Infof("Streaming logs from %s at %v.", logger.Name(), url)
}
<|endoftext|>