<commit_before>package peco

import (
	"fmt"
	"time"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
	"github.com/nsf/termbox-go"
)

type Layout interface {
	ClearStatus(time.Duration)
	PrintStatus(string)
	DrawScreen([]Match)
}

// Utility function
func mergeAttribute(a, b termbox.Attribute) termbox.Attribute {
	if a&0x0F == 0 || b&0x0F == 0 {
		return a | b
	} else {
		return ((a - 1) | (b - 1)) + 1
	}
}

// Utility function
func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) {
	for len(msg) > 0 {
		c, w := utf8.DecodeRuneInString(msg)
		if c == utf8.RuneError {
			c = '?'
			w = 1
		}
		msg = msg[w:]
		termbox.SetCell(x, y, c, fg, bg)
		x += runewidth.RuneWidth(c)
	}

	if !fill {
		return
	}

	width, _ := termbox.Size()
	for ; x < width; x++ {
		termbox.SetCell(x, y, ' ', fg, bg)
	}
}

// UserPrompt draws the prompt line
type UserPrompt struct {
	*Ctx
	prefix string
	prefixLen int
}

func NewUserPrompt(ctx *Ctx) *UserPrompt {
	prefix := ctx.config.Prompt
	if len(prefix) <= 0 { // default
		prefix = "QUERY>"
	}
	prefixLen := runewidth.StringWidth(prefix)

	return &UserPrompt{
		Ctx: ctx,
		prefix:    prefix,
		prefixLen: prefixLen,
	}
}

func (u UserPrompt) Draw() {
	// print "QUERY>"
	printScreen(0, 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false)

	if u.caretPos <= 0 {
		u.caretPos = 0 // sanity
	}

	if u.caretPos > len(u.query) {
		u.caretPos = len(u.query)
	}

	if u.caretPos == len(u.query) {
		// the entire string + the caret after the string
		fg := u.config.Style.QueryFG()
		bg := u.config.Style.QueryBG()
		qs := string(u.query)
		ql := runewidth.StringWidth(qs)
		printScreen(u.prefixLen+1, 0, fg, bg, qs, false)
		printScreen(u.prefixLen+1+ql, 0, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
		printScreen(u.prefixLen+1+ql+1, 0, fg, bg, "", true)
	} else {
		// the caret is in the middle of the string
		prev := 0
		fg := u.config.Style.QueryFG()
		bg := u.config.Style.QueryBG()
		for i, r := range u.query {
			if i == u.caretPos {
				fg |= termbox.AttrReverse
				bg |= termbox.AttrReverse
			}
			termbox.SetCell(u.prefixLen+1+prev, 0, r, fg, bg)
			prev += runewidth.RuneWidth(r)
		}
	}

	width, _ := termbox.Size()

	pmsg := fmt.Sprintf("%s [%d/%d]", u.Matcher().String(), u.currentPage.index, u.maxPage)
	printScreen(width-runewidth.StringWidth(pmsg), 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false)
}

// StatusBar draws the status message bar
type StatusBar struct {
	*Ctx
	clearTimer *time.Timer
}

func NewStatusBar(ctx *Ctx) *StatusBar {
	return &StatusBar{
		ctx,
		nil,
	}
}

func (s *StatusBar) stopTimer() {
	if t := s.clearTimer; t != nil {
		t.Stop()
	}
}

func (s *StatusBar) ClearStatus(d time.Duration) {
	s.stopTimer()
	s.clearTimer = time.AfterFunc(d, func() {
		s.PrintStatus("")
	})
}

func (s *StatusBar) PrintStatus(msg string) {
	s.stopTimer()

	w, h := termbox.Size()

	width := runewidth.StringWidth(msg)
	for width > w {
		_, rw := utf8.DecodeRuneInString(msg)
		width = width - rw
		msg = msg[rw:]
	}

	var pad []byte
	if w > width {
		pad = make([]byte, w-width)
		for i := 0; i < w-width; i++ {
			pad[i] = ' '
		}
	}

	fgAttr := s.config.Style.BasicFG()
	bgAttr := s.config.Style.BasicBG()

	if w > width {
		printScreen(0, h-2, fgAttr, bgAttr, string(pad), false)
	}

	if width > 0 {
		printScreen(w-width, h-2, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false)
	}
	termbox.Flush()
}

type basicLayout struct {
	*Ctx
	*StatusBar
	*UserPrompt
}

// DefaultLayout implements the top-down layout
type DefaultLayout struct {
	*basicLayout
}
type BottomUpLayout struct {
	*basicLayout
}

func NewDefaultLayout(ctx *Ctx) *DefaultLayout {
	return &DefaultLayout{
		&basicLayout{
			Ctx: ctx,
			StatusBar: NewStatusBar(ctx),
			UserPrompt: NewUserPrompt(ctx),
		},
	}
}

func (l *DefaultLayout) DrawScreen(targets []Match) {
	fgAttr := l.config.Style.BasicFG()
	bgAttr := l.config.Style.BasicBG()

	if err := termbox.Clear(fgAttr, bgAttr); err != nil {
		return
	}

	if l.currentLine > len(targets) && len(targets) > 0 {
		l.currentLine = len(targets)
	}

	_, height := termbox.Size()
	perPage := height - 4

CALCULATE_PAGE:
	currentPage := l.currentPage
	currentPage.index = ((l.currentLine - 1) / perPage) + 1
	if currentPage.index <= 0 {
		currentPage.index = 1
	}
	currentPage.offset = (currentPage.index - 1) * perPage
	currentPage.perPage = perPage
	if len(targets) == 0 {
		l.maxPage = 1
	} else {
		l.maxPage = ((len(targets) + perPage - 1) / perPage)
	}

	if l.maxPage < currentPage.index {
		if len(targets) == 0 && len(l.query) == 0 {
			// wait for targets
			return
		}
		l.currentLine = currentPage.offset
		goto CALCULATE_PAGE
	}

	l.UserPrompt.Draw()

	for n := 1; n <= perPage; n++ {
		switch {
		case n+currentPage.offset == l.currentLine:
			fgAttr = l.config.Style.SelectedFG()
			bgAttr = l.config.Style.SelectedBG()
		case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset):
			fgAttr = l.config.Style.SavedSelectionFG()
			bgAttr = l.config.Style.SavedSelectionBG()
		default:
			fgAttr = l.config.Style.BasicFG()
			bgAttr = l.config.Style.BasicBG()
		}

		targetIdx := currentPage.offset + n - 1
		if targetIdx >= len(targets) {
			break
		}

		target := targets[targetIdx]
		line := target.Line()
		matches := target.Indices()
		if matches == nil {
			printScreen(0, n, fgAttr, bgAttr, line, true)
		} else {
			prev := 0
			index := 0
			for _, m := range matches {
				if m[0] > index {
					c := line[index:m[0]]
					printScreen(prev, n, fgAttr, bgAttr, c, false)
					prev += runewidth.StringWidth(c)
					index += len(c)
				}
				c := line[m[0]:m[1]]
				printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true)
				prev += runewidth.StringWidth(c)
				index += len(c)
			}

			m := matches[len(matches)-1]
			if m[0] > index {
				printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true)
			} else if len(line) > m[1] {
				printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true)
			}
		}
	}

	if err := termbox.Flush(); err != nil {
		return
	}
}
<commit_msg>rip out page calculation<commit_after>package peco

import (
	"fmt"
	"time"
	"unicode/utf8"

	"github.com/mattn/go-runewidth"
	"github.com/nsf/termbox-go"
)

type Layout interface {
	ClearStatus(time.Duration)
	PrintStatus(string)
	DrawScreen([]Match)
}

// Utility function
func mergeAttribute(a, b termbox.Attribute) termbox.Attribute {
	if a&0x0F == 0 || b&0x0F == 0 {
		return a | b
	} else {
		return ((a - 1) | (b - 1)) + 1
	}
}

// Utility function
func printScreen(x, y int, fg, bg termbox.Attribute, msg string, fill bool) {
	for len(msg) > 0 {
		c, w := utf8.DecodeRuneInString(msg)
		if c == utf8.RuneError {
			c = '?'
			w = 1
		}
		msg = msg[w:]
		termbox.SetCell(x, y, c, fg, bg)
		x += runewidth.RuneWidth(c)
	}

	if !fill {
		return
	}

	width, _ := termbox.Size()
	for ; x < width; x++ {
		termbox.SetCell(x, y, ' ', fg, bg)
	}
}

// UserPrompt draws the prompt line
type UserPrompt struct {
	*Ctx
	prefix string
	prefixLen int
}

func NewUserPrompt(ctx *Ctx) *UserPrompt {
	prefix := ctx.config.Prompt
	if len(prefix) <= 0 { // default
		prefix = "QUERY>"
	}
	prefixLen := runewidth.StringWidth(prefix)

	return &UserPrompt{
		Ctx: ctx,
		prefix:    prefix,
		prefixLen: prefixLen,
	}
}

func (u UserPrompt) Draw() {
	// print "QUERY>"
	printScreen(0, 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), u.prefix, false)

	if u.caretPos <= 0 {
		u.caretPos = 0 // sanity
	}

	if u.caretPos > len(u.query) {
		u.caretPos = len(u.query)
	}

	if u.caretPos == len(u.query) {
		// the entire string + the caret after the string
		fg := u.config.Style.QueryFG()
		bg := u.config.Style.QueryBG()
		qs := string(u.query)
		ql := runewidth.StringWidth(qs)
		printScreen(u.prefixLen+1, 0, fg, bg, qs, false)
		printScreen(u.prefixLen+1+ql, 0, fg|termbox.AttrReverse, bg|termbox.AttrReverse, " ", false)
		printScreen(u.prefixLen+1+ql+1, 0, fg, bg, "", true)
	} else {
		// the caret is in the middle of the string
		prev := 0
		fg := u.config.Style.QueryFG()
		bg := u.config.Style.QueryBG()
		for i, r := range u.query {
			if i == u.caretPos {
				fg |= termbox.AttrReverse
				bg |= termbox.AttrReverse
			}
			termbox.SetCell(u.prefixLen+1+prev, 0, r, fg, bg)
			prev += runewidth.RuneWidth(r)
		}
	}

	width, _ := termbox.Size()

	pmsg := fmt.Sprintf("%s [%d/%d]", u.Matcher().String(), u.currentPage.index, u.maxPage)
	printScreen(width-runewidth.StringWidth(pmsg), 0, u.config.Style.BasicFG(), u.config.Style.BasicBG(), pmsg, false)
}

// StatusBar draws the status message bar
type StatusBar struct {
	*Ctx
	clearTimer *time.Timer
}

func NewStatusBar(ctx *Ctx) *StatusBar {
	return &StatusBar{
		ctx,
		nil,
	}
}

func (s *StatusBar) stopTimer() {
	if t := s.clearTimer; t != nil {
		t.Stop()
	}
}

func (s *StatusBar) ClearStatus(d time.Duration) {
	s.stopTimer()
	s.clearTimer = time.AfterFunc(d, func() {
		s.PrintStatus("")
	})
}

func (s *StatusBar) PrintStatus(msg string) {
	s.stopTimer()

	w, h := termbox.Size()

	width := runewidth.StringWidth(msg)
	for width > w {
		_, rw := utf8.DecodeRuneInString(msg)
		width = width - rw
		msg = msg[rw:]
	}

	var pad []byte
	if w > width {
		pad = make([]byte, w-width)
		for i := 0; i < w-width; i++ {
			pad[i] = ' '
		}
	}

	fgAttr := s.config.Style.BasicFG()
	bgAttr := s.config.Style.BasicBG()

	if w > width {
		printScreen(0, h-2, fgAttr, bgAttr, string(pad), false)
	}

	if width > 0 {
		printScreen(w-width, h-2, fgAttr|termbox.AttrReverse|termbox.AttrBold, bgAttr|termbox.AttrReverse, msg, false)
	}
	termbox.Flush()
}

type basicLayout struct {
	*Ctx
	*StatusBar
	*UserPrompt
}

// DefaultLayout implements the top-down layout
type DefaultLayout struct {
	*basicLayout
}
type BottomUpLayout struct {
	*basicLayout
}

func NewDefaultLayout(ctx *Ctx) *DefaultLayout {
	return &DefaultLayout{
		&basicLayout{
			Ctx: ctx,
			StatusBar: NewStatusBar(ctx),
			UserPrompt: NewUserPrompt(ctx),
		},
	}
}

func (l *DefaultLayout) CalculatePage(targets []Match, perPage int) error {
CALCULATE_PAGE:
	currentPage := l.currentPage
	currentPage.index = ((l.currentLine - 1) / perPage) + 1
	if currentPage.index <= 0 {
		currentPage.index = 1
	}
	currentPage.offset = (currentPage.index - 1) * perPage
	currentPage.perPage = perPage
	if len(targets) == 0 {
		l.maxPage = 1
	} else {
		l.maxPage = ((len(targets) + perPage - 1) / perPage)
	}

	if l.maxPage < currentPage.index {
		if len(targets) == 0 && len(l.query) == 0 {
			// wait for targets
			return fmt.Errorf("no targets or query. nothing to do")
		}
		l.currentLine = currentPage.offset
		goto CALCULATE_PAGE
	}

	return nil
}

func (l *DefaultLayout) DrawScreen(targets []Match) {
	fgAttr := l.config.Style.BasicFG()
	bgAttr := l.config.Style.BasicBG()

	if err := termbox.Clear(fgAttr, bgAttr); err != nil {
		return
	}

	if l.currentLine > len(targets) && len(targets) > 0 {
		l.currentLine = len(targets)
	}

	_, height := termbox.Size()
	perPage := height - 4

	if err := l.CalculatePage(targets, perPage); err != nil {
		return
	}

	l.UserPrompt.Draw()
	currentPage := l.currentPage

	for n := 1; n <= perPage; n++ {
		switch {
		case n+currentPage.offset == l.currentLine:
			fgAttr = l.config.Style.SelectedFG()
			bgAttr = l.config.Style.SelectedBG()
		case l.selection.Has(n+currentPage.offset) || l.SelectedRange().Has(n+currentPage.offset):
			fgAttr = l.config.Style.SavedSelectionFG()
			bgAttr = l.config.Style.SavedSelectionBG()
		default:
			fgAttr = l.config.Style.BasicFG()
			bgAttr = l.config.Style.BasicBG()
		}

		targetIdx := currentPage.offset + n - 1
		if targetIdx >= len(targets) {
			break
		}

		target := targets[targetIdx]
		line := target.Line()
		matches := target.Indices()
		if matches == nil {
			printScreen(0, n, fgAttr, bgAttr, line, true)
		} else {
			prev := 0
			index := 0
			for _, m := range matches {
				if m[0] > index {
					c := line[index:m[0]]
					printScreen(prev, n, fgAttr, bgAttr, c, false)
					prev += runewidth.StringWidth(c)
					index += len(c)
				}
				c := line[m[0]:m[1]]
				printScreen(prev, n, l.config.Style.MatchedFG(), mergeAttribute(bgAttr, l.config.Style.MatchedBG()), c, true)
				prev += runewidth.StringWidth(c)
				index += len(c)
			}

			m := matches[len(matches)-1]
			if m[0] > index {
				printScreen(prev, n, l.config.Style.QueryFG(), mergeAttribute(bgAttr, l.config.Style.QueryBG()), line[m[0]:m[1]], true)
			} else if len(line) > m[1] {
				printScreen(prev, n, fgAttr, bgAttr, line[m[1]:len(line)], true)
			}
		}
	}

	if err := termbox.Flush(); err != nil {
		return
	}
}
<|endoftext|>


<commit_before>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package grpc

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

	"golang.org/x/net/context"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/transport"
)

var (
	// ErrUnspecTarget indicates that the target address is unspecified.
	ErrUnspecTarget = errors.New("grpc: target is unspecified")
	// ErrNoTransportSecurity indicates that there is no transport security
	// being set for ClientConn. Users should either set one or explicityly
	// call WithInsecure DialOption to disable security.
	ErrNoTransportSecurity = errors.New("grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)")
	// ErrCredentialsMisuse indicates that users want to transmit security infomation
	// (e.g., oauth2 token) which requires secure connection on an insecure
	// connection.
	ErrCredentialsMisuse = errors.New("grpc: the credentials require transport level security (use grpc.WithTransportAuthenticator() to set)")
	// ErrClientConnClosing indicates that the operation is illegal because
	// the session is closing.
	ErrClientConnClosing = errors.New("grpc: the client connection is closing")
	// ErrClientConnTimeout indicates that the connection could not be
	// established or re-established within the specified timeout.
	ErrClientConnTimeout = errors.New("grpc: timed out trying to connect")
	// minimum time to give a connection to complete
	minConnectTimeout = 20 * time.Second
)

// dialOptions configure a Dial call. dialOptions are set by the DialOption
// values passed to Dial.
type dialOptions struct {
	codec    Codec
	block    bool
	insecure bool
	copts    transport.ConnectOptions
}

// DialOption configures how we set up the connection.
type DialOption func(*dialOptions)

// WithCodec returns a DialOption which sets a codec for message marshaling and unmarshaling.
func WithCodec(c Codec) DialOption {
	return func(o *dialOptions) {
		o.codec = c
	}
}

// WithBlock returns a DialOption which makes caller of Dial blocks until the underlying
// connection is up. Without this, Dial returns immediately and connecting the server
// happens in background.
func WithBlock() DialOption {
	return func(o *dialOptions) {
		o.block = true
	}
}

func WithInsecure() DialOption {
	return func(o *dialOptions) {
		o.insecure = true
	}
}

// WithTransportCredentials returns a DialOption which configures a
// connection level security credentials (e.g., TLS/SSL).
func WithTransportCredentials(creds credentials.TransportAuthenticator) DialOption {
	return func(o *dialOptions) {
		o.copts.AuthOptions = append(o.copts.AuthOptions, creds)
	}
}

// WithPerRPCCredentials returns a DialOption which sets
// credentials which will place auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.Credentials) DialOption {
	return func(o *dialOptions) {
		o.copts.AuthOptions = append(o.copts.AuthOptions, creds)
	}
}

// WithTimeout returns a DialOption that configures a timeout for dialing a client connection.
func WithTimeout(d time.Duration) DialOption {
	return func(o *dialOptions) {
		o.copts.Timeout = d
	}
}

// WithDialer returns a DialOption that specifies a function to use for dialing network addresses.
func WithDialer(f func(addr string, timeout time.Duration) (net.Conn, error)) DialOption {
	return func(o *dialOptions) {
		o.copts.Dialer = f
	}
}

// WithUserAgent returns a DialOption that specifies a user agent string for all the RPCs.
func WithUserAgent(s string) DialOption {
	return func(o *dialOptions) {
		o.copts.UserAgent = s
	}
}

// Dial creates a client connection the given target.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
	if target == "" {
		return nil, ErrUnspecTarget
	}
	cc := &ClientConn{
		target:       target,
		shutdownChan: make(chan struct{}),
	}
	for _, opt := range opts {
		opt(&cc.dopts)
	}
	if !cc.dopts.insecure {
		var ok bool
		for _, c := range cc.dopts.copts.AuthOptions {
			if _, ok := c.(credentials.TransportAuthenticator); !ok {
				continue
			}
			ok = true
		}
		if !ok {
			return nil, ErrNoTransportSecurity
		}
	} else {
		for _, c := range cc.dopts.copts.AuthOptions {
			if c.RequireTransportSecurity() {
				return nil, ErrCredentialsMisuse
			}
		}
	}
	colonPos := strings.LastIndex(target, ":")
	if colonPos == -1 {
		colonPos = len(target)
	}
	cc.authority = target[:colonPos]
	if cc.dopts.codec == nil {
		// Set the default codec.
		cc.dopts.codec = protoCodec{}
	}
	cc.stateCV = sync.NewCond(&cc.mu)
	if cc.dopts.block {
		if err := cc.resetTransport(false); err != nil {
			cc.Close()
			return nil, err
		}
		// Start to monitor the error status of transport.
		go cc.transportMonitor()
	} else {
		// Start a goroutine connecting to the server asynchronously.
		go func() {
			if err := cc.resetTransport(false); err != nil {
				grpclog.Printf("Failed to dial %s: %v; please retry.", target, err)
				cc.Close()
				return
			}
			go cc.transportMonitor()
		}()
	}
	return cc, nil
}

// ConnectivityState indicates the state of a client connection.
type ConnectivityState int

const (
	// Idle indicates the ClientConn is idle.
	Idle ConnectivityState = iota
	// Connecting indicates the ClienConn is connecting.
	Connecting
	// Ready indicates the ClientConn is ready for work.
	Ready
	// TransientFailure indicates the ClientConn has seen a failure but expects to recover.
	TransientFailure
	// Shutdown indicates the ClientConn has stated shutting down.
	Shutdown
)

func (s ConnectivityState) String() string {
	switch s {
	case Idle:
		return "IDLE"
	case Connecting:
		return "CONNECTING"
	case Ready:
		return "READY"
	case TransientFailure:
		return "TRANSIENT_FAILURE"
	case Shutdown:
		return "SHUTDOWN"
	default:
		panic(fmt.Sprintf("unknown connectivity state: %d", s))
	}
}

// ClientConn represents a client connection to an RPC service.
type ClientConn struct {
	target       string
	authority    string
	dopts        dialOptions
	shutdownChan chan struct{}

	mu      sync.Mutex
	state   ConnectivityState
	stateCV *sync.Cond
	// ready is closed and becomes nil when a new transport is up or failed
	// due to timeout.
	ready chan struct{}
	// Every time a new transport is created, this is incremented by 1. Used
	// to avoid trying to recreate a transport while the new one is already
	// under construction.
	transportSeq int
	transport    transport.ClientTransport
}

// State returns the connectivity state of the ClientConn
func (cc *ClientConn) State() ConnectivityState {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	return cc.state
}

// WaitForStateChange blocks until the state changes to something other than the sourceState
// or timeout fires. It returns false if timeout fires and true otherwise.
func (cc *ClientConn) WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool {
	start := time.Now()
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if sourceState != cc.state {
		return true
	}
	expired := timeout <= time.Since(start)
	if expired {
		return false
	}
	done := make(chan struct{})
	go func() {
		select {
		case <-time.After(timeout - time.Since(start)):
			cc.mu.Lock()
			expired = true
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
		case <-done:
		}
	}()
	defer close(done)
	for sourceState == cc.state {
		cc.stateCV.Wait()
		if expired {
			return false
		}
	}
	return true
}

func (cc *ClientConn) resetTransport(closeTransport bool) error {
	var retries int
	start := time.Now()
	for {
		cc.mu.Lock()
		cc.state = Connecting
		cc.stateCV.Broadcast()
		t := cc.transport
		ts := cc.transportSeq
		// Avoid wait() picking up a dying transport unnecessarily.
		cc.transportSeq = 0
		if cc.state == Shutdown {
			cc.mu.Unlock()
			return ErrClientConnClosing
		}
		cc.mu.Unlock()
		if closeTransport {
			t.Close()
		}
		// Adjust timeout for the current try.
		copts := cc.dopts.copts
		if copts.Timeout < 0 {
			cc.Close()
			return ErrClientConnTimeout
		}
		if copts.Timeout > 0 {
			copts.Timeout -= time.Since(start)
			if copts.Timeout <= 0 {
				cc.Close()
				return ErrClientConnTimeout
			}
		}
		sleepTime := backoff(retries)
		timeout := sleepTime
		if timeout < minConnectTimeout {
			timeout = minConnectTimeout
		}
		if copts.Timeout == 0 || copts.Timeout > timeout {
			copts.Timeout = timeout
		}
		connectTime := time.Now()
		newTransport, err := transport.NewClientTransport(cc.target, &copts)
		if err != nil {
			cc.mu.Lock()
			cc.state = TransientFailure
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
			sleepTime -= time.Since(connectTime)
			if sleepTime < 0 {
				sleepTime = 0
			}
			// Fail early before falling into sleep.
			if cc.dopts.copts.Timeout > 0 && cc.dopts.copts.Timeout < sleepTime+time.Since(start) {
				cc.Close()
				return ErrClientConnTimeout
			}
			closeTransport = false
			time.Sleep(sleepTime)
			retries++
			grpclog.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target)
			continue
		}
		cc.mu.Lock()
		if cc.state == Shutdown {
			// cc.Close() has been invoked.
			cc.mu.Unlock()
			newTransport.Close()
			return ErrClientConnClosing
		}
		cc.state = Ready
		cc.stateCV.Broadcast()
		cc.transport = newTransport
		cc.transportSeq = ts + 1
		if cc.ready != nil {
			close(cc.ready)
			cc.ready = nil
		}
		cc.mu.Unlock()
		return nil
	}
}

// Run in a goroutine to track the error in transport and create the
// new transport if an error happens. It returns when the channel is closing.
func (cc *ClientConn) transportMonitor() {
	for {
		select {
		// shutdownChan is needed to detect the teardown when
		// the ClientConn is idle (i.e., no RPC in flight).
		case <-cc.shutdownChan:
			return
		case <-cc.transport.Error():
			cc.mu.Lock()
			cc.state = TransientFailure
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
			if err := cc.resetTransport(true); err != nil {
				// The ClientConn is closing.
				grpclog.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err)
				return
			}
			continue
		}
	}
}

// When wait returns, either the new transport is up or ClientConn is
// closing. Used to avoid working on a dying transport. It updates and
// returns the transport and its version when there is no error.
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
	for {
		cc.mu.Lock()
		switch {
		case cc.state == Shutdown:
			cc.mu.Unlock()
			return nil, 0, ErrClientConnClosing
		case ts < cc.transportSeq:
			// Worked on a dying transport. Try the new one immediately.
			defer cc.mu.Unlock()
			return cc.transport, cc.transportSeq, nil
		default:
			ready := cc.ready
			if ready == nil {
				ready = make(chan struct{})
				cc.ready = ready
			}
			cc.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, 0, transport.ContextErr(ctx.Err())
			// Wait until the new transport is ready or failed.
			case <-ready:
			}
		}
	}
}

// Close starts to tear down the ClientConn. Returns ErrClientConnClosing if
// it has been closed (mostly due to dial time-out).
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
// tight loop.
func (cc *ClientConn) Close() error {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if cc.state == Shutdown {
		return ErrClientConnClosing
	}
	cc.state = Shutdown
	cc.stateCV.Broadcast()
	if cc.ready != nil {
		close(cc.ready)
		cc.ready = nil
	}
	if cc.transport != nil {
		cc.transport.Close()
	}
	if cc.shutdownChan != nil {
		close(cc.shutdownChan)
	}
	return nil
}
<commit_msg>s/stated/started/ in Shutdown doc<commit_after>/*
 *
 * Copyright 2014, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

package grpc

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

	"golang.org/x/net/context"
	"google.golang.org/grpc/credentials"
	"google.golang.org/grpc/grpclog"
	"google.golang.org/grpc/transport"
)

var (
	// ErrUnspecTarget indicates that the target address is unspecified.
	ErrUnspecTarget = errors.New("grpc: target is unspecified")
	// ErrNoTransportSecurity indicates that there is no transport security
	// being set for ClientConn. Users should either set one or explicityly
	// call WithInsecure DialOption to disable security.
	ErrNoTransportSecurity = errors.New("grpc: no transport security set (use grpc.WithInsecure() explicitly or set credentials)")
	// ErrCredentialsMisuse indicates that users want to transmit security infomation
	// (e.g., oauth2 token) which requires secure connection on an insecure
	// connection.
	ErrCredentialsMisuse = errors.New("grpc: the credentials require transport level security (use grpc.WithTransportAuthenticator() to set)")
	// ErrClientConnClosing indicates that the operation is illegal because
	// the session is closing.
	ErrClientConnClosing = errors.New("grpc: the client connection is closing")
	// ErrClientConnTimeout indicates that the connection could not be
	// established or re-established within the specified timeout.
	ErrClientConnTimeout = errors.New("grpc: timed out trying to connect")
	// minimum time to give a connection to complete
	minConnectTimeout = 20 * time.Second
)

// dialOptions configure a Dial call. dialOptions are set by the DialOption
// values passed to Dial.
type dialOptions struct {
	codec    Codec
	block    bool
	insecure bool
	copts    transport.ConnectOptions
}

// DialOption configures how we set up the connection.
type DialOption func(*dialOptions)

// WithCodec returns a DialOption which sets a codec for message marshaling and unmarshaling.
func WithCodec(c Codec) DialOption {
	return func(o *dialOptions) {
		o.codec = c
	}
}

// WithBlock returns a DialOption which makes caller of Dial blocks until the underlying
// connection is up. Without this, Dial returns immediately and connecting the server
// happens in background.
func WithBlock() DialOption {
	return func(o *dialOptions) {
		o.block = true
	}
}

func WithInsecure() DialOption {
	return func(o *dialOptions) {
		o.insecure = true
	}
}

// WithTransportCredentials returns a DialOption which configures a
// connection level security credentials (e.g., TLS/SSL).
func WithTransportCredentials(creds credentials.TransportAuthenticator) DialOption {
	return func(o *dialOptions) {
		o.copts.AuthOptions = append(o.copts.AuthOptions, creds)
	}
}

// WithPerRPCCredentials returns a DialOption which sets
// credentials which will place auth state on each outbound RPC.
func WithPerRPCCredentials(creds credentials.Credentials) DialOption {
	return func(o *dialOptions) {
		o.copts.AuthOptions = append(o.copts.AuthOptions, creds)
	}
}

// WithTimeout returns a DialOption that configures a timeout for dialing a client connection.
func WithTimeout(d time.Duration) DialOption {
	return func(o *dialOptions) {
		o.copts.Timeout = d
	}
}

// WithDialer returns a DialOption that specifies a function to use for dialing network addresses.
func WithDialer(f func(addr string, timeout time.Duration) (net.Conn, error)) DialOption {
	return func(o *dialOptions) {
		o.copts.Dialer = f
	}
}

// WithUserAgent returns a DialOption that specifies a user agent string for all the RPCs.
func WithUserAgent(s string) DialOption {
	return func(o *dialOptions) {
		o.copts.UserAgent = s
	}
}

// Dial creates a client connection the given target.
func Dial(target string, opts ...DialOption) (*ClientConn, error) {
	if target == "" {
		return nil, ErrUnspecTarget
	}
	cc := &ClientConn{
		target:       target,
		shutdownChan: make(chan struct{}),
	}
	for _, opt := range opts {
		opt(&cc.dopts)
	}
	if !cc.dopts.insecure {
		var ok bool
		for _, c := range cc.dopts.copts.AuthOptions {
			if _, ok := c.(credentials.TransportAuthenticator); !ok {
				continue
			}
			ok = true
		}
		if !ok {
			return nil, ErrNoTransportSecurity
		}
	} else {
		for _, c := range cc.dopts.copts.AuthOptions {
			if c.RequireTransportSecurity() {
				return nil, ErrCredentialsMisuse
			}
		}
	}
	colonPos := strings.LastIndex(target, ":")
	if colonPos == -1 {
		colonPos = len(target)
	}
	cc.authority = target[:colonPos]
	if cc.dopts.codec == nil {
		// Set the default codec.
		cc.dopts.codec = protoCodec{}
	}
	cc.stateCV = sync.NewCond(&cc.mu)
	if cc.dopts.block {
		if err := cc.resetTransport(false); err != nil {
			cc.Close()
			return nil, err
		}
		// Start to monitor the error status of transport.
		go cc.transportMonitor()
	} else {
		// Start a goroutine connecting to the server asynchronously.
		go func() {
			if err := cc.resetTransport(false); err != nil {
				grpclog.Printf("Failed to dial %s: %v; please retry.", target, err)
				cc.Close()
				return
			}
			go cc.transportMonitor()
		}()
	}
	return cc, nil
}

// ConnectivityState indicates the state of a client connection.
type ConnectivityState int

const (
	// Idle indicates the ClientConn is idle.
	Idle ConnectivityState = iota
	// Connecting indicates the ClienConn is connecting.
	Connecting
	// Ready indicates the ClientConn is ready for work.
	Ready
	// TransientFailure indicates the ClientConn has seen a failure but expects to recover.
	TransientFailure
	// Shutdown indicates the ClientConn has started shutting down.
	Shutdown
)

func (s ConnectivityState) String() string {
	switch s {
	case Idle:
		return "IDLE"
	case Connecting:
		return "CONNECTING"
	case Ready:
		return "READY"
	case TransientFailure:
		return "TRANSIENT_FAILURE"
	case Shutdown:
		return "SHUTDOWN"
	default:
		panic(fmt.Sprintf("unknown connectivity state: %d", s))
	}
}

// ClientConn represents a client connection to an RPC service.
type ClientConn struct {
	target       string
	authority    string
	dopts        dialOptions
	shutdownChan chan struct{}

	mu      sync.Mutex
	state   ConnectivityState
	stateCV *sync.Cond
	// ready is closed and becomes nil when a new transport is up or failed
	// due to timeout.
	ready chan struct{}
	// Every time a new transport is created, this is incremented by 1. Used
	// to avoid trying to recreate a transport while the new one is already
	// under construction.
	transportSeq int
	transport    transport.ClientTransport
}

// State returns the connectivity state of the ClientConn
func (cc *ClientConn) State() ConnectivityState {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	return cc.state
}

// WaitForStateChange blocks until the state changes to something other than the sourceState
// or timeout fires. It returns false if timeout fires and true otherwise.
func (cc *ClientConn) WaitForStateChange(timeout time.Duration, sourceState ConnectivityState) bool {
	start := time.Now()
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if sourceState != cc.state {
		return true
	}
	expired := timeout <= time.Since(start)
	if expired {
		return false
	}
	done := make(chan struct{})
	go func() {
		select {
		case <-time.After(timeout - time.Since(start)):
			cc.mu.Lock()
			expired = true
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
		case <-done:
		}
	}()
	defer close(done)
	for sourceState == cc.state {
		cc.stateCV.Wait()
		if expired {
			return false
		}
	}
	return true
}

func (cc *ClientConn) resetTransport(closeTransport bool) error {
	var retries int
	start := time.Now()
	for {
		cc.mu.Lock()
		cc.state = Connecting
		cc.stateCV.Broadcast()
		t := cc.transport
		ts := cc.transportSeq
		// Avoid wait() picking up a dying transport unnecessarily.
		cc.transportSeq = 0
		if cc.state == Shutdown {
			cc.mu.Unlock()
			return ErrClientConnClosing
		}
		cc.mu.Unlock()
		if closeTransport {
			t.Close()
		}
		// Adjust timeout for the current try.
		copts := cc.dopts.copts
		if copts.Timeout < 0 {
			cc.Close()
			return ErrClientConnTimeout
		}
		if copts.Timeout > 0 {
			copts.Timeout -= time.Since(start)
			if copts.Timeout <= 0 {
				cc.Close()
				return ErrClientConnTimeout
			}
		}
		sleepTime := backoff(retries)
		timeout := sleepTime
		if timeout < minConnectTimeout {
			timeout = minConnectTimeout
		}
		if copts.Timeout == 0 || copts.Timeout > timeout {
			copts.Timeout = timeout
		}
		connectTime := time.Now()
		newTransport, err := transport.NewClientTransport(cc.target, &copts)
		if err != nil {
			cc.mu.Lock()
			cc.state = TransientFailure
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
			sleepTime -= time.Since(connectTime)
			if sleepTime < 0 {
				sleepTime = 0
			}
			// Fail early before falling into sleep.
			if cc.dopts.copts.Timeout > 0 && cc.dopts.copts.Timeout < sleepTime+time.Since(start) {
				cc.Close()
				return ErrClientConnTimeout
			}
			closeTransport = false
			time.Sleep(sleepTime)
			retries++
			grpclog.Printf("grpc: ClientConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, cc.target)
			continue
		}
		cc.mu.Lock()
		if cc.state == Shutdown {
			// cc.Close() has been invoked.
			cc.mu.Unlock()
			newTransport.Close()
			return ErrClientConnClosing
		}
		cc.state = Ready
		cc.stateCV.Broadcast()
		cc.transport = newTransport
		cc.transportSeq = ts + 1
		if cc.ready != nil {
			close(cc.ready)
			cc.ready = nil
		}
		cc.mu.Unlock()
		return nil
	}
}

// Run in a goroutine to track the error in transport and create the
// new transport if an error happens. It returns when the channel is closing.
func (cc *ClientConn) transportMonitor() {
	for {
		select {
		// shutdownChan is needed to detect the teardown when
		// the ClientConn is idle (i.e., no RPC in flight).
		case <-cc.shutdownChan:
			return
		case <-cc.transport.Error():
			cc.mu.Lock()
			cc.state = TransientFailure
			cc.stateCV.Broadcast()
			cc.mu.Unlock()
			if err := cc.resetTransport(true); err != nil {
				// The ClientConn is closing.
				grpclog.Printf("grpc: ClientConn.transportMonitor exits due to: %v", err)
				return
			}
			continue
		}
	}
}

// When wait returns, either the new transport is up or ClientConn is
// closing. Used to avoid working on a dying transport. It updates and
// returns the transport and its version when there is no error.
func (cc *ClientConn) wait(ctx context.Context, ts int) (transport.ClientTransport, int, error) {
	for {
		cc.mu.Lock()
		switch {
		case cc.state == Shutdown:
			cc.mu.Unlock()
			return nil, 0, ErrClientConnClosing
		case ts < cc.transportSeq:
			// Worked on a dying transport. Try the new one immediately.
			defer cc.mu.Unlock()
			return cc.transport, cc.transportSeq, nil
		default:
			ready := cc.ready
			if ready == nil {
				ready = make(chan struct{})
				cc.ready = ready
			}
			cc.mu.Unlock()
			select {
			case <-ctx.Done():
				return nil, 0, transport.ContextErr(ctx.Err())
			// Wait until the new transport is ready or failed.
			case <-ready:
			}
		}
	}
}

// Close starts to tear down the ClientConn. Returns ErrClientConnClosing if
// it has been closed (mostly due to dial time-out).
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many ClientConn's in a
// tight loop.
func (cc *ClientConn) Close() error {
	cc.mu.Lock()
	defer cc.mu.Unlock()
	if cc.state == Shutdown {
		return ErrClientConnClosing
	}
	cc.state = Shutdown
	cc.stateCV.Broadcast()
	if cc.ready != nil {
		close(cc.ready)
		cc.ready = nil
	}
	if cc.transport != nil {
		cc.transport.Close()
	}
	if cc.shutdownChan != nil {
		close(cc.shutdownChan)
	}
	return nil
}
<|endoftext|>


<commit_before>package clix

import (
	"time"

	"github.com/urfave/cli/v2"
)

// Flag constants declared for CLI use.
const (
	FlagPort = "port"

	FlagLogFormat = "log-format"
	FlagLogLevel  = "log-level"
	FlagLogTags   = "log-tags"

	FlagStatsDSN    = "stats-dsn"
	FlagStatsPrefix = "stats-prefix"
	FlagStatsTags   = "stats-tags"

	FlagProfiler     = "profiler"
	FlagProfilerPort = "profiler-port"

	FlagKafkaConsumerBrokers      = "kafka-consumer-brokers"
	FlagKafkaConsumerGroupID      = "kafka-consumer-group-id"
	FlagKafkaConsumerTopic        = "kafka-consumer-topic"
	FlagKafkaConsumerKafkaVersion = "kafka-consumer-kafka-version"
	FlagKafkaProducerBrokers      = "kafka-producer-brokers"
	FlagKafkaProducerTopic        = "kafka-producer-topic"
	FlagKafkaProducerKafkaVersion = "kafka-producer-kafka-version"

	FlagCommitBatch    = "commit-batch"
	FlagCommitInterval = "commit-interval"

	FlagRedisDSN = "redis-dsn"
)

type defaults struct {
	Port      string
	LogFormat string
	LogLevel  string

	ProfilerPort string
}

// Defaults holds the flag default values.
var Defaults = defaults{
	Port:      "80",
	LogFormat: "json",
	LogLevel:  "info",

	ProfilerPort: "8081",
}

// Flags represents a set of CLI flags.
type Flags []cli.Flag

// Merge joins one or more Flags together, making a new set.
func (f Flags) Merge(flags ...Flags) Flags {
	var m Flags
	m = append(m, f...)
	for _, flag := range flags {
		m = append(m, flag...)
	}

	return m
}

// ServerFlags are flags that configure a server.
var ServerFlags = Flags{
	&cli.StringFlag{
		Name:    FlagPort,
		Value:   Defaults.Port,
		Usage:   "Port for HTTP server to listen on",
		EnvVars: []string{"PORT"},
	},
}

// KafkaConsumerFlags are flags that configure a Kafka consumer.
var KafkaConsumerFlags = Flags{
	&cli.StringSliceFlag{
		Name:     FlagKafkaConsumerBrokers,
		Usage:    "Kafka consumer brokers.",
		EnvVars:  []string{"KAFKA_CONSUMER_BROKERS"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerGroupID,
		Usage:    "Kafka consumer group id.",
		EnvVars:  []string{"KAFKA_CONSUMER_GROUP_ID"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerTopic,
		Usage:    "Kafka topic to consume from.",
		EnvVars:  []string{"KAFKA_CONSUMER_TOPIC"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerKafkaVersion,
		Usage:    "Kafka version (e.g. 0.10.2.0 or 2.3.0).",
		EnvVars:  []string{"KAFKA_CONSUMER_KAFKA_VERSION"},
		Required: true,
	},
}

// KafkaProducerFlags are flags that configure a Kafka producer.
var KafkaProducerFlags = Flags{
	&cli.StringSliceFlag{
		Name:     FlagKafkaProducerBrokers,
		Usage:    "Kafka producer brokers.",
		EnvVars:  []string{"KAFKA_PRODUCER_BROKERS"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaProducerTopic,
		Usage:    "Kafka topic to produce into.",
		EnvVars:  []string{"KAFKA_PRODUCER_TOPIC"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaProducerKafkaVersion,
		Usage:    "Kafka version (e.g. 0.10.2.0 or 2.3.0).",
		EnvVars:  []string{"KAFKA_PRODUCER_KAFKA_VERSION"},
		Required: true,
	},
}

// CommitterFlags are flags that configure message processing batch size and committing interval.
var CommitterFlags = Flags{
	&cli.IntFlag{
		Name:    FlagCommitBatch,
		Value:   500,
		Usage:   "Commit batch size for message processing.",
		EnvVars: []string{"COMMIT_BATCH"},
	},
	&cli.DurationFlag{
		Name:    FlagCommitInterval,
		Value:   1 * time.Second,
		Usage:   "Commit interval for message processing.",
		EnvVars: []string{"COMMIT_INTERVAL"},
	},
}

// RedisFlags are flags that configure redis.
var RedisFlags = Flags{
	&cli.StringFlag{
		Name:     FlagRedisDSN,
		Usage:    "The DSN of Redis.",
		EnvVars:  []string{"REDIS_DSN"},
		Required: true,
	},
}

// CommonFlags are flags that configure logging and stats.
var CommonFlags = Flags{
	&cli.StringFlag{
		Name:    FlagLogFormat,
		Value:   Defaults.LogFormat,
		Usage:   "Specify the format of logs. Supported formats: 'terminal', 'json'",
		EnvVars: []string{"LOG_FORMAT"},
	},
	&cli.StringFlag{
		Name:    FlagLogLevel,
		Value:   Defaults.LogLevel,
		Usage:   "Specify the log level. E.g. 'debug', 'warning'.",
		EnvVars: []string{"LOG_LEVEL"},
	},
	&cli.StringSliceFlag{
		Name:    FlagLogTags,
		Usage:   "A list of tags appended to every log. Format: key=value.",
		EnvVars: []string{"LOG_TAGS"},
	},
	&cli.StringFlag{
		Name:     FlagStatsDSN,
		Usage:    "The URL of a stats backend.",
		EnvVars:  []string{"STATS_DSN"},
		Required: true,
	},
	&cli.StringFlag{
		Name:    FlagStatsPrefix,
		Usage:   "The prefix of the measurements names.",
		EnvVars: []string{"STATS_PREFIX"},
	},
	&cli.StringSliceFlag{
		Name:    FlagStatsTags,
		Usage:   "A list of tags appended to every measurement. Format: key=value.",
		EnvVars: []string{"STATS_TAGS"},
	},
}

// ProfilerFlags are flags that configure to the profiler.
var ProfilerFlags = Flags{
	&cli.BoolFlag{
		Name:    FlagProfiler,
		Usage:   "Enable profiler server.",
		EnvVars: []string{"PROFILER"},
	},
	&cli.StringFlag{
		Name:    FlagProfilerPort,
		Value:   Defaults.ProfilerPort,
		Usage:   "Port for the profiler to listen on.",
		EnvVars: []string{"PROFILER_PORT"},
	},
}
<commit_msg>[FEATURE] Added redis addrs flag for cluster reds. (#156)<commit_after>package clix

import (
	"time"

	"github.com/urfave/cli/v2"
)

// Flag constants declared for CLI use.
const (
	FlagPort = "port"

	FlagLogFormat = "log-format"
	FlagLogLevel  = "log-level"
	FlagLogTags   = "log-tags"

	FlagStatsDSN    = "stats-dsn"
	FlagStatsPrefix = "stats-prefix"
	FlagStatsTags   = "stats-tags"

	FlagProfiler     = "profiler"
	FlagProfilerPort = "profiler-port"

	FlagKafkaConsumerBrokers      = "kafka-consumer-brokers"
	FlagKafkaConsumerGroupID      = "kafka-consumer-group-id"
	FlagKafkaConsumerTopic        = "kafka-consumer-topic"
	FlagKafkaConsumerKafkaVersion = "kafka-consumer-kafka-version"
	FlagKafkaProducerBrokers      = "kafka-producer-brokers"
	FlagKafkaProducerTopic        = "kafka-producer-topic"
	FlagKafkaProducerKafkaVersion = "kafka-producer-kafka-version"

	FlagCommitBatch    = "commit-batch"
	FlagCommitInterval = "commit-interval"

	FlagRedisDSN   = "redis-dsn"
	FlagRedisAddrs = "redis-addrs"
)

type defaults struct {
	Port      string
	LogFormat string
	LogLevel  string

	ProfilerPort string
}

// Defaults holds the flag default values.
var Defaults = defaults{
	Port:      "80",
	LogFormat: "json",
	LogLevel:  "info",

	ProfilerPort: "8081",
}

// Flags represents a set of CLI flags.
type Flags []cli.Flag

// Merge joins one or more Flags together, making a new set.
func (f Flags) Merge(flags ...Flags) Flags {
	var m Flags
	m = append(m, f...)
	for _, flag := range flags {
		m = append(m, flag...)
	}

	return m
}

// ServerFlags are flags that configure a server.
var ServerFlags = Flags{
	&cli.StringFlag{
		Name:    FlagPort,
		Value:   Defaults.Port,
		Usage:   "Port for HTTP server to listen on",
		EnvVars: []string{"PORT"},
	},
}

// KafkaConsumerFlags are flags that configure a Kafka consumer.
var KafkaConsumerFlags = Flags{
	&cli.StringSliceFlag{
		Name:     FlagKafkaConsumerBrokers,
		Usage:    "Kafka consumer brokers.",
		EnvVars:  []string{"KAFKA_CONSUMER_BROKERS"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerGroupID,
		Usage:    "Kafka consumer group id.",
		EnvVars:  []string{"KAFKA_CONSUMER_GROUP_ID"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerTopic,
		Usage:    "Kafka topic to consume from.",
		EnvVars:  []string{"KAFKA_CONSUMER_TOPIC"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaConsumerKafkaVersion,
		Usage:    "Kafka version (e.g. 0.10.2.0 or 2.3.0).",
		EnvVars:  []string{"KAFKA_CONSUMER_KAFKA_VERSION"},
		Required: true,
	},
}

// KafkaProducerFlags are flags that configure a Kafka producer.
var KafkaProducerFlags = Flags{
	&cli.StringSliceFlag{
		Name:     FlagKafkaProducerBrokers,
		Usage:    "Kafka producer brokers.",
		EnvVars:  []string{"KAFKA_PRODUCER_BROKERS"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaProducerTopic,
		Usage:    "Kafka topic to produce into.",
		EnvVars:  []string{"KAFKA_PRODUCER_TOPIC"},
		Required: true,
	},
	&cli.StringFlag{
		Name:     FlagKafkaProducerKafkaVersion,
		Usage:    "Kafka version (e.g. 0.10.2.0 or 2.3.0).",
		EnvVars:  []string{"KAFKA_PRODUCER_KAFKA_VERSION"},
		Required: true,
	},
}

// CommitterFlags are flags that configure message processing batch size and committing interval.
var CommitterFlags = Flags{
	&cli.IntFlag{
		Name:    FlagCommitBatch,
		Value:   500,
		Usage:   "Commit batch size for message processing.",
		EnvVars: []string{"COMMIT_BATCH"},
	},
	&cli.DurationFlag{
		Name:    FlagCommitInterval,
		Value:   1 * time.Second,
		Usage:   "Commit interval for message processing.",
		EnvVars: []string{"COMMIT_INTERVAL"},
	},
}

// RedisFlags are flags that configure redis.
var RedisFlags = Flags{
	&cli.StringFlag{
		Name:     FlagRedisDSN,
		Usage:    "The DSN of Redis.",
		EnvVars:  []string{"REDIS_DSN"},
		Required: true,
	},
}

// RedisClusterFlags are flags that configure redis cluster.
var RedisClusterFlags = Flags{
	&cli.StringSliceFlag{
		Name:     FlagRedisAddrs,
		Usage:    "Adresses of Redis cluster.",
		EnvVars:  []string{"REDIS_ADDRS"},
		Required: true,
	},
}

// CommonFlags are flags that configure logging and stats.
var CommonFlags = Flags{
	&cli.StringFlag{
		Name:    FlagLogFormat,
		Value:   Defaults.LogFormat,
		Usage:   "Specify the format of logs. Supported formats: 'terminal', 'json'",
		EnvVars: []string{"LOG_FORMAT"},
	},
	&cli.StringFlag{
		Name:    FlagLogLevel,
		Value:   Defaults.LogLevel,
		Usage:   "Specify the log level. E.g. 'debug', 'warning'.",
		EnvVars: []string{"LOG_LEVEL"},
	},
	&cli.StringSliceFlag{
		Name:    FlagLogTags,
		Usage:   "A list of tags appended to every log. Format: key=value.",
		EnvVars: []string{"LOG_TAGS"},
	},
	&cli.StringFlag{
		Name:     FlagStatsDSN,
		Usage:    "The URL of a stats backend.",
		EnvVars:  []string{"STATS_DSN"},
		Required: true,
	},
	&cli.StringFlag{
		Name:    FlagStatsPrefix,
		Usage:   "The prefix of the measurements names.",
		EnvVars: []string{"STATS_PREFIX"},
	},
	&cli.StringSliceFlag{
		Name:    FlagStatsTags,
		Usage:   "A list of tags appended to every measurement. Format: key=value.",
		EnvVars: []string{"STATS_TAGS"},
	},
}

// ProfilerFlags are flags that configure to the profiler.
var ProfilerFlags = Flags{
	&cli.BoolFlag{
		Name:    FlagProfiler,
		Usage:   "Enable profiler server.",
		EnvVars: []string{"PROFILER"},
	},
	&cli.StringFlag{
		Name:    FlagProfilerPort,
		Value:   Defaults.ProfilerPort,
		Usage:   "Port for the profiler to listen on.",
		EnvVars: []string{"PROFILER_PORT"},
	},
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package client /* import "mig.ninja/mig/client" */

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
)

type CommandLocation struct {
	Endpoint      string   `json:"endpoint"`
	CommandID     float64  `json:"commandid"`
	ActionID      float64  `json:"actionid"`
	FoundAnything bool     `json:"foundanything"`
	ConnectionsTo []string `json:"connections_to"`
	Latitude      float64  `json:"latitude"`
	Longitude     float64  `json:"longitude"`
	City          string   `json:"city"`
	Country       string   `json:"country"`
}

func ValueToLocation(v interface{}) (cl CommandLocation, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("ValueToLocation) -> %v", e)
		}
	}()
	bData, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &cl)
	if err != nil {
		panic(err)
	}
	return
}

func PrintMap(locs []CommandLocation, title string) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("PrintMap() -> %v", e)
		}
	}()
	gmap := makeMapHeader(title)
	locs = singularizeLocations(locs)
	data, err := json.Marshal(locs)
	if err != nil {
		panic(err)
	}
	gmap += fmt.Sprintf(`        <script type="text/javascript"> var endpoints = %s </script>`, data)
	var details []string
	details = append(details, "
\n")
	for _, cl := range locs {
		detail := fmt.Sprintf("
%s: found=%t
", cl.Endpoint, cl.FoundAnything)
		details = append(details, detail)
	}
	details = append(details, "
\n")
	gmap += makeMapFooter(title, details)

	// write map data to temp file
	fd, err := ioutil.TempFile("", "migmap_")
	defer fd.Close()
	if err != nil {
		panic(err)
	}
	_, err = fd.Write([]byte(gmap))
	if err != nil {
		panic(err)
	}
	fi, err := fd.Stat()
	if err != nil {
		panic(err)
	}
	filepath := fmt.Sprintf("%s/%s", os.TempDir(), fi.Name())
	fmt.Fprintf(os.Stderr, "map written to %s\n", filepath)
	err = exec.Command("firefox", filepath).Start()
	if err != nil {
		panic(err)
	}
	return
}

// singularizeLocations prevent multiple point from using the same coordinates, and therefore show as one point on the map
func singularizeLocations(orig_locs []CommandLocation) (locs []CommandLocation) {
	locs = orig_locs
	for i, _ := range locs {
		for j := 0; j < i; j++ {
			if locs[i].Latitude == locs[j].Latitude && locs[i].Longitude == locs[j].Longitude {
				switch i % 8 {
				case 0:
					locs[i].Latitude += 0.005
				case 1:
					locs[i].Longitude += 0.005
				case 2:
					locs[i].Latitude -= 0.005
				case 3:
					locs[i].Longitude -= 0.005
				case 4:
					locs[i].Latitude += 0.005
					locs[i].Longitude += 0.005
				case 5:
					locs[i].Latitude -= 0.005
					locs[i].Longitude -= 0.005
				case 6:
					locs[i].Latitude += 0.005
					locs[i].Longitude -= 0.005
				case 7:
					locs[i].Latitude -= 0.005
					locs[i].Longitude += 0.005
				}
			}
		}
	}
	return
}

func makeMapHeader(title string) string {
	return fmt.Sprintf(`
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>%s</title>

`, title)
}
func makeMapFooter(title string, body []string) (footer string) {
	footer = `

`
	footer += fmt.Sprintf("
%s
\n", title)
	footer += `<div id="map"></div>`
	for _, p := range body {
		footer += p + "\n"
	}
	footer += `

`
	return
}
<commit_msg>[minor] fix map opening on macos<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]

package client /* import "mig.ninja/mig/client" */

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
)

type CommandLocation struct {
	Endpoint      string   `json:"endpoint"`
	CommandID     float64  `json:"commandid"`
	ActionID      float64  `json:"actionid"`
	FoundAnything bool     `json:"foundanything"`
	ConnectionsTo []string `json:"connections_to"`
	Latitude      float64  `json:"latitude"`
	Longitude     float64  `json:"longitude"`
	City          string   `json:"city"`
	Country       string   `json:"country"`
}

func ValueToLocation(v interface{}) (cl CommandLocation, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("ValueToLocation) -> %v", e)
		}
	}()
	bData, err := json.Marshal(v)
	if err != nil {
		panic(err)
	}
	err = json.Unmarshal(bData, &cl)
	if err != nil {
		panic(err)
	}
	return
}

func PrintMap(locs []CommandLocation, title string) (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("PrintMap() -> %v", e)
		}
	}()
	gmap := makeMapHeader(title)
	locs = singularizeLocations(locs)
	data, err := json.Marshal(locs)
	if err != nil {
		panic(err)
	}
	gmap += fmt.Sprintf(`        <script type="text/javascript"> var endpoints = %s </script>`, data)
	var details []string
	details = append(details, "
\n")
	for _, cl := range locs {
		detail := fmt.Sprintf("
%s: found=%t
", cl.Endpoint, cl.FoundAnything)
		details = append(details, detail)
	}
	details = append(details, "
\n")
	gmap += makeMapFooter(title, details)

	// write map data to temp file
	fd, err := ioutil.TempFile("", "migmap_")
	defer fd.Close()
	if err != nil {
		panic(err)
	}
	_, err = fd.Write([]byte(gmap))
	if err != nil {
		panic(err)
	}
	fi, err := fd.Stat()
	if err != nil {
		panic(err)
	}
	filepath := fmt.Sprintf("%s/%s", os.TempDir(), fi.Name())
	fmt.Fprintf(os.Stderr, "map written to %s\n", filepath)
	switch runtime.GOOS {
	case "linux":
		err = exec.Command("firefox", filepath).Start()
	case "darwin":
		err = exec.Command("open", "-b", "org.mozilla.firefox", filepath).Start()
	default:
		return
	}
	if err != nil {
		panic(err)
	}
	return
}

// singularizeLocations prevent multiple point from using the same coordinates, and therefore show as one point on the map
func singularizeLocations(orig_locs []CommandLocation) (locs []CommandLocation) {
	locs = orig_locs
	for i, _ := range locs {
		for j := 0; j < i; j++ {
			if locs[i].Latitude == locs[j].Latitude && locs[i].Longitude == locs[j].Longitude {
				switch i % 8 {
				case 0:
					locs[i].Latitude += 0.005
				case 1:
					locs[i].Longitude += 0.005
				case 2:
					locs[i].Latitude -= 0.005
				case 3:
					locs[i].Longitude -= 0.005
				case 4:
					locs[i].Latitude += 0.005
					locs[i].Longitude += 0.005
				case 5:
					locs[i].Latitude -= 0.005
					locs[i].Longitude -= 0.005
				case 6:
					locs[i].Latitude += 0.005
					locs[i].Longitude -= 0.005
				case 7:
					locs[i].Latitude -= 0.005
					locs[i].Longitude += 0.005
				}
			}
		}
	}
	return
}

func makeMapHeader(title string) string {
	return fmt.Sprintf(`
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
        <title>%s</title>

`, title)
}
func makeMapFooter(title string, body []string) (footer string) {
	footer = `

`
	footer += fmt.Sprintf("
%s
\n", title)
	footer += `<div id="map"></div>`
	for _, p := range body {
		footer += p + "\n"
	}
	footer += `

`
	return
}
<|endoftext|>


<commit_before>package client

import (
	"github.com/juju/errgo"
	"github.com/coreos/fleet/job"

	execPkg "os/exec"
	"fmt"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	return &ClientCLI{
		etcdPeer: ENDPOINT_VALUE,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Get(name string) (*job.Job, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Get")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<commit_msg>Fixes ignored peers parameter<commit_after>package client

import (
	"github.com/juju/errgo"
	"github.com/coreos/fleet/job"

	execPkg "os/exec"
	"fmt"
)

const (
	FLEETCTL        = "fleetctl"
	ENDPOINT_OPTION = "--endpoint"
	ENDPOINT_VALUE  = "http://172.17.42.1:4001"
)

type ClientCLI struct {
	etcdPeer string
}

func NewClientCLI() FleetClient {
	return NewClientCLIWithPeer(ENDPOINT_VALUE)
}

func NewClientCLIWithPeer(etcdPeer string) FleetClient {
	return &ClientCLI{
		etcdPeer: etcdPeer,
	}
}

func (this *ClientCLI) Submit(name, filePath string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "submit", filePath)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Get(name string) (*job.Job, error) {
	return nil, fmt.Errorf("Method not implemented: ClientCLI.Get")
}

func (this *ClientCLI) Start(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "start", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Stop(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "stop", "--no-block=true", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}

func (this *ClientCLI) Destroy(name string) error {
	cmd := execPkg.Command(FLEETCTL, ENDPOINT_OPTION, this.etcdPeer, "destroy", name)
	_, err := exec(cmd)

	if err != nil {
		return errgo.Mask(err)
	}

	return nil
}
<|endoftext|>


<commit_before><commit_msg>decode with go<commit_after><|endoftext|>


<commit_before>// Copyright 2014 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author: polaris	polaris@studygolang.com

package controller

import (
	"http/middleware"
	"logic"
	"net/http"
	"strings"

	"github.com/labstack/echo"
	"github.com/polaris1119/echoutils"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"

	. "http"
	"model"
)

// 在需要评论（喜欢）且要回调的地方注册评论（喜欢）对象
func init() {
	// 注册评论（喜欢）对象
	logic.RegisterCommentObject(model.TypeArticle, logic.ArticleComment{})
	logic.RegisterLikeObject(model.TypeArticle, logic.ArticleLike{})
}

type ArticleController struct{}

// 注册路由
func (self ArticleController) RegisterRoute(g *echo.Group) {
	g.Get("/articles", self.ReadList)
	g.Get("/articles/crawl", self.Crawl)

	g.Get("/articles/:id", self.Detail)

	g.Match([]string{"GET", "POST"}, "/articles/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.BalanceCheck(), middleware.PublishNotice())
	g.Post("/articles/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
}

// ReadList 网友文章列表页
func (ArticleController) ReadList(ctx echo.Context) error {
	limit := 20

	lastId := goutils.MustInt(ctx.QueryParam("lastid"))
	articles := logic.DefaultArticle.FindBy(ctx, limit+5, lastId)
	if articles == nil {
		logger.Errorln("article controller: find article error")
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	num := len(articles)
	if num == 0 {
		if lastId == 0 {
			return render(ctx, "articles/list.html", map[string]interface{}{"articles": articles, "activeArticles": "active"})
		}
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	var (
		hasPrev, hasNext bool
		prevId, nextId   int
	)

	if lastId != 0 {
		prevId = lastId

		firstNoTopId := articles[0].Id
		for i := 0; i < num; i++ {
			if articles[i].Top != 1 {
				firstNoTopId = articles[i].Id
				break
			}
		}
		// 避免因为文章下线，导致判断错误（所以 > 5）
		if prevId-firstNoTopId > 5 {
			hasPrev = false
		} else {
			prevId += limit
			hasPrev = true
		}
	}

	if num > limit {
		hasNext = true
		articles = articles[:limit]
		nextId = articles[limit-1].Id
	} else {
		nextId = articles[num-1].Id
	}

	pageInfo := map[string]interface{}{
		"has_prev": hasPrev,
		"prev_id":  prevId,
		"has_next": hasNext,
		"next_id":  nextId,
	}

	// 获取当前用户喜欢对象信息
	me, ok := ctx.Get("user").(*model.Me)
	var likeFlags map[int]int
	if ok {
		likeFlags, _ = logic.DefaultLike.FindUserLikeObjects(ctx, me.Uid, model.TypeArticle, articles[0].Id, nextId)
	}

	return render(ctx, "articles/list.html", map[string]interface{}{"articles": articles, "activeArticles": "active", "page": pageInfo, "likeflags": likeFlags})
}

// Detail 文章详细页
func (ArticleController) Detail(ctx echo.Context) error {
	article, prevNext, err := logic.DefaultArticle.FindByIdAndPreNext(ctx, goutils.MustInt(ctx.Param("id")))
	if err != nil {
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	if article == nil || article.Id == 0 || article.Status == model.ArticleStatusOffline {
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	data := map[string]interface{}{
		"activeArticles": "active",
		"article":        article,
		"prev":           prevNext[0],
		"next":           prevNext[1],
	}

	me, ok := ctx.Get("user").(*model.Me)
	if ok {
		data["likeflag"] = logic.DefaultLike.HadLike(ctx, me.Uid, article.Id, model.TypeArticle)
		data["hadcollect"] = logic.DefaultFavorite.HadFavorite(ctx, me.Uid, article.Id, model.TypeArticle)

		logic.Views.Incr(Request(ctx), model.TypeArticle, article.Id, me.Uid)

		if article.IsSelf && me.Uid != article.User.Uid {
			go logic.DefaultViewRecord.Record(article.Id, model.TypeArticle, me.Uid)
		}

		if me.IsRoot || (article.IsSelf && me.Uid == article.User.Uid) {
			data["view_user_num"] = logic.DefaultViewRecord.FindUserNum(ctx, article.Id, model.TypeArticle)
		}
	} else {
		logic.Views.Incr(Request(ctx), model.TypeArticle, article.Id)
	}

	// 为了阅读数即时看到
	article.Viewnum++

	return render(ctx, "articles/detail.html,common/comment.html", data)
}

// Create 发布新文章
func (ArticleController) Create(ctx echo.Context) error {
	title := ctx.FormValue("title")
	if title == "" || ctx.Request().Method() != "POST" {
		return render(ctx, "articles/new.html", map[string]interface{}{"activeArticles": "active"})
	}

	if ctx.FormValue("content") == "" || ctx.FormValue("txt") == "" {
		return fail(ctx, 1, "内容不能为空")
	}

	me := ctx.Get("user").(*model.Me)
	err := logic.DefaultArticle.Publish(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 2, "内部服务错误")
	}

	return success(ctx, nil)
}

// Modify 修改文章
func (ArticleController) Modify(ctx echo.Context) error {
	if ctx.FormValue("id") == "" || ctx.FormValue("content") == "" {
		return fail(ctx, 1, "内容不能为空")
	}
	article, err := logic.DefaultArticle.FindById(ctx, ctx.FormValue("id"))
	if err != nil {
		return fail(ctx, 2, "文章不存在")
	}

	me := ctx.Get("user").(*model.Me)
	if !logic.CanEdit(me, article) {
		return fail(ctx, 3, "没有修改权限")
	}

	errMsg, err := logic.DefaultArticle.Modify(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 4, errMsg)
	}

	return success(ctx, nil)
}

func (ArticleController) Crawl(ctx echo.Context) error {
	strUrl := ctx.QueryParam("url")

	var (
		errMsg string
		err    error
	)
	strUrl = strings.TrimSpace(strUrl)
	_, err = logic.DefaultArticle.ParseArticle(ctx, strUrl, false)
	if err != nil {
		errMsg = err.Error()
	}

	if errMsg != "" {
		return fail(ctx, 1, errMsg)
	}
	return success(ctx, nil)
}
<commit_msg>bugfix<commit_after>// Copyright 2014 The StudyGolang Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// http://studygolang.com
// Author: polaris	polaris@studygolang.com

package controller

import (
	"http/middleware"
	"logic"
	"net/http"
	"strings"

	"github.com/labstack/echo"
	"github.com/polaris1119/echoutils"
	"github.com/polaris1119/goutils"
	"github.com/polaris1119/logger"

	. "http"
	"model"
)

// 在需要评论（喜欢）且要回调的地方注册评论（喜欢）对象
func init() {
	// 注册评论（喜欢）对象
	logic.RegisterCommentObject(model.TypeArticle, logic.ArticleComment{})
	logic.RegisterLikeObject(model.TypeArticle, logic.ArticleLike{})
}

type ArticleController struct{}

// 注册路由
func (self ArticleController) RegisterRoute(g *echo.Group) {
	g.Get("/articles", self.ReadList)
	g.Get("/articles/crawl", self.Crawl)

	g.Get("/articles/:id", self.Detail)

	g.Match([]string{"GET", "POST"}, "/articles/new", self.Create, middleware.NeedLogin(), middleware.Sensivite(), middleware.BalanceCheck(), middleware.PublishNotice())
	g.Post("/articles/modify", self.Modify, middleware.NeedLogin(), middleware.Sensivite())
}

// ReadList 网友文章列表页
func (ArticleController) ReadList(ctx echo.Context) error {
	limit := 20

	lastId := goutils.MustInt(ctx.QueryParam("lastid"))
	articles := logic.DefaultArticle.FindBy(ctx, limit+5, lastId)
	if articles == nil {
		logger.Errorln("article controller: find article error")
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	num := len(articles)
	if num == 0 {
		if lastId == 0 {
			return render(ctx, "articles/list.html", map[string]interface{}{"articles": articles, "activeArticles": "active"})
		}
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	var (
		hasPrev, hasNext bool
		prevId, nextId   int
	)

	if lastId != 0 {
		prevId = lastId

		firstNoTopId := articles[0].Id
		for i := 0; i < num; i++ {
			if articles[i].Top != 1 {
				firstNoTopId = articles[i].Id
				break
			}
		}
		// 避免因为文章下线，导致判断错误（所以 > 5）
		if prevId-firstNoTopId > 5 {
			hasPrev = false
		} else {
			prevId += limit
			hasPrev = true
		}
	}

	if num > limit {
		hasNext = true
		articles = articles[:limit]
		nextId = articles[limit-1].Id
	} else {
		nextId = articles[num-1].Id
	}

	pageInfo := map[string]interface{}{
		"has_prev": hasPrev,
		"prev_id":  prevId,
		"has_next": hasNext,
		"next_id":  nextId,
	}

	// 获取当前用户喜欢对象信息
	me, ok := ctx.Get("user").(*model.Me)
	var likeFlags map[int]int
	if ok {
		likeFlags, _ = logic.DefaultLike.FindUserLikeObjects(ctx, me.Uid, model.TypeArticle, articles[0].Id, nextId)
	}

	return render(ctx, "articles/list.html", map[string]interface{}{"articles": articles, "activeArticles": "active", "page": pageInfo, "likeflags": likeFlags})
}

// Detail 文章详细页
func (ArticleController) Detail(ctx echo.Context) error {
	article, prevNext, err := logic.DefaultArticle.FindByIdAndPreNext(ctx, goutils.MustInt(ctx.Param("id")))
	if err != nil {
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	if article == nil || article.Id == 0 || article.Status == model.ArticleStatusOffline {
		return ctx.Redirect(http.StatusSeeOther, "/articles")
	}

	data := map[string]interface{}{
		"activeArticles": "active",
		"article":        article,
		"prev":           prevNext[0],
		"next":           prevNext[1],
	}

	me, ok := ctx.Get("user").(*model.Me)
	if ok {
		data["likeflag"] = logic.DefaultLike.HadLike(ctx, me.Uid, article.Id, model.TypeArticle)
		data["hadcollect"] = logic.DefaultFavorite.HadFavorite(ctx, me.Uid, article.Id, model.TypeArticle)

		logic.Views.Incr(Request(ctx), model.TypeArticle, article.Id, me.Uid)

		if !article.IsSelf || me.Uid != article.User.Uid {
			go logic.DefaultViewRecord.Record(article.Id, model.TypeArticle, me.Uid)
		}

		if me.IsRoot || (article.IsSelf && me.Uid == article.User.Uid) {
			data["view_user_num"] = logic.DefaultViewRecord.FindUserNum(ctx, article.Id, model.TypeArticle)
		}
	} else {
		logic.Views.Incr(Request(ctx), model.TypeArticle, article.Id)
	}

	// 为了阅读数即时看到
	article.Viewnum++

	return render(ctx, "articles/detail.html,common/comment.html", data)
}

// Create 发布新文章
func (ArticleController) Create(ctx echo.Context) error {
	title := ctx.FormValue("title")
	if title == "" || ctx.Request().Method() != "POST" {
		return render(ctx, "articles/new.html", map[string]interface{}{"activeArticles": "active"})
	}

	if ctx.FormValue("content") == "" || ctx.FormValue("txt") == "" {
		return fail(ctx, 1, "内容不能为空")
	}

	me := ctx.Get("user").(*model.Me)
	err := logic.DefaultArticle.Publish(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 2, "内部服务错误")
	}

	return success(ctx, nil)
}

// Modify 修改文章
func (ArticleController) Modify(ctx echo.Context) error {
	if ctx.FormValue("id") == "" || ctx.FormValue("content") == "" {
		return fail(ctx, 1, "内容不能为空")
	}
	article, err := logic.DefaultArticle.FindById(ctx, ctx.FormValue("id"))
	if err != nil {
		return fail(ctx, 2, "文章不存在")
	}

	me := ctx.Get("user").(*model.Me)
	if !logic.CanEdit(me, article) {
		return fail(ctx, 3, "没有修改权限")
	}

	errMsg, err := logic.DefaultArticle.Modify(echoutils.WrapEchoContext(ctx), me, ctx.FormParams())
	if err != nil {
		return fail(ctx, 4, errMsg)
	}

	return success(ctx, nil)
}

func (ArticleController) Crawl(ctx echo.Context) error {
	strUrl := ctx.QueryParam("url")

	var (
		errMsg string
		err    error
	)
	strUrl = strings.TrimSpace(strUrl)
	_, err = logic.DefaultArticle.ParseArticle(ctx, strUrl, false)
	if err != nil {
		errMsg = err.Error()
	}

	if errMsg != "" {
		return fail(ctx, 1, errMsg)
	}
	return success(ctx, nil)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"path/filepath"

	"github.com/hashicorp/go-checkpoint"
	"github.com/mitchellh/packer/command"
)

func init() {
	checkpointResult = make(chan *checkpoint.CheckResponse, 1)
}

var checkpointResult chan *checkpoint.CheckResponse

// runCheckpoint runs a HashiCorp Checkpoint request. You can read about
// Checkpoint here: https://github.com/hashicorp/go-checkpoint.
func runCheckpoint(c *config) {
	// If the user doesn't want checkpoint at all, then return.
	if c.DisableCheckpoint {
		log.Printf("[INFO] Checkpoint disabled. Not running.")
		checkpointResult <- nil
		return
	}

	configDir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERR] Checkpoint setup error: %s", err)
		checkpointResult <- nil
		return
	}

	version := Version
	if VersionPrerelease != "" {
		version += fmt.Sprintf(".%s", VersionPrerelease)
	}

	signaturePath := filepath.Join(configDir, "checkpoint_signature")
	if c.DisableCheckpointSignature {
		log.Printf("[INFO] Checkpoint signature disabled")
		signaturePath = ""
	}

	resp, err := checkpoint.Check(&checkpoint.CheckParams{
		Product:       "packer",
		Version:       version,
		SignatureFile: signaturePath,
		CacheFile:     filepath.Join(configDir, "checkpoint_cache"),
	})
	if err != nil {
		log.Printf("[ERR] Checkpoint error: %s", err)
		resp = nil
	}

	checkpointResult <- resp
}

// commandVersionCheck implements command.VersionCheckFunc and is used
// as the version checker.
func commandVersionCheck() (command.VersionCheckInfo, error) {
	// Wait for the result to come through
	info := <-checkpointResult
	if info == nil {
		var zero command.VersionCheckInfo
		return zero, nil
	}

	// Build the alerts that we may have received about our version
	alerts := make([]string, len(info.Alerts))
	for i, a := range info.Alerts {
		alerts[i] = a.Message
	}

	return command.VersionCheckInfo{
		Outdated: info.Outdated,
		Latest:   info.CurrentVersion,
		Alerts:   alerts,
	}, nil
}
<commit_msg>fixing version numbers: RCs should be labeled x.x.x-rcx<commit_after>package main

import (
	"fmt"
	"log"
	"path/filepath"

	"github.com/hashicorp/go-checkpoint"
	"github.com/mitchellh/packer/command"
)

func init() {
	checkpointResult = make(chan *checkpoint.CheckResponse, 1)
}

var checkpointResult chan *checkpoint.CheckResponse

// runCheckpoint runs a HashiCorp Checkpoint request. You can read about
// Checkpoint here: https://github.com/hashicorp/go-checkpoint.
func runCheckpoint(c *config) {
	// If the user doesn't want checkpoint at all, then return.
	if c.DisableCheckpoint {
		log.Printf("[INFO] Checkpoint disabled. Not running.")
		checkpointResult <- nil
		return
	}

	configDir, err := ConfigDir()
	if err != nil {
		log.Printf("[ERR] Checkpoint setup error: %s", err)
		checkpointResult <- nil
		return
	}

	version := Version
	if VersionPrerelease != "" {
		version += fmt.Sprintf("-%s", VersionPrerelease)
	}

	signaturePath := filepath.Join(configDir, "checkpoint_signature")
	if c.DisableCheckpointSignature {
		log.Printf("[INFO] Checkpoint signature disabled")
		signaturePath = ""
	}

	resp, err := checkpoint.Check(&checkpoint.CheckParams{
		Product:       "packer",
		Version:       version,
		SignatureFile: signaturePath,
		CacheFile:     filepath.Join(configDir, "checkpoint_cache"),
	})
	if err != nil {
		log.Printf("[ERR] Checkpoint error: %s", err)
		resp = nil
	}

	checkpointResult <- resp
}

// commandVersionCheck implements command.VersionCheckFunc and is used
// as the version checker.
func commandVersionCheck() (command.VersionCheckInfo, error) {
	// Wait for the result to come through
	info := <-checkpointResult
	if info == nil {
		var zero command.VersionCheckInfo
		return zero, nil
	}

	// Build the alerts that we may have received about our version
	alerts := make([]string, len(info.Alerts))
	for i, a := range info.Alerts {
		alerts[i] = a.Message
	}

	return command.VersionCheckInfo{
		Outdated: info.Outdated,
		Latest:   info.CurrentVersion,
		Alerts:   alerts,
	}, nil
}
<|endoftext|>


<commit_before>package acl

import (
	"io/ioutil"
	"os"
	"testing"
)

func TestChmod(t *testing.T) {
	p, err := ioutil.TempDir(os.TempDir(), "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(p)
	if err := Chmod(p, 0); err != nil {
		t.Fatal(err)
	}
	d, err := os.Open(p)
	if err == nil {
		d.Close()
		t.Fatal("owner able to access directory")
	}
	if err := Chmod(p, 0400); err != nil {
		t.Fatal(err)
	}
	d, err = os.Open(p)
	if err != nil {
		t.Fatal("owner unable to access directory")
	}
	d.Close()
}
<commit_msg>Updated Chmod() test.<commit_after>package acl

import (
	"io/ioutil"
	"os"
	"testing"
)

func TestChmod(t *testing.T) {
	f, err := ioutil.TempDir(os.TempFile(), "")
	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(f)
	if err := Chmod(f.Name(), 0); err != nil {
		t.Fatal(err)
	}
	d, err := os.Open(f.Name())
	if err == nil {
		d.Close()
		t.Fatal("owner able to access directory")
	}
	if err := Chmod(f.Name(), 0400); err != nil {
		t.Fatal(err)
	}
	d, err = os.Open(f.Name())
	if err != nil {
		t.Fatal("owner unable to access directory")
	}
	d.Close()
}
<|endoftext|>


<commit_before>package cmd

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/jvikstedt/alarmy/api"
	"github.com/jvikstedt/alarmy/schedule"
	"github.com/jvikstedt/alarmy/store"
	"github.com/spf13/cobra"
)

// serverCmd represents the server command
var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		// Store / Database Setup
		boltStore, err := store.NewBoltStore("alarmy_dev.db")
		if err != nil {
			printAndExitError(err, nil)
		}
		defer boltStore.Close()

		// Logger setup
		f, err := os.OpenFile("dev.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			printAndExitError(err, nil)
		}
		defer f.Close()
		logger := log.New(f, "", log.LstdFlags)

		// Scheduler setup
		scheduler := schedule.NewCronScheduler(logger)
		go scheduler.Start()
		defer scheduler.Stop()

		// Server & http.Handler setup
		api := api.NewApi(boltStore.Store(), logger, scheduler)
		handler, err := api.Handler()
		if err != nil {
			printAndExitError(err, logger)
		}
		s := http.Server{Addr: ":8080", Handler: handler}

		// Signalling
		stop := make(chan os.Signal, 1)
		signal.Notify(stop, os.Interrupt)

		go func() {
			<-stop

			ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
			defer cancel()

			// Shutdown server gracefully
			s.Shutdown(ctx)
		}()

		// Server startup
		if err := s.ListenAndServe(); err != nil {
			printAndExitError(err, logger)
		}
	},
}

func printAndExitError(a interface{}, logger *log.Logger) {
	if logger != nil {
		logger.Println(a)
	}
	fmt.Println(a)
	os.Exit(1)
}

func init() {
	RootCmd.AddCommand(serverCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// serverCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
<commit_msg>Server command better handling of errors and exit<commit_after>package cmd

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"time"

	"github.com/jvikstedt/alarmy/api"
	"github.com/jvikstedt/alarmy/schedule"
	"github.com/jvikstedt/alarmy/store"
	"github.com/spf13/cobra"
)

// serverCmd represents the server command
var serverCmd = &cobra.Command{
	Use:   "server",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		err := setupServer()
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	},
}

func setupServer() error {
	// Store / Database Setup
	boltStore, err := store.NewBoltStore("alarmy_dev.db")
	if err != nil {
		return err
	}
	defer boltStore.Close()

	// Logger setup
	f, err := os.OpenFile("dev.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return err
	}
	defer f.Close()
	logger := log.New(f, "", log.LstdFlags)

	// Scheduler setup
	scheduler := schedule.NewCronScheduler(logger)
	go scheduler.Start()
	defer scheduler.Stop()

	// Server & http.Handler setup
	api := api.NewApi(boltStore.Store(), logger, scheduler)
	handler, err := api.Handler()
	if err != nil {
		return err
	}
	s := http.Server{Addr: ":8080", Handler: handler}

	// Signalling
	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt)

	go func() {
		<-stop

		ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		// Shutdown server gracefully
		s.Shutdown(ctx)
	}()

	// Server startup
	if err := s.ListenAndServe(); err != nil {
		if err == http.ErrServerClosed {
			logger.Println(err)
		} else {
			return err
		}
	}

	return nil
}

func init() {
	RootCmd.AddCommand(serverCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// serverCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
<|endoftext|>


<commit_before>package cmd

type AppendConfig struct {

}

var _ Mode = &AppendConfig{}

func (config *AppendConfig) ExampleConfig() string {
	panic("NYI")
}

func (config *AppendConfig) ReadConfig(fname string) error {
	panic("NYI")
}

func (config *AppendConfig) validate() error {
	panic("NYI")
}

func (config *AppendConfig) Run(
	flags []string, gConfig *GlobalConfig, stdin []string,
) ([]string, error) {
	panic("NYI")
}<commit_msg>Removed append mode skeleton.<commit_after><|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"path"
	"time"

	"github.com/oberd/ecsy/ecs"
	"github.com/spf13/cobra"
)

var prevCount int
var poll bool

func printServiceStatus(cluster, service string) bool {
	serviceObj, err := ecs.FindService(cluster, service)
	failOnError(err, fmt.Sprintf("Problem finding service: %s %s\n", cluster, service))
	fmt.Printf("Cluster:\t\t%s\n", cluster)
	fmt.Printf("Service:\t\t%s\n", service)
	fmt.Printf("Task Definition:\t%s\n", path.Base(*serviceObj.TaskDefinition))
	fmt.Println("Deployments:")
	for _, d := range serviceObj.Deployments {
		fmt.Printf("%s %s (%d Desired, %d Pending, %d Running) %v\n", path.Base(*d.TaskDefinition), *d.Status, *d.DesiredCount, *d.PendingCount, *d.RunningCount, *d.CreatedAt)
	}
	currCount := len(serviceObj.Deployments)
	hasChanged := prevCount != 1000 && currCount != prevCount
	prevCount = currCount
	return !hasChanged
}

// statusCmd represents the status command
var statusCmd = &cobra.Command{
	Use:   "status",
	Short: "View current cluster or service deployment status",
	Long:  "View current cluster or service deployment status",
	Run: func(cmd *cobra.Command, args []string) {
		cluster, service := ServiceChooser(args)
		prevCount = 1000
		printServiceStatus(cluster, service)
		for poll {
			time.Sleep(15 * time.Second)
			poll = printServiceStatus(cluster, service)
		}
	},
}

func init() {
	RootCmd.AddCommand(statusCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// statusCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	statusCmd.Flags().BoolVarP(&poll, "poll", "p", false, "Poll at a 15 second interval until a change in deployments occurs")

}
<commit_msg>adds instances to status command<commit_after>package cmd

import (
	"fmt"
	"path"
	"strings"
	"time"

	"github.com/oberd/ecsy/ecs"
	"github.com/spf13/cobra"
)

var prevCount int
var poll bool

func printServiceStatus(cluster, service string) bool {
	serviceObj, err := ecs.FindService(cluster, service)
	failOnError(err, fmt.Sprintf("Problem finding service: %s %s\n", cluster, service))
	instances, err := ecs.GetContainerInstances(cluster, service)
	failOnError(err, "")
	fmt.Printf("Cluster:\t\t%s\n", cluster)
	fmt.Printf("Service:\t\t%s\n", service)
	fmt.Printf("Task Definition:\t%s\n", path.Base(*serviceObj.TaskDefinition))
	fmt.Printf("Instances:\n\t%s\n", strings.Join(instances, "\n\t"))
	fmt.Println("Deployments:")
	for _, d := range serviceObj.Deployments {
		fmt.Printf("%s %s (%d Desired, %d Pending, %d Running) %v\n", path.Base(*d.TaskDefinition), *d.Status, *d.DesiredCount, *d.PendingCount, *d.RunningCount, *d.CreatedAt)
	}
	currCount := len(serviceObj.Deployments)
	hasChanged := prevCount != 1000 && currCount != prevCount
	prevCount = currCount
	return !hasChanged
}

// statusCmd represents the status command
var statusCmd = &cobra.Command{
	Use:   "status",
	Short: "View current cluster or service deployment status",
	Long:  "View current cluster or service deployment status",
	Run: func(cmd *cobra.Command, args []string) {
		cluster, service := ServiceChooser(args)
		prevCount = 1000
		printServiceStatus(cluster, service)
		for poll {
			time.Sleep(15 * time.Second)
			poll = printServiceStatus(cluster, service)
		}
	},
}

func init() {
	RootCmd.AddCommand(statusCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// statusCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	statusCmd.Flags().BoolVarP(&poll, "poll", "p", false, "Poll at a 15 second interval until a change in deployments occurs")

}
<|endoftext|>


<commit_before>package cmd

import (
	"context"
	"fmt"
	"github.com/spf13/cobra"
	"github.com/zrepl/zrepl/logger"
	"os"
	"os/signal"
	"syscall"
	"time"
)

// daemonCmd represents the daemon command
var daemonCmd = &cobra.Command{
	Use:   "daemon",
	Short: "start daemon",
	Run:   doDaemon,
}

func init() {
	RootCmd.AddCommand(daemonCmd)
}

type Job interface {
	JobName() string
	JobStart(ctxt context.Context)
}

func doDaemon(cmd *cobra.Command, args []string) {

	conf, err := ParseConfig(rootArgs.configFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error parsing config: %s", err)
		os.Exit(1)
	}

	log := logger.NewLogger(conf.Global.logging.Outlets, 1*time.Second)

	log.Info(NewZreplVersionInformation().String())
	log.Debug("starting daemon")
	ctx := context.WithValue(context.Background(), contextKeyLog, log)
	ctx = context.WithValue(ctx, contextKeyLog, log)

	d := NewDaemon(conf)
	d.Loop(ctx)

}

type contextKey string

const (
	contextKeyLog contextKey = contextKey("log")
)

type Daemon struct {
	conf *Config
}

func NewDaemon(initialConf *Config) *Daemon {
	return &Daemon{initialConf}
}

func (d *Daemon) Loop(ctx context.Context) {

	log := ctx.Value(contextKeyLog).(Logger)

	ctx, cancel := context.WithCancel(ctx)

	sigChan := make(chan os.Signal, 1)
	finishs := make(chan Job)

	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	log.Info("starting jobs from config")
	i := 0
	for _, job := range d.conf.Jobs {
		logger := log.WithField(logJobField, job.JobName())
		logger.Info("starting")
		i++
		jobCtx := context.WithValue(ctx, contextKeyLog, logger)
		go func(j Job) {
			j.JobStart(jobCtx)
			finishs <- j
		}(job)
	}

	finishCount := 0
outer:
	for {
		select {
		case <-finishs:
			finishCount++
			if finishCount == len(d.conf.Jobs) {
				log.Info("all jobs finished")
				break outer
			}

		case sig := <-sigChan:
			log.WithField("signal", sig).Info("received signal")
			log.Info("cancelling all jobs")
			cancel()
		}
	}

	signal.Stop(sigChan)
	cancel() // make go vet happy

	log.Info("exiting")

}
<commit_msg>daemon: Task abstraction + TaskStatus<commit_after>package cmd

import (
	"container/list"
	"context"
	"fmt"
	"github.com/spf13/cobra"
	"github.com/zrepl/zrepl/logger"
	"io"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"
)

// daemonCmd represents the daemon command
var daemonCmd = &cobra.Command{
	Use:   "daemon",
	Short: "start daemon",
	Run:   doDaemon,
}

func init() {
	RootCmd.AddCommand(daemonCmd)
}

type Job interface {
	JobName() string
	JobStart(ctxt context.Context)
}

func doDaemon(cmd *cobra.Command, args []string) {

	conf, err := ParseConfig(rootArgs.configFile)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error parsing config: %s", err)
		os.Exit(1)
	}

	log := logger.NewLogger(conf.Global.logging.Outlets, 1*time.Second)

	log.Info(NewZreplVersionInformation().String())
	log.Debug("starting daemon")
	ctx := context.WithValue(context.Background(), contextKeyLog, log)
	ctx = context.WithValue(ctx, contextKeyLog, log)

	d := NewDaemon(conf)
	d.Loop(ctx)

}

type contextKey string

const (
	contextKeyLog contextKey = contextKey("log")
)

type Daemon struct {
	conf *Config
}

func NewDaemon(initialConf *Config) *Daemon {
	return &Daemon{initialConf}
}

func (d *Daemon) Loop(ctx context.Context) {

	log := ctx.Value(contextKeyLog).(Logger)

	ctx, cancel := context.WithCancel(ctx)

	sigChan := make(chan os.Signal, 1)
	finishs := make(chan Job)

	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

	log.Info("starting jobs from config")
	i := 0
	for _, job := range d.conf.Jobs {
		logger := log.WithField(logJobField, job.JobName())
		logger.Info("starting")
		i++
		jobCtx := context.WithValue(ctx, contextKeyLog, logger)
		go func(j Job) {
			j.JobStart(jobCtx)
			finishs <- j
		}(job)
	}

	finishCount := 0
outer:
	for {
		select {
		case <-finishs:
			finishCount++
			if finishCount == len(d.conf.Jobs) {
				log.Info("all jobs finished")
				break outer
			}

		case sig := <-sigChan:
			log.WithField("signal", sig).Info("received signal")
			log.Info("cancelling all jobs")
			cancel()
		}
	}

	signal.Stop(sigChan)
	cancel() // make go vet happy

	log.Info("exiting")

}

// Representation of a Task's status
type TaskStatus struct {
	Name string
	// Whether the task is idle.
	Idle bool
	// The stack of activities the task is currently executing.
	// The first element is the root activity and equal to Name.
	ActivityStack []string
	// Number of bytes received by the task since it last left idle state.
	ProgressRx int64
	// Number of bytes sent by the task since it last left idle state.
	ProgressTx int64
	// Log entries emitted by the task since it last left idle state.
	// Only contains the log entries emitted through the task's logger
	// (provided by Task.Log()).
	LogEntries []logger.Entry
	// The maximum log level of LogEntries.
	// Only valid if len(LogEntries) > 0.
	MaxLogLevel logger.Level
}

// An instance of Task tracks  a single thread of activity that is part of a Job.
type Task struct {
	// Stack of activities the task is currently in
	// Members are instances of taskActivity
	activities *list.List
	// Protects Task members from modification
	rwl sync.RWMutex
}

// Structure that describes the progress a Task has made
type taskProgress struct {
	rx         int64
	tx         int64
	lastUpdate time.Time
	logEntries []logger.Entry
	mtx        sync.RWMutex
}

func newTaskProgress() (p *taskProgress) {
	return &taskProgress{
		logEntries: make([]logger.Entry, 0),
	}
}

func (p *taskProgress) UpdateIO(drx, dtx int64) {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	p.rx += drx
	p.tx += dtx
	p.lastUpdate = time.Now()
}

func (p *taskProgress) UpdateLogEntry(entry logger.Entry) {
	p.mtx.Lock()
	defer p.mtx.Unlock()
	// FIXME: ensure maximum size (issue #48)
	p.logEntries = append(p.logEntries, entry)
	p.lastUpdate = time.Now()
}

func (p *taskProgress) DeepCopy() (out taskProgress) {
	p.mtx.RLock()
	defer p.mtx.RUnlock()
	out.rx, out.tx = p.rx, p.tx
	out.logEntries = make([]logger.Entry, len(p.logEntries))
	for i := range p.logEntries {
		out.logEntries[i] = p.logEntries[i]
	}
	return
}

// returns a copy of this taskProgress, the mutex carries no semantic value
func (p *taskProgress) Read() (out taskProgress) {
	p.mtx.RLock()
	defer p.mtx.RUnlock()
	return p.DeepCopy()
}

// Element of a Task's activity stack
type taskActivity struct {
	name   string
	idle   bool
	logger *logger.Logger
	// The progress of the task that is updated by UpdateIO() and UpdateLogEntry()
	//
	// Progress happens on a task-level and is thus global to the task.
	// That's why progress is just a pointer to the current taskProgress:
	// we reset progress when leaving the idle root activity
	progress *taskProgress
}

func NewTask(name string, lg *logger.Logger) *Task {
	t := &Task{
		activities: list.New(),
	}
	rootLogger := lg.ReplaceField(logTaskField, name).
		WithOutlet(t, logger.Debug)
	rootAct := &taskActivity{name, true, rootLogger, newTaskProgress()}
	t.activities.PushFront(rootAct)
	return t
}

// callers must hold t.rwl
func (t *Task) cur() *taskActivity {
	return t.activities.Front().Value.(*taskActivity)
}

// buildActivityStack returns the stack of activity names
// t.rwl must be held, but the slice can be returned since strings are immutable
func (t *Task) buildActivityStack() []string {
	comps := make([]string, 0, t.activities.Len())
	for e := t.activities.Back(); e != nil; e = e.Prev() {
		act := e.Value.(*taskActivity)
		comps = append(comps, act.name)
	}
	return comps
}

// Start a sub-activity.
// Must always be matched with a call to t.Finish()
// --- consider using defer for this purpose.
func (t *Task) Enter(activity string) {
	t.rwl.Lock()
	defer t.rwl.Unlock()

	prev := t.cur()
	if prev.idle {
		// reset progress when leaving idle task
		// we leave the old progress dangling to have the user not worry about
		prev.progress = newTaskProgress()
	}
	act := &taskActivity{activity, false, nil, prev.progress}
	t.activities.PushFront(act)
	stack := t.buildActivityStack()
	activityField := strings.Join(stack, ".")
	act.logger = prev.logger.ReplaceField(logTaskField, activityField)

}

func (t *Task) UpdateProgress(dtx, drx int64) {
	t.rwl.RLock()
	p := t.cur().progress // protected by own rwlock
	t.rwl.RUnlock()
	p.UpdateIO(dtx, drx)
}

// Returns a wrapper io.Reader that updates this task's _current_ progress value.
// Progress updates after this task resets its progress value are discarded.
func (t *Task) ProgressUpdater(r io.Reader) *IOProgressUpdater {
	t.rwl.RLock()
	defer t.rwl.RUnlock()
	return &IOProgressUpdater{r, t.cur().progress}
}

func (t *Task) Status() *TaskStatus {
	t.rwl.RLock()
	defer t.rwl.RUnlock()
	// NOTE
	// do not return any state in TaskStatus that is protected by t.rwl

	cur := t.cur()
	stack := t.buildActivityStack()
	prog := cur.progress.Read()

	var maxLevel logger.Level
	for _, entry := range prog.logEntries {
		if maxLevel < entry.Level {
			maxLevel = entry.Level
		}
	}

	s := &TaskStatus{
		Name:          stack[0],
		ActivityStack: stack,
		Idle:          cur.idle,
		ProgressRx:    prog.rx,
		ProgressTx:    prog.tx,
		LogEntries:    prog.logEntries,
		MaxLogLevel:   maxLevel,
	}

	return s
}

// Finish a sub-activity.
// Corresponds to a preceding call to t.Enter()
func (t *Task) Finish() {
	t.rwl.Lock()
	defer t.rwl.Unlock()
	top := t.activities.Front()
	if top.Next() == nil {
		return // cannot remove root activity
	}
	t.activities.Remove(top)

}

// Returns a logger derived from the logger passed to the constructor function.
// The logger's task field contains the current activity stack joined by '.'.
func (t *Task) Log() *logger.Logger {
	t.rwl.RLock()
	defer t.rwl.RUnlock()
	return t.cur().logger
}

// implement logger.Outlet interface
func (t *Task) WriteEntry(ctx context.Context, entry logger.Entry) error {
	t.rwl.RLock()
	defer t.rwl.RUnlock()
	t.cur().progress.UpdateLogEntry(entry)
	return nil
}

type IOProgressUpdater struct {
	r io.Reader
	p *taskProgress
}

func (u *IOProgressUpdater) Read(p []byte) (n int, err error) {
	n, err = u.r.Read(p)
	u.p.UpdateIO(int64(n), 0)
	return
}
<|endoftext|>