<commit_before>package irckit

import (
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/mattermost/platform/model"
	"github.com/sorcix/irc"
)

// Channel is a representation of a room in our server
type Channel interface {
	Prefixer

	// ID is a normalized unique identifier for the channel
	ID() string

	// Created returns the time when the Channel was created.
	Created() time.Time

	// Names returns a sorted slice of Nicks in the channel
	Names() []string

	// Users returns a slice of Users in the channel.
	Users() []*User

	// HasUser returns whether a User is in the channel.
	HasUser(*User) bool

	// Invite prompts the User to join the Channel on behalf of Prefixer.
	Invite(from Prefixer, u *User) error

	// Join introduces the User to the channel (handler for JOIN).
	Join(u *User) error

	// Part removes the User from the channel (handler for PART).
	Part(u *User, text string)

	// Message transmits a message from a User to the channel (handler for PRIVMSG).
	Message(u *User, text string)

	// Service returns the service that set the channel
	Service() string

	// Topic sets the topic of the channel (handler for TOPIC).
	Topic(from Prefixer, text string)

	// GetTopic gets the topic of the channel
	GetTopic() string

	// Unlink will disassociate the Channel from its Server.
	Unlink()

	// Len returns the number of Users in the channel.
	Len() int

	// String returns the name of the channel
	String() string

	// Spoof message
	SpoofMessage(from string, text string)

	// Spoof notice
	SpoofNotice(from string, text string)
}

type channel struct {
	created time.Time
	name    string
	server  Server
	id      string
	service string

	mu       sync.RWMutex
	topic    string
	usersIdx map[*User]struct{}
}

// NewChannel returns a Channel implementation for a given Server.
func NewChannel(server Server, channelId string, name string, service string) Channel {
	return &channel{
		created:  time.Now(),
		server:   server,
		id:       channelId,
		name:     name,
		service:  service,
		usersIdx: map[*User]struct{}{},
	}
}

func (ch *channel) GetTopic() string {
	return ch.topic
}

func (ch *channel) Prefix() *irc.Prefix {
	return ch.server.Prefix()
}

func (ch *channel) Service() string {
	return ch.service
}

func (ch *channel) String() string {
	return ch.name
}

// Created returns the time when the Channel was created.
func (ch *channel) Created() time.Time {
	return ch.created
}

// ID returns a normalized unique identifier for the channel.
func (ch *channel) ID() string {
	return ID(ch.id)
}

func (ch *channel) Message(from *User, text string) {
	for len(text) > 400 {
		msg := &irc.Message{
			Prefix:   from.Prefix(),
			Command:  irc.PRIVMSG,
			Params:   []string{ch.name},
			Trailing: text[:400] + "\n",
		}
		ch.mu.RLock()
		for to := range ch.usersIdx {
			to.Encode(msg)
		}
		ch.mu.RUnlock()
		text = text[400:]
	}
	msg := &irc.Message{
		Prefix:   from.Prefix(),
		Command:  irc.PRIVMSG,
		Params:   []string{ch.name},
		Trailing: text,
	}
	ch.mu.RLock()
	for to := range ch.usersIdx {
		to.Encode(msg)
	}
	ch.mu.RUnlock()
}

// Quit will remove the user from the channel and emit a PART message.
func (ch *channel) Part(u *User, text string) {
	msg := &irc.Message{
		Prefix:   u.Prefix(),
		Command:  irc.PART,
		Params:   []string{ch.name},
		Trailing: text,
	}
	ch.mu.Lock()
	if _, ok := ch.usersIdx[u]; !ok {
		ch.mu.Unlock()
		u.Encode(&irc.Message{
			Prefix:   ch.Prefix(),
			Command:  irc.ERR_NOTONCHANNEL,
			Params:   []string{ch.name},
			Trailing: "You're not on that channel",
		})
		return
	}
	u.Encode(msg)
	delete(ch.usersIdx, u)
	ch.mu.Unlock()
	u.Lock()
	delete(u.channels, ch)
	u.Unlock()
}

// Unlink will disassociate the Channel from the Server.
func (ch *channel) Unlink() {
	ch.server.UnlinkChannel(ch)
}

// Close will evict all users in the channel.
func (ch *channel) Close() error {
	ch.mu.Lock()
	for to := range ch.usersIdx {
		to.Encode(&irc.Message{
			Prefix:  to.Prefix(),
			Command: irc.PART,
			Params:  []string{ch.name},
		})
	}
	ch.usersIdx = map[*User]struct{}{}
	ch.mu.Unlock()
	return nil
}

// Invite prompts the User to join the Channel on behalf of Prefixer.
func (ch *channel) Invite(from Prefixer, u *User) error {
	return u.Encode(&irc.Message{
		Prefix:  from.Prefix(),
		Command: irc.INVITE,
		Params:  []string{u.Nick, ch.name},
	})
	// TODO: Save state that the user is invited?
}

// Topic sets the topic of the channel (handler for TOPIC).
func (ch *channel) Topic(from Prefixer, text string) {
	ch.mu.RLock()
	ch.topic = text
	// no newlines in topic
	ch.topic = strings.Replace(ch.topic, "\n", " ", -1)

	msg := &irc.Message{
		Prefix:   from.Prefix(),
		Command:  irc.TOPIC,
		Params:   []string{ch.name},
		Trailing: text,
	}
	for to := range ch.usersIdx {
		to.Encode(msg)
	}

	ch.mu.RUnlock()
}

// Join introduces the User to the channel (sends relevant messages, stores).
func (ch *channel) Join(u *User) error {
	// TODO: Check if user is already here?
	ch.mu.Lock()
	if _, exists := ch.usersIdx[u]; exists {
		ch.mu.Unlock()
		return nil
	}
	topic := ch.topic
	ch.usersIdx[u] = struct{}{}
	ch.mu.Unlock()
	u.Lock()
	u.channels[ch] = struct{}{}
	u.Unlock()

	msg := &irc.Message{
		Prefix:  u.Prefix(),
		Command: irc.JOIN,
		Params:  []string{ch.name},
	}

	// only send join messages to real users
	for to := range ch.usersIdx {
		if to.MmGhostUser == false {
			to.Encode(msg)
		}
	}

	msgs := []*irc.Message{}
	if topic != "" {
		msgs = append(msgs, &irc.Message{
			Prefix:   ch.Prefix(),
			Command:  irc.RPL_TOPIC,
			Params:   []string{u.Nick, ch.name},
			Trailing: topic,
		})
	}

	line := ""
	i := 0
	for _, name := range ch.Names() {
		if i+len(name) < 400 {
			line += name + " "
			i += len(name)
		} else {
			msgs = append(msgs, &irc.Message{
				Prefix:   ch.Prefix(),
				Command:  irc.RPL_NAMREPLY,
				Params:   []string{u.Nick, "=", ch.name},
				Trailing: line,
			})
			line = ""
			line += name + " "
			i = len(name)
		}
	}
	msgs = append(msgs, &irc.Message{
		Prefix:   ch.Prefix(),
		Command:  irc.RPL_NAMREPLY,
		Params:   []string{u.Nick, "=", ch.name},
		Trailing: line,
	})

	msgs = append(msgs, &irc.Message{
		Prefix:   ch.Prefix(),
		Params:   []string{u.Nick, ch.name},
		Command:  irc.RPL_ENDOFNAMES,
		Trailing: "End of /NAMES list.",
	},
	)

	return u.Encode(msgs...)
}

func (ch *channel) HasUser(u *User) bool {
	ch.mu.RLock()
	_, ok := ch.usersIdx[u]
	ch.mu.RUnlock()
	return ok
}

// Users returns an unsorted slice of users who are in the channel.
func (ch *channel) Users() []*User {
	ch.mu.RLock()
	users := make([]*User, 0, len(ch.usersIdx))
	for u := range ch.usersIdx {
		users = append(users, u)
	}
	ch.mu.RUnlock()
	return users
}

// Names returns a sorted slice of Nick strings of users who are in the channel.
func (ch *channel) Names() []string {
	users := ch.Users()
	names := make([]string, 0, len(users))
	for _, u := range users {
		if strings.Contains(u.Roles, model.SYSTEM_ADMIN_ROLE_ID) {
			names = append(names, "@"+u.Nick)
		} else {
			names = append(names, u.Nick)
		}
	}
	// TODO: Append in sorted order?
	sort.Strings(names)
	return names
}

// Len returns the number of users in the channel.
func (ch *channel) Len() int {
	ch.mu.RLock()
	defer ch.mu.RUnlock()
	return len(ch.usersIdx)
}
<commit_msg>Fix newlines in topic to client. Closes 42wim/matterircd#163<commit_after>package irckit

import (
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/mattermost/platform/model"
	"github.com/sorcix/irc"
)

// Channel is a representation of a room in our server
type Channel interface {
	Prefixer

	// ID is a normalized unique identifier for the channel
	ID() string

	// Created returns the time when the Channel was created.
	Created() time.Time

	// Names returns a sorted slice of Nicks in the channel
	Names() []string

	// Users returns a slice of Users in the channel.
	Users() []*User

	// HasUser returns whether a User is in the channel.
	HasUser(*User) bool

	// Invite prompts the User to join the Channel on behalf of Prefixer.
	Invite(from Prefixer, u *User) error

	// Join introduces the User to the channel (handler for JOIN).
	Join(u *User) error

	// Part removes the User from the channel (handler for PART).
	Part(u *User, text string)

	// Message transmits a message from a User to the channel (handler for PRIVMSG).
	Message(u *User, text string)

	// Service returns the service that set the channel
	Service() string

	// Topic sets the topic of the channel (handler for TOPIC).
	Topic(from Prefixer, text string)

	// GetTopic gets the topic of the channel
	GetTopic() string

	// Unlink will disassociate the Channel from its Server.
	Unlink()

	// Len returns the number of Users in the channel.
	Len() int

	// String returns the name of the channel
	String() string

	// Spoof message
	SpoofMessage(from string, text string)

	// Spoof notice
	SpoofNotice(from string, text string)
}

type channel struct {
	created time.Time
	name    string
	server  Server
	id      string
	service string

	mu       sync.RWMutex
	topic    string
	usersIdx map[*User]struct{}
}

// NewChannel returns a Channel implementation for a given Server.
func NewChannel(server Server, channelId string, name string, service string) Channel {
	return &channel{
		created:  time.Now(),
		server:   server,
		id:       channelId,
		name:     name,
		service:  service,
		usersIdx: map[*User]struct{}{},
	}
}

func (ch *channel) GetTopic() string {
	return ch.topic
}

func (ch *channel) Prefix() *irc.Prefix {
	return ch.server.Prefix()
}

func (ch *channel) Service() string {
	return ch.service
}

func (ch *channel) String() string {
	return ch.name
}

// Created returns the time when the Channel was created.
func (ch *channel) Created() time.Time {
	return ch.created
}

// ID returns a normalized unique identifier for the channel.
func (ch *channel) ID() string {
	return ID(ch.id)
}

func (ch *channel) Message(from *User, text string) {
	for len(text) > 400 {
		msg := &irc.Message{
			Prefix:   from.Prefix(),
			Command:  irc.PRIVMSG,
			Params:   []string{ch.name},
			Trailing: text[:400] + "\n",
		}
		ch.mu.RLock()
		for to := range ch.usersIdx {
			to.Encode(msg)
		}
		ch.mu.RUnlock()
		text = text[400:]
	}
	msg := &irc.Message{
		Prefix:   from.Prefix(),
		Command:  irc.PRIVMSG,
		Params:   []string{ch.name},
		Trailing: text,
	}
	ch.mu.RLock()
	for to := range ch.usersIdx {
		to.Encode(msg)
	}
	ch.mu.RUnlock()
}

// Quit will remove the user from the channel and emit a PART message.
func (ch *channel) Part(u *User, text string) {
	msg := &irc.Message{
		Prefix:   u.Prefix(),
		Command:  irc.PART,
		Params:   []string{ch.name},
		Trailing: text,
	}
	ch.mu.Lock()
	if _, ok := ch.usersIdx[u]; !ok {
		ch.mu.Unlock()
		u.Encode(&irc.Message{
			Prefix:   ch.Prefix(),
			Command:  irc.ERR_NOTONCHANNEL,
			Params:   []string{ch.name},
			Trailing: "You're not on that channel",
		})
		return
	}
	u.Encode(msg)
	delete(ch.usersIdx, u)
	ch.mu.Unlock()
	u.Lock()
	delete(u.channels, ch)
	u.Unlock()
}

// Unlink will disassociate the Channel from the Server.
func (ch *channel) Unlink() {
	ch.server.UnlinkChannel(ch)
}

// Close will evict all users in the channel.
func (ch *channel) Close() error {
	ch.mu.Lock()
	for to := range ch.usersIdx {
		to.Encode(&irc.Message{
			Prefix:  to.Prefix(),
			Command: irc.PART,
			Params:  []string{ch.name},
		})
	}
	ch.usersIdx = map[*User]struct{}{}
	ch.mu.Unlock()
	return nil
}

// Invite prompts the User to join the Channel on behalf of Prefixer.
func (ch *channel) Invite(from Prefixer, u *User) error {
	return u.Encode(&irc.Message{
		Prefix:  from.Prefix(),
		Command: irc.INVITE,
		Params:  []string{u.Nick, ch.name},
	})
	// TODO: Save state that the user is invited?
}

// Topic sets the topic of the channel (handler for TOPIC).
func (ch *channel) Topic(from Prefixer, text string) {
	ch.mu.RLock()
	ch.topic = text
	// no newlines in topic
	ch.topic = strings.Replace(ch.topic, "\n", " ", -1)

	msg := &irc.Message{
		Prefix:   from.Prefix(),
		Command:  irc.TOPIC,
		Params:   []string{ch.name},
		Trailing: ch.topic,
	}
	for to := range ch.usersIdx {
		to.Encode(msg)
	}

	ch.mu.RUnlock()
}

// Join introduces the User to the channel (sends relevant messages, stores).
func (ch *channel) Join(u *User) error {
	// TODO: Check if user is already here?
	ch.mu.Lock()
	if _, exists := ch.usersIdx[u]; exists {
		ch.mu.Unlock()
		return nil
	}
	topic := ch.topic
	ch.usersIdx[u] = struct{}{}
	ch.mu.Unlock()
	u.Lock()
	u.channels[ch] = struct{}{}
	u.Unlock()

	msg := &irc.Message{
		Prefix:  u.Prefix(),
		Command: irc.JOIN,
		Params:  []string{ch.name},
	}

	// only send join messages to real users
	for to := range ch.usersIdx {
		if to.MmGhostUser == false {
			to.Encode(msg)
		}
	}

	msgs := []*irc.Message{}
	if topic != "" {
		msgs = append(msgs, &irc.Message{
			Prefix:   ch.Prefix(),
			Command:  irc.RPL_TOPIC,
			Params:   []string{u.Nick, ch.name},
			Trailing: topic,
		})
	}

	line := ""
	i := 0
	for _, name := range ch.Names() {
		if i+len(name) < 400 {
			line += name + " "
			i += len(name)
		} else {
			msgs = append(msgs, &irc.Message{
				Prefix:   ch.Prefix(),
				Command:  irc.RPL_NAMREPLY,
				Params:   []string{u.Nick, "=", ch.name},
				Trailing: line,
			})
			line = ""
			line += name + " "
			i = len(name)
		}
	}
	msgs = append(msgs, &irc.Message{
		Prefix:   ch.Prefix(),
		Command:  irc.RPL_NAMREPLY,
		Params:   []string{u.Nick, "=", ch.name},
		Trailing: line,
	})

	msgs = append(msgs, &irc.Message{
		Prefix:   ch.Prefix(),
		Params:   []string{u.Nick, ch.name},
		Command:  irc.RPL_ENDOFNAMES,
		Trailing: "End of /NAMES list.",
	},
	)

	return u.Encode(msgs...)
}

func (ch *channel) HasUser(u *User) bool {
	ch.mu.RLock()
	_, ok := ch.usersIdx[u]
	ch.mu.RUnlock()
	return ok
}

// Users returns an unsorted slice of users who are in the channel.
func (ch *channel) Users() []*User {
	ch.mu.RLock()
	users := make([]*User, 0, len(ch.usersIdx))
	for u := range ch.usersIdx {
		users = append(users, u)
	}
	ch.mu.RUnlock()
	return users
}

// Names returns a sorted slice of Nick strings of users who are in the channel.
func (ch *channel) Names() []string {
	users := ch.Users()
	names := make([]string, 0, len(users))
	for _, u := range users {
		if strings.Contains(u.Roles, model.SYSTEM_ADMIN_ROLE_ID) {
			names = append(names, "@"+u.Nick)
		} else {
			names = append(names, u.Nick)
		}
	}
	// TODO: Append in sorted order?
	sort.Strings(names)
	return names
}

// Len returns the number of users in the channel.
func (ch *channel) Len() int {
	ch.mu.RLock()
	defer ch.mu.RUnlock()
	return len(ch.usersIdx)
}
<|endoftext|>


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

package sqlbuilder

import (
	"context"
	"database/sql"
	"strconv"

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

// SelectStmt 查询语句
type SelectStmt struct {
	engine    Engine
	dialect   Dialect
	table     string
	where     *WhereStmt
	cols      []string
	distinct  bool
	forupdate bool

	// COUNT 查询的列内容
	countExpr string

	joins  []*join
	orders *SQLBuilder
	group  string

	havingQuery string
	havingVals  []interface{}

	limitQuery string
	limitVals  []interface{}
}

type join struct {
	typ   string
	on    string
	table string
}

// Select 声明一条 Select 语句
func Select(e Engine, d Dialect) *SelectStmt {
	return &SelectStmt{
		engine:  e,
		dialect: d,
		where:   Where(),
	}
}

// Distinct 声明一条 Select 语句的 Distinct
//
// 若指定了此值，则 Select() 所指定的列，均为 Distinct 之后的列。
func (stmt *SelectStmt) Distinct() *SelectStmt {
	stmt.distinct = true
	return stmt
}

// Reset 重置语句
func (stmt *SelectStmt) Reset() {
	stmt.table = ""
	stmt.where.Reset()
	stmt.cols = stmt.cols[:0]
	stmt.distinct = false
	stmt.forupdate = false

	stmt.countExpr = ""

	stmt.joins = stmt.joins[:0]
	if stmt.orders != nil {
		stmt.orders.Reset()
	}
	stmt.group = ""

	stmt.havingQuery = ""
	stmt.havingVals = nil

	stmt.limitQuery = ""
	stmt.limitVals = nil
}

// SQL 获取 SQL 语句及对应的参数
func (stmt *SelectStmt) SQL() (string, []interface{}, error) {
	if stmt.table == "" {
		return "", nil, ErrTableIsEmpty
	}

	if len(stmt.cols) == 0 && stmt.countExpr == "" {
		return "", nil, ErrColumnsIsEmpty
	}

	buf := New("SELECT ")
	args := make([]interface{}, 0, 10)

	if stmt.countExpr == "" {
		if stmt.distinct {
			buf.WriteString("DISTINCT ")
		}
		for _, c := range stmt.cols {
			buf.WriteString(c)
			buf.WriteByte(',')
		}
		buf.TruncateLast(1)
	} else {
		buf.WriteString(stmt.countExpr)
	}

	buf.WriteString(" FROM ")
	buf.WriteString(stmt.table)

	// join
	if len(stmt.joins) > 0 {
		buf.WriteByte(' ')
		for _, join := range stmt.joins {
			buf.WriteString(join.typ)
			buf.WriteString(" JOIN ")
			buf.WriteString(join.table)
			buf.WriteString(" ON ")
			buf.WriteString(join.on)
			buf.WriteByte(',')
		}
		buf.TruncateLast(1)
	}

	// where
	wq, wa, err := stmt.where.SQL()
	if err != nil {
		return "", nil, err
	}
	if wq != "" {
		buf.WriteString(" WHERE ")
		buf.WriteString(wq)
		args = append(args, wa...)
	}

	// group by
	if stmt.group != "" {
		buf.WriteString(stmt.group)
	}

	// having
	if stmt.havingQuery != "" {
		buf.WriteString(stmt.havingQuery)
		args = append(args, stmt.havingVals...)
	}

	// order by
	if stmt.orders != nil && stmt.orders.Len() > 0 {
		buf.WriteString(stmt.orders.String())
	}

	// limit
	if stmt.countExpr == "" && stmt.limitQuery != "" {
		buf.WriteString(stmt.limitQuery)
		args = append(args, stmt.limitVals...)
	}

	// for update
	if stmt.forupdate {
		buf.WriteString(" FOR UPDATE")
	}

	return buf.String(), args, nil
}

// Select 指定列名
func (stmt *SelectStmt) Select(cols ...string) *SelectStmt {
	if stmt.cols == nil {
		stmt.cols = make([]string, 0, len(cols))
	}

	stmt.cols = append(stmt.cols, cols...)
	return stmt
}

// From 指定表名
func (stmt *SelectStmt) From(table string) *SelectStmt {
	stmt.table = table

	return stmt
}

// Having 指定 having 语句
func (stmt *SelectStmt) Having(expr string, args ...interface{}) *SelectStmt {
	stmt.havingQuery = expr
	stmt.havingVals = args

	return stmt
}

// WhereStmt 实现 WhereStmter 接口
func (stmt *SelectStmt) WhereStmt() *WhereStmt {
	return stmt.where
}

// Where 指定 where 语句
func (stmt *SelectStmt) Where(cond string, args ...interface{}) *SelectStmt {
	return stmt.And(cond, args...)
}

// And 指定 where ... AND ... 语句
func (stmt *SelectStmt) And(cond string, args ...interface{}) *SelectStmt {
	stmt.where.And(cond, args...)
	return stmt
}

// Or 指定 where ... OR ... 语句
func (stmt *SelectStmt) Or(cond string, args ...interface{}) *SelectStmt {
	stmt.where.Or(cond, args...)
	return stmt
}

// Join 添加一条 Join 语句
func (stmt *SelectStmt) Join(typ, table, on string) *SelectStmt {
	if stmt.joins == nil {
		stmt.joins = make([]*join, 0, 5)
	}

	stmt.joins = append(stmt.joins, &join{typ: typ, table: table, on: on})
	return stmt
}

// Desc 倒序查询
func (stmt *SelectStmt) Desc(col ...string) *SelectStmt {
	return stmt.orderBy(false, col...)
}

// Asc 正序查询
func (stmt *SelectStmt) Asc(col ...string) *SelectStmt {
	return stmt.orderBy(true, col...)
}

func (stmt *SelectStmt) orderBy(asc bool, col ...string) *SelectStmt {
	if stmt.orders == nil {
		stmt.orders = New("")
	}

	if stmt.orders.Len() == 0 {
		stmt.orders.WriteString(" ORDER BY ")
	} else {
		stmt.orders.WriteByte(',')
	}

	for _, c := range col {
		stmt.orders.WriteString(c)
		stmt.orders.WriteByte(',')
	}
	stmt.orders.TruncateLast(1)

	if asc {
		stmt.orders.WriteString(" ASC ")
	} else {
		stmt.orders.WriteString(" DESC ")
	}

	return stmt
}

// ForUpdate 添加 FOR UPDATE 语句部分
func (stmt *SelectStmt) ForUpdate() *SelectStmt {
	stmt.forupdate = true
	return stmt
}

// Group 添加 GROUP BY 语句
func (stmt *SelectStmt) Group(col string) *SelectStmt {
	stmt.group = " GROUP BY " + col + " "
	return stmt
}

// Limit 生成 SQL 的 Limit 语句
func (stmt *SelectStmt) Limit(limit interface{}, offset ...interface{}) *SelectStmt {
	query, vals := stmt.dialect.LimitSQL(limit, offset...)
	stmt.limitQuery = query
	stmt.limitVals = vals
	return stmt
}

// Count 指定 Count 表示式，如果指定了 count 表达式，则会造成 limit 失效。
//
// 传递空的 expr 参数，表示去除 count 表达式。
func (stmt *SelectStmt) Count(expr string) *SelectStmt {
	stmt.countExpr = expr
	return stmt
}

// Prepare 预编译
func (stmt *SelectStmt) Prepare() (*sql.Stmt, error) {
	return prepare(stmt.engine, stmt)
}

// PrepareContext 预编译
func (stmt *SelectStmt) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
	return prepareContext(ctx, stmt.engine, stmt)
}

// Query 查询
func (stmt *SelectStmt) Query() (*sql.Rows, error) {
	return query(stmt.engine, stmt)
}

// QueryContext 查询
func (stmt *SelectStmt) QueryContext(ctx context.Context) (*sql.Rows, error) {
	return queryContext(ctx, stmt.engine, stmt)
}

// QueryObj 将符合当前条件的所有记录依次写入 objs 中。
//
// 关于 objs 的值类型，可以参考 github.com/issue9/orm/fetch.Obj 函数的相关介绍。
func (stmt *SelectStmt) QueryObj(objs interface{}) (int, error) {
	rows, err := stmt.Query()
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	return fetch.Obj(objs, rows)
}

// QueryInt 查询指定列的第一行数据，并将其转换成 int
func (stmt *SelectStmt) QueryInt(colName string) (int64, error) {
	rows, err := stmt.Query()
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	cols, err := fetch.ColumnString(true, colName, rows)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(cols[0], 10, 64)
}
<commit_msg>修正 left join 错误<commit_after>// Copyright 2018 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package sqlbuilder

import (
	"context"
	"database/sql"
	"strconv"

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

// SelectStmt 查询语句
type SelectStmt struct {
	engine    Engine
	dialect   Dialect
	table     string
	where     *WhereStmt
	cols      []string
	distinct  bool
	forupdate bool

	// COUNT 查询的列内容
	countExpr string

	joins  []*join
	orders *SQLBuilder
	group  string

	havingQuery string
	havingVals  []interface{}

	limitQuery string
	limitVals  []interface{}
}

type join struct {
	typ   string
	on    string
	table string
}

// Select 声明一条 Select 语句
func Select(e Engine, d Dialect) *SelectStmt {
	return &SelectStmt{
		engine:  e,
		dialect: d,
		where:   Where(),
	}
}

// Distinct 声明一条 Select 语句的 Distinct
//
// 若指定了此值，则 Select() 所指定的列，均为 Distinct 之后的列。
func (stmt *SelectStmt) Distinct() *SelectStmt {
	stmt.distinct = true
	return stmt
}

// Reset 重置语句
func (stmt *SelectStmt) Reset() {
	stmt.table = ""
	stmt.where.Reset()
	stmt.cols = stmt.cols[:0]
	stmt.distinct = false
	stmt.forupdate = false

	stmt.countExpr = ""

	stmt.joins = stmt.joins[:0]
	if stmt.orders != nil {
		stmt.orders.Reset()
	}
	stmt.group = ""

	stmt.havingQuery = ""
	stmt.havingVals = nil

	stmt.limitQuery = ""
	stmt.limitVals = nil
}

// SQL 获取 SQL 语句及对应的参数
func (stmt *SelectStmt) SQL() (string, []interface{}, error) {
	if stmt.table == "" {
		return "", nil, ErrTableIsEmpty
	}

	if len(stmt.cols) == 0 && stmt.countExpr == "" {
		return "", nil, ErrColumnsIsEmpty
	}

	buf := New("SELECT ")
	args := make([]interface{}, 0, 10)

	if stmt.countExpr == "" {
		if stmt.distinct {
			buf.WriteString("DISTINCT ")
		}
		for _, c := range stmt.cols {
			buf.WriteString(c)
			buf.WriteByte(',')
		}
		buf.TruncateLast(1)
	} else {
		buf.WriteString(stmt.countExpr)
	}

	buf.WriteString(" FROM ")
	buf.WriteString(stmt.table)

	// join
	if len(stmt.joins) > 0 {
		buf.WriteByte(' ')
		for _, join := range stmt.joins {
			buf.WriteString(join.typ)
			buf.WriteString(" JOIN ")
			buf.WriteString(join.table)
			buf.WriteString(" ON ")
			buf.WriteString(join.on)
			buf.WriteByte(' ')
		}
		buf.TruncateLast(1)
	}

	// where
	wq, wa, err := stmt.where.SQL()
	if err != nil {
		return "", nil, err
	}
	if wq != "" {
		buf.WriteString(" WHERE ")
		buf.WriteString(wq)
		args = append(args, wa...)
	}

	// group by
	if stmt.group != "" {
		buf.WriteString(stmt.group)
	}

	// having
	if stmt.havingQuery != "" {
		buf.WriteString(stmt.havingQuery)
		args = append(args, stmt.havingVals...)
	}

	// order by
	if stmt.orders != nil && stmt.orders.Len() > 0 {
		buf.WriteString(stmt.orders.String())
	}

	// limit
	if stmt.countExpr == "" && stmt.limitQuery != "" {
		buf.WriteString(stmt.limitQuery)
		args = append(args, stmt.limitVals...)
	}

	// for update
	if stmt.forupdate {
		buf.WriteString(" FOR UPDATE")
	}

	return buf.String(), args, nil
}

// Select 指定列名
func (stmt *SelectStmt) Select(cols ...string) *SelectStmt {
	if stmt.cols == nil {
		stmt.cols = make([]string, 0, len(cols))
	}

	stmt.cols = append(stmt.cols, cols...)
	return stmt
}

// From 指定表名
func (stmt *SelectStmt) From(table string) *SelectStmt {
	stmt.table = table

	return stmt
}

// Having 指定 having 语句
func (stmt *SelectStmt) Having(expr string, args ...interface{}) *SelectStmt {
	stmt.havingQuery = expr
	stmt.havingVals = args

	return stmt
}

// WhereStmt 实现 WhereStmter 接口
func (stmt *SelectStmt) WhereStmt() *WhereStmt {
	return stmt.where
}

// Where 指定 where 语句
func (stmt *SelectStmt) Where(cond string, args ...interface{}) *SelectStmt {
	return stmt.And(cond, args...)
}

// And 指定 where ... AND ... 语句
func (stmt *SelectStmt) And(cond string, args ...interface{}) *SelectStmt {
	stmt.where.And(cond, args...)
	return stmt
}

// Or 指定 where ... OR ... 语句
func (stmt *SelectStmt) Or(cond string, args ...interface{}) *SelectStmt {
	stmt.where.Or(cond, args...)
	return stmt
}

// Join 添加一条 Join 语句
func (stmt *SelectStmt) Join(typ, table, on string) *SelectStmt {
	if stmt.joins == nil {
		stmt.joins = make([]*join, 0, 5)
	}

	stmt.joins = append(stmt.joins, &join{typ: typ, table: table, on: on})
	return stmt
}

// Desc 倒序查询
func (stmt *SelectStmt) Desc(col ...string) *SelectStmt {
	return stmt.orderBy(false, col...)
}

// Asc 正序查询
func (stmt *SelectStmt) Asc(col ...string) *SelectStmt {
	return stmt.orderBy(true, col...)
}

func (stmt *SelectStmt) orderBy(asc bool, col ...string) *SelectStmt {
	if stmt.orders == nil {
		stmt.orders = New("")
	}

	if stmt.orders.Len() == 0 {
		stmt.orders.WriteString(" ORDER BY ")
	} else {
		stmt.orders.WriteByte(',')
	}

	for _, c := range col {
		stmt.orders.WriteString(c)
		stmt.orders.WriteByte(',')
	}
	stmt.orders.TruncateLast(1)

	if asc {
		stmt.orders.WriteString(" ASC ")
	} else {
		stmt.orders.WriteString(" DESC ")
	}

	return stmt
}

// ForUpdate 添加 FOR UPDATE 语句部分
func (stmt *SelectStmt) ForUpdate() *SelectStmt {
	stmt.forupdate = true
	return stmt
}

// Group 添加 GROUP BY 语句
func (stmt *SelectStmt) Group(col string) *SelectStmt {
	stmt.group = " GROUP BY " + col + " "
	return stmt
}

// Limit 生成 SQL 的 Limit 语句
func (stmt *SelectStmt) Limit(limit interface{}, offset ...interface{}) *SelectStmt {
	query, vals := stmt.dialect.LimitSQL(limit, offset...)
	stmt.limitQuery = query
	stmt.limitVals = vals
	return stmt
}

// Count 指定 Count 表示式，如果指定了 count 表达式，则会造成 limit 失效。
//
// 传递空的 expr 参数，表示去除 count 表达式。
func (stmt *SelectStmt) Count(expr string) *SelectStmt {
	stmt.countExpr = expr
	return stmt
}

// Prepare 预编译
func (stmt *SelectStmt) Prepare() (*sql.Stmt, error) {
	return prepare(stmt.engine, stmt)
}

// PrepareContext 预编译
func (stmt *SelectStmt) PrepareContext(ctx context.Context) (*sql.Stmt, error) {
	return prepareContext(ctx, stmt.engine, stmt)
}

// Query 查询
func (stmt *SelectStmt) Query() (*sql.Rows, error) {
	return query(stmt.engine, stmt)
}

// QueryContext 查询
func (stmt *SelectStmt) QueryContext(ctx context.Context) (*sql.Rows, error) {
	return queryContext(ctx, stmt.engine, stmt)
}

// QueryObj 将符合当前条件的所有记录依次写入 objs 中。
//
// 关于 objs 的值类型，可以参考 github.com/issue9/orm/fetch.Obj 函数的相关介绍。
func (stmt *SelectStmt) QueryObj(objs interface{}) (int, error) {
	rows, err := stmt.Query()
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	return fetch.Obj(objs, rows)
}

// QueryInt 查询指定列的第一行数据，并将其转换成 int
func (stmt *SelectStmt) QueryInt(colName string) (int64, error) {
	rows, err := stmt.Query()
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	cols, err := fetch.ColumnString(true, colName, rows)
	if err != nil {
		return 0, err
	}

	return strconv.ParseInt(cols[0], 10, 64)
}
<|endoftext|>


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

package service

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"
)

var (
	bufcnt            int64
	DefaultBufferSize int64

	DeviceInBufferSize  int64
	DeviceOutBufferSize int64

	MasterInBufferSize  int64
	MasterOutBufferSize int64
)

type sequence struct {
	// The current position of the producer or consumer
	cursor,

	// The previous known position of the consumer (if producer) or producer (if consumer)
	gate,

	// These are fillers to pad the cache line, which is generally 64 bytes
	p2, p3, p4, p5, p6, p7 int64
}

func newSequence() *sequence {
	return &sequence{}
}

func (this *sequence) get() int64 {
	return atomic.LoadInt64(&this.cursor)
}

func (this *sequence) set(seq int64) {
	atomic.StoreInt64(&this.cursor, seq)
}

type buffer struct {
	id int64

	readIndex  int64 //读序号
	writeIndex int64 //写序号
	buf        []*[]byte

	size int64
	mask int64

	done int64

	pcond *sync.Cond
	ccond *sync.Cond
}

func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}

	if size == 0 {
		size = DefaultBufferSize
	}

	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		id:         atomic.AddInt64(&bufcnt, 1),
		readIndex:  int64(0),
		writeIndex: int64(0),
		buf:        make([]*[]byte, size),
		size:       size,
		mask:       size - 1,
		pcond:      sync.NewCond(new(sync.Mutex)),
		ccond:      sync.NewCond(new(sync.Mutex)),
	}, nil
}

/**
获取当前读序号
*/
func (this *buffer) GetCurrentReadIndex() int64 {
	return atomic.LoadInt64(&this.readIndex)
}

/**
获取当前写序号
*/
func (this *buffer) GetCurrentWriteIndex() int64 {
	return atomic.LoadInt64(&this.writeIndex)
}

func (this *buffer) ID() int64 {
	return this.id
}

func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)

	this.pcond.L.Lock()
	this.ccond.Broadcast()
	this.pcond.L.Unlock()

	this.ccond.L.Lock()
	this.pcond.Broadcast()
	this.ccond.L.Unlock()

	return nil
}

/**
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
*/
func (this *buffer) ReadBuffer() (p *[]byte, ok bool) {
	this.ccond.L.Lock()
	defer func() {
		this.pcond.Broadcast()
		this.ccond.L.Unlock()
		time.Sleep(5 * time.Microsecond)
	}()
	ok = false
	p = nil
	readIndex := this.GetCurrentReadIndex()
	writeIndex := this.GetCurrentWriteIndex()
	for {
		if this.isDone() {
			return nil, false
		}
		writeIndex = this.GetCurrentWriteIndex()
		if readIndex >= writeIndex {
			//fmt.Println("read wait")
			this.pcond.Broadcast()
			this.ccond.Wait()
		} else {
			break
		}
	}
	index := readIndex & this.mask //替代求模
	p = this.buf[index]
	this.buf[index] = nil
	atomic.AddInt64(&this.readIndex, int64(1))
	if p != nil {
		ok = true
	}
	return p, ok
}

/**
写入ringbuffer指针，以及将写序号加1
*/
func (this *buffer) WriteBuffer(in *[]byte) (ok bool) {
	this.pcond.L.Lock()
	defer func() {
		this.ccond.Broadcast()
		this.pcond.L.Unlock()
		time.Sleep(5 * time.Microsecond)
	}()
	ok = false
	readIndex := this.GetCurrentReadIndex()
	writeIndex := this.GetCurrentWriteIndex()
	for {
		if this.isDone() {
			return false
		}
		readIndex = this.GetCurrentReadIndex()
		if writeIndex >= readIndex && writeIndex-readIndex >= this.size {
			//fmt.Println("write wait")
			this.ccond.Broadcast()
			this.pcond.Wait()
			//time.Sleep(1 * time.Millisecond)
		} else {
			break
		}

	}
	index := writeIndex & this.mask //替代求模
	this.buf[index] = in
	atomic.AddInt64(&this.writeIndex, int64(1))
	ok = true
	return ok
}

/**
修改 尽量减少数据的创建
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()

	total := int64(0)

	for {
		time.Sleep(5 * time.Millisecond)
		if this.isDone() {
			return total, io.EOF
		}

		var write_bytes []byte

		b := make([]byte, 5)
		n, err := r.Read(b[0:1])
		if err != nil {
			return total, io.EOF
		}
		total += int64(n)
		max_cnt := 1
		for {
			if this.isDone() {
				return total, io.EOF
			}
			// If we have read 5 bytes and still not done, then there's a problem.
			if max_cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}
			_, err := r.Read(b[max_cnt:(max_cnt + 1)])

			//fmt.Println(b)
			if err != nil {
				return total, err
			}
			if b[max_cnt] >= 0x80 {
				max_cnt++
			} else {
				break
			}
		}
		remlen, m := binary.Uvarint(b[1 : max_cnt+1])
		remlen_tmp := int64(remlen)
		start_ := int64(1) + int64(m)
		total_tmp := remlen_tmp + start_

		write_bytes = make([]byte, total_tmp)
		copy(write_bytes[0:m+1], b[0:m+1]...)
		nlen := int64(0)
		times := 0
		cnt_ := int64(32)
		for nlen < remlen_tmp {
			if this.isDone() {
				return total, io.EOF
			}
			if times > 100 {
				return total, io.EOF
			} else {
				times = 0
			}
			times++
			tmpm := remlen_tmp - nlen

			b_ := write_bytes[(start_ + nlen):]
			if tmpm > cnt_ {
				b_ = write_bytes[(start_ + nlen):(start_ + nlen + cnt_)]
			}

			//b_ := make([]byte, remlen)
			n, err = r.Read(b_[0:])

			if err != nil {
				/*Log.Errorc(func() string {
					return fmt.Sprintf("从conn读取数据失败(%s)(0)", err)
				})
				time.Sleep(5 * time.Millisecond)
				continue*/
				return total, err
			}
			//write_bytes = append(write_bytes, b_[0:]...)
			nlen += int64(n)
			total += int64(n)
		}

		ok := this.WriteBuffer(&write_bytes)

		if !ok {
			return total, errors.New("write ringbuffer failed")
		}
	}
}

//func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
//	defer this.Close()
//
//	total := int64(0)
//
//	for {
//		time.Sleep(5 * time.Millisecond)
//		if this.isDone() {
//			return total, io.EOF
//		}
//
//		var write_bytes []byte
//
//		b := make([]byte, 5)
//		n, err := r.Read(b[0:1])
//		if err != nil {
//			return total, io.EOF
//		}
//		total += int64(n)
//		max_cnt := 1
//		for {
//			if this.isDone() {
//				return total, io.EOF
//			}
//			// If we have read 5 bytes and still not done, then there's a problem.
//			if max_cnt > 4 {
//				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
//			}
//			_, err := r.Read(b[max_cnt:(max_cnt + 1)])
//
//			//fmt.Println(b)
//			if err != nil {
//				return total, err
//			}
//			if b[max_cnt] >= 0x80 {
//				max_cnt++
//			} else {
//				break
//			}
//		}
//		remlen, m := binary.Uvarint(b[1 : max_cnt + 1])
//		remlen_tmp := int64(remlen)
//		total_tmp := remlen_tmp + int64(1) + int64(m)
//
//		write_bytes = make([]byte, 0, total_tmp)
//		write_bytes = append(write_bytes, b[0:m + 1]...)
//		nlen := int64(0)
//		times := 0
//		cnt_ := 32
//		for nlen < remlen_tmp {
//			if this.isDone() {
//				return total, io.EOF
//			}
//			if times > 100 {
//				return total, io.EOF
//			} else {
//				times = 0
//			}
//			times++
//			tmpm := remlen_tmp - nlen
//
//			var b_ []byte
//			if tmpm < int64(cnt_) {
//				b_ = make([]byte, tmpm)
//			} else {
//				b_ = make([]byte, cnt_)
//			}
//
//			//b_ := make([]byte, remlen)
//			n, err = r.Read(b_[0:])
//
//			if err != nil {
//				/*Log.Errorc(func() string {
//					return fmt.Sprintf("从conn读取数据失败(%s)(0)", err)
//				})
//				time.Sleep(5 * time.Millisecond)
//				continue*/
//				return total, err
//			}
//			write_bytes = append(write_bytes, b_[0:]...)
//			nlen += int64(n)
//			total += int64(n)
//		}
//
//		ok := this.WriteBuffer(&write_bytes)
//
//		if !ok {
//			return total, errors.New("write ringbuffer failed")
//		}
//	}
//}

func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()

	total := int64(0)

	for {
		if this.isDone() {
			return total, io.EOF
		}

		p, ok := this.ReadBuffer()
		if !ok {
			return total, errors.New("read buffer failed")
		}
		// There's some data, let's process it first
		if len(*p) > 0 {
			n, err := w.Write(*p)
			total += int64(n)
			//Log.Debugc(func() string{ return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)})

			if err != nil {
				return total, err
			}
		}

	}
}

func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func ringCopy(dst, src []byte, start int64) int {
	n := len(src)

	i, l := 0, 0

	for n > 0 {
		l = copy(dst[start:], src[i:])
		i += l
		n -= l

		if n > 0 {
			start = 0
		}
	}

	return i
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n&(n-1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<commit_msg>修改 buffer.go process.go sendrecv.go buffer modify sleep 5 Microsecond modify buffre.go readfrom sleep 5毫秒 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<commit_after>// Copyright (c) 2014 The SurgeMQ Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package service

import (
	"bufio"
	"encoding/binary"
	"errors"
	"fmt"
	"io"
	"sync"
	"sync/atomic"
	"time"
)

var (
	bufcnt            int64
	DefaultBufferSize int64

	DeviceInBufferSize  int64
	DeviceOutBufferSize int64

	MasterInBufferSize  int64
	MasterOutBufferSize int64
)

type sequence struct {
	// The current position of the producer or consumer
	cursor,

	// The previous known position of the consumer (if producer) or producer (if consumer)
	gate,

	// These are fillers to pad the cache line, which is generally 64 bytes
	p2, p3, p4, p5, p6, p7 int64
}

func newSequence() *sequence {
	return &sequence{}
}

func (this *sequence) get() int64 {
	return atomic.LoadInt64(&this.cursor)
}

func (this *sequence) set(seq int64) {
	atomic.StoreInt64(&this.cursor, seq)
}

type buffer struct {
	id int64

	readIndex  int64 //读序号
	writeIndex int64 //写序号
	buf        []*[]byte

	size int64
	mask int64

	done int64

	pcond *sync.Cond
	ccond *sync.Cond
}

func newBuffer(size int64) (*buffer, error) {
	if size < 0 {
		return nil, bufio.ErrNegativeCount
	}

	if size == 0 {
		size = DefaultBufferSize
	}

	if !powerOfTwo64(size) {
		fmt.Printf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
		return nil, fmt.Errorf("Size must be power of two. Try %d.", roundUpPowerOfTwo64(size))
	}

	return &buffer{
		id:         atomic.AddInt64(&bufcnt, 1),
		readIndex:  int64(0),
		writeIndex: int64(0),
		buf:        make([]*[]byte, size),
		size:       size,
		mask:       size - 1,
		pcond:      sync.NewCond(new(sync.Mutex)),
		ccond:      sync.NewCond(new(sync.Mutex)),
	}, nil
}

/**
获取当前读序号
*/
func (this *buffer) GetCurrentReadIndex() int64 {
	return atomic.LoadInt64(&this.readIndex)
}

/**
获取当前写序号
*/
func (this *buffer) GetCurrentWriteIndex() int64 {
	return atomic.LoadInt64(&this.writeIndex)
}

func (this *buffer) ID() int64 {
	return this.id
}

func (this *buffer) Close() error {
	atomic.StoreInt64(&this.done, 1)

	this.pcond.L.Lock()
	this.ccond.Broadcast()
	this.pcond.L.Unlock()

	this.ccond.L.Lock()
	this.pcond.Broadcast()
	this.ccond.L.Unlock()

	return nil
}

/**
读取ringbuffer指定的buffer指针，返回该指针并清空ringbuffer该位置存在的指针内容，以及将读序号加1
*/
func (this *buffer) ReadBuffer() (p *[]byte, ok bool) {
	this.ccond.L.Lock()
	defer func() {
		this.pcond.Broadcast()
		this.ccond.L.Unlock()
		time.Sleep(5 * time.Microsecond)
	}()
	ok = false
	p = nil
	readIndex := this.GetCurrentReadIndex()
	writeIndex := this.GetCurrentWriteIndex()
	for {
		if this.isDone() {
			return nil, false
		}
		writeIndex = this.GetCurrentWriteIndex()
		if readIndex >= writeIndex {
			//fmt.Println("read wait")
			this.pcond.Broadcast()
			this.ccond.Wait()
		} else {
			break
		}
	}
	index := readIndex & this.mask //替代求模
	p = this.buf[index]
	this.buf[index] = nil
	atomic.AddInt64(&this.readIndex, int64(1))
	if p != nil {
		ok = true
	}
	return p, ok
}

/**
写入ringbuffer指针，以及将写序号加1
*/
func (this *buffer) WriteBuffer(in *[]byte) (ok bool) {
	this.pcond.L.Lock()
	defer func() {
		this.ccond.Broadcast()
		this.pcond.L.Unlock()
		time.Sleep(5 * time.Microsecond)
	}()
	ok = false
	readIndex := this.GetCurrentReadIndex()
	writeIndex := this.GetCurrentWriteIndex()
	for {
		if this.isDone() {
			return false
		}
		readIndex = this.GetCurrentReadIndex()
		if writeIndex >= readIndex && writeIndex-readIndex >= this.size {
			//fmt.Println("write wait")
			this.ccond.Broadcast()
			this.pcond.Wait()
			//time.Sleep(1 * time.Millisecond)
		} else {
			break
		}

	}
	index := writeIndex & this.mask //替代求模
	this.buf[index] = in
	atomic.AddInt64(&this.writeIndex, int64(1))
	ok = true
	return ok
}

/**
修改 尽量减少数据的创建
*/
func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
	defer this.Close()

	total := int64(0)

	for {
		time.Sleep(5 * time.Millisecond)
		if this.isDone() {
			return total, io.EOF
		}

		var write_bytes []byte

		b := make([]byte, 5)
		n, err := r.Read(b[0:1])
		if err != nil {
			return total, io.EOF
		}
		total += int64(n)
		max_cnt := 1
		for {
			if this.isDone() {
				return total, io.EOF
			}
			// If we have read 5 bytes and still not done, then there's a problem.
			if max_cnt > 4 {
				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
			}
			_, err := r.Read(b[max_cnt:(max_cnt + 1)])

			//fmt.Println(b)
			if err != nil {
				return total, err
			}
			if b[max_cnt] >= 0x80 {
				max_cnt++
			} else {
				break
			}
		}
		remlen, m := binary.Uvarint(b[1 : max_cnt+1])
		remlen_tmp := int64(remlen)
		start_ := int64(1) + int64(m)
		total_tmp := remlen_tmp + start_

		write_bytes = make([]byte, total_tmp)
		copy(write_bytes[0:m+1], b[0:m+1])
		nlen := int64(0)
		times := 0
		cnt_ := int64(32)
		for nlen < remlen_tmp {
			if this.isDone() {
				return total, io.EOF
			}
			if times > 100 {
				return total, io.EOF
			} else {
				times = 0
			}
			times++
			tmpm := remlen_tmp - nlen

			b_ := write_bytes[(start_ + nlen):]
			if tmpm > cnt_ {
				b_ = write_bytes[(start_ + nlen):(start_ + nlen + cnt_)]
			}

			//b_ := make([]byte, remlen)
			n, err = r.Read(b_[0:])

			if err != nil {
				/*Log.Errorc(func() string {
					return fmt.Sprintf("从conn读取数据失败(%s)(0)", err)
				})
				time.Sleep(5 * time.Millisecond)
				continue*/
				return total, err
			}
			//write_bytes = append(write_bytes, b_[0:]...)
			nlen += int64(n)
			total += int64(n)
		}

		ok := this.WriteBuffer(&write_bytes)

		if !ok {
			return total, errors.New("write ringbuffer failed")
		}
	}
}

//func (this *buffer) ReadFrom(r io.Reader) (int64, error) {
//	defer this.Close()
//
//	total := int64(0)
//
//	for {
//		time.Sleep(5 * time.Millisecond)
//		if this.isDone() {
//			return total, io.EOF
//		}
//
//		var write_bytes []byte
//
//		b := make([]byte, 5)
//		n, err := r.Read(b[0:1])
//		if err != nil {
//			return total, io.EOF
//		}
//		total += int64(n)
//		max_cnt := 1
//		for {
//			if this.isDone() {
//				return total, io.EOF
//			}
//			// If we have read 5 bytes and still not done, then there's a problem.
//			if max_cnt > 4 {
//				return 0, fmt.Errorf("sendrecv/peekMessageSize: 4th byte of remaining length has continuation bit set")
//			}
//			_, err := r.Read(b[max_cnt:(max_cnt + 1)])
//
//			//fmt.Println(b)
//			if err != nil {
//				return total, err
//			}
//			if b[max_cnt] >= 0x80 {
//				max_cnt++
//			} else {
//				break
//			}
//		}
//		remlen, m := binary.Uvarint(b[1 : max_cnt + 1])
//		remlen_tmp := int64(remlen)
//		total_tmp := remlen_tmp + int64(1) + int64(m)
//
//		write_bytes = make([]byte, 0, total_tmp)
//		write_bytes = append(write_bytes, b[0:m + 1]...)
//		nlen := int64(0)
//		times := 0
//		cnt_ := 32
//		for nlen < remlen_tmp {
//			if this.isDone() {
//				return total, io.EOF
//			}
//			if times > 100 {
//				return total, io.EOF
//			} else {
//				times = 0
//			}
//			times++
//			tmpm := remlen_tmp - nlen
//
//			var b_ []byte
//			if tmpm < int64(cnt_) {
//				b_ = make([]byte, tmpm)
//			} else {
//				b_ = make([]byte, cnt_)
//			}
//
//			//b_ := make([]byte, remlen)
//			n, err = r.Read(b_[0:])
//
//			if err != nil {
//				/*Log.Errorc(func() string {
//					return fmt.Sprintf("从conn读取数据失败(%s)(0)", err)
//				})
//				time.Sleep(5 * time.Millisecond)
//				continue*/
//				return total, err
//			}
//			write_bytes = append(write_bytes, b_[0:]...)
//			nlen += int64(n)
//			total += int64(n)
//		}
//
//		ok := this.WriteBuffer(&write_bytes)
//
//		if !ok {
//			return total, errors.New("write ringbuffer failed")
//		}
//	}
//}

func (this *buffer) WriteTo(w io.Writer) (int64, error) {
	defer this.Close()

	total := int64(0)

	for {
		if this.isDone() {
			return total, io.EOF
		}

		p, ok := this.ReadBuffer()
		if !ok {
			return total, errors.New("read buffer failed")
		}
		// There's some data, let's process it first
		if len(*p) > 0 {
			n, err := w.Write(*p)
			total += int64(n)
			//Log.Debugc(func() string{ return fmt.Sprintf("Wrote %d bytes, totaling %d bytes", n, total)})

			if err != nil {
				return total, err
			}
		}

	}
}

func (this *buffer) isDone() bool {
	if atomic.LoadInt64(&this.done) == 1 {
		return true
	}

	return false
}

func ringCopy(dst, src []byte, start int64) int {
	n := len(src)

	i, l := 0, 0

	for n > 0 {
		l = copy(dst[start:], src[i:])
		i += l
		n -= l

		if n > 0 {
			start = 0
		}
	}

	return i
}

func powerOfTwo64(n int64) bool {
	return n != 0 && (n&(n-1)) == 0
}

func roundUpPowerOfTwo64(n int64) int64 {
	n--
	n |= n >> 1
	n |= n >> 2
	n |= n >> 4
	n |= n >> 8
	n |= n >> 16
	n |= n >> 32
	n++

	return n
}
<|endoftext|>


<commit_before>package HumorChecker // "cirello.io/HumorChecker"

import (
	"bufio"
	"regexp"
	"strings"
)

type sign int

const (
	positive sign = +1
	negative sign = -1
)

// analysis is the complete sentiment calculation
type analysis struct {
	// positivityScore is the sum of the positive sentiment points of the
	// analyzed text.
	positivityScore float64

	// negativityScore is the sum of the negativity sentiment points of the
	// analyzed text.
	negativityScore float64

	// positivityComparative establishes a ratio of sentiment per positive
	// word
	positivityComparative float64

	// negativityComparative establishes a ratio of sentiment per negative
	// word
	negativityComparative float64

	// positiveWords is the list of positive words for a given sentiment.
	positiveWords []string

	// negativeWords is the list of negative words for a given sentiment.
	negativeWords []string
}

// Score is the result of sentiment calculation
type Score struct {
	// Score is the sum of the sentiment points of the analyzed text.
	// Negativity will render negative points only, and vice-versa.
	Score float64

	// Comparative establishes a ratio of sentiment per word
	Comparative float64

	// Words is the list of words for a given sentiment.
	Words []string
}

// FullScore is the difference between positive and negative sentiment
type FullScore struct {
	// Score is the difference between positive and negative sentiment
	// scores.
	Score float64

	// Comparative is the difference between positive and negative sentiment
	// comparative scores.
	Comparative float64

	// Positive score object
	Positive Score

	// Negative score object
	Negative Score
}

var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)

func calculateScore(phrase string) analysis {
	var phits, nhits float64
	var pwords, nwords []string
	var count int

	scanner := bufio.NewScanner(strings.NewReader(strings.ToLower(lettersAndSpaceOnly.ReplaceAllString(phrase, " "))))
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		count++
		word := scanner.Text()
		if v, ok := afinn[word]; ok {
			if v > 0 {
				phits += v
				pwords = append(pwords, word)
			} else if v < 0 {
				nhits -= v
				nwords = append(nwords, word)
			}
		}
	}

	return analysis{
		positivityScore:       phits,
		positivityComparative: phits / float64(count),
		positiveWords:         pwords,
		negativityScore:       nhits,
		negativityComparative: nhits / float64(count),
		negativeWords:         nwords,
	}
}

func renderNegativeScore(a analysis) Score {
	return Score{
		Score:       a.negativityScore,
		Comparative: a.negativityComparative,
		Words:       a.negativeWords,
	}
}

// Negativity calculates the negative sentiment of a sentence
func Negativity(phrase string) Score {
	return renderNegativeScore(calculateScore(phrase))
}

func renderPositiveScore(a analysis) Score {
	return Score{
		Score:       a.positivityScore,
		Comparative: a.positivityComparative,
		Words:       a.positiveWords,
	}
}

// Positivity calculates the positive sentiment of a sentence
func Positivity(phrase string) Score {
	return renderPositiveScore(calculateScore(phrase))
}

// Analyze calculates overall sentiment
func Analyze(phrase string) FullScore {
	analysis := calculateScore(phrase)

	return FullScore{
		Score:       analysis.positivityScore - analysis.negativityScore,
		Comparative: analysis.positivityComparative - analysis.negativityComparative,
		Positive:    renderPositiveScore(analysis),
		Negative:    renderNegativeScore(analysis),
	}
}
<commit_msg>Remove unused data structure<commit_after>package HumorChecker // "cirello.io/HumorChecker"

import (
	"bufio"
	"regexp"
	"strings"
)

// analysis is the complete sentiment calculation
type analysis struct {
	// positivityScore is the sum of the positive sentiment points of the
	// analyzed text.
	positivityScore float64

	// negativityScore is the sum of the negativity sentiment points of the
	// analyzed text.
	negativityScore float64

	// positivityComparative establishes a ratio of sentiment per positive
	// word
	positivityComparative float64

	// negativityComparative establishes a ratio of sentiment per negative
	// word
	negativityComparative float64

	// positiveWords is the list of positive words for a given sentiment.
	positiveWords []string

	// negativeWords is the list of negative words for a given sentiment.
	negativeWords []string
}

// Score is the result of sentiment calculation
type Score struct {
	// Score is the sum of the sentiment points of the analyzed text.
	// Negativity will render negative points only, and vice-versa.
	Score float64

	// Comparative establishes a ratio of sentiment per word
	Comparative float64

	// Words is the list of words for a given sentiment.
	Words []string
}

// FullScore is the difference between positive and negative sentiment
type FullScore struct {
	// Score is the difference between positive and negative sentiment
	// scores.
	Score float64

	// Comparative is the difference between positive and negative sentiment
	// comparative scores.
	Comparative float64

	// Positive score object
	Positive Score

	// Negative score object
	Negative Score
}

var lettersAndSpaceOnly = regexp.MustCompile(`[^a-zA-Z ]+`)

func calculateScore(phrase string) analysis {
	var phits, nhits float64
	var pwords, nwords []string
	var count int

	scanner := bufio.NewScanner(strings.NewReader(strings.ToLower(lettersAndSpaceOnly.ReplaceAllString(phrase, " "))))
	scanner.Split(bufio.ScanWords)
	for scanner.Scan() {
		count++
		word := scanner.Text()
		if v, ok := afinn[word]; ok {
			if v > 0 {
				phits += v
				pwords = append(pwords, word)
			} else if v < 0 {
				nhits -= v
				nwords = append(nwords, word)
			}
		}
	}

	return analysis{
		positivityScore:       phits,
		positivityComparative: phits / float64(count),
		positiveWords:         pwords,
		negativityScore:       nhits,
		negativityComparative: nhits / float64(count),
		negativeWords:         nwords,
	}
}

func renderNegativeScore(a analysis) Score {
	return Score{
		Score:       a.negativityScore,
		Comparative: a.negativityComparative,
		Words:       a.negativeWords,
	}
}

// Negativity calculates the negative sentiment of a sentence
func Negativity(phrase string) Score {
	return renderNegativeScore(calculateScore(phrase))
}

func renderPositiveScore(a analysis) Score {
	return Score{
		Score:       a.positivityScore,
		Comparative: a.positivityComparative,
		Words:       a.positiveWords,
	}
}

// Positivity calculates the positive sentiment of a sentence
func Positivity(phrase string) Score {
	return renderPositiveScore(calculateScore(phrase))
}

// Analyze calculates overall sentiment
func Analyze(phrase string) FullScore {
	analysis := calculateScore(phrase)

	return FullScore{
		Score:       analysis.positivityScore - analysis.negativityScore,
		Comparative: analysis.positivityComparative - analysis.negativityComparative,
		Positive:    renderPositiveScore(analysis),
		Negative:    renderNegativeScore(analysis),
	}
}
<|endoftext|>


<commit_before>package apps

import (
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers"
)

var _ = Describe("loggregator", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", helpers.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Context("cf logs", func() {
		var logs *Session

		BeforeEach(func() {
			logs = cf.Cf("logs", appName)
		})

		AfterEach(func() {
			// logs might be nil if the BeforeEach panics
			if logs != nil {
				logs.Interrupt().Wait(DEFAULT_TIMEOUT)
			}
		})

		It("exercises basic loggregator behavior", func() {
			Eventually(logs, DEFAULT_TIMEOUT).Should(Say("Connected, tailing logs for app"))

			Expect(helpers.CurlAppRoot(appName)).To(ContainSubstring("Hi, I'm Dora!"))

			expectedLogMessage := fmt.Sprintf("OUT %s.%s", appName, helpers.LoadConfig().AppsDomain)
			Eventually(logs, DEFAULT_TIMEOUT).Should(Say(expectedLogMessage))
		})
	})

	Context("cf logs --recent", func() {
		It("makes loggregator buffer and dump log messages", func() {
			logs := cf.Cf("logs", appName, "--recent").Wait(DEFAULT_TIMEOUT)
			Expect(logs).To(Exit(0))
			Expect(logs).To(Say("Connected, dumping recent logs for app"))
			Expect(logs).To(Say("OUT Created app"))
			Expect(logs).To(Say("OUT Starting app instance"))
		})
	})
})
<commit_msg>Add longer timeout to loggregator test<commit_after>package apps

import (
	"fmt"
	"time"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"

	"github.com/cloudfoundry-incubator/cf-test-helpers/cf"
	"github.com/cloudfoundry-incubator/cf-test-helpers/generator"
	"github.com/cloudfoundry/cf-acceptance-tests/helpers"
)

var _ = Describe("loggregator", func() {
	var appName string

	BeforeEach(func() {
		appName = generator.RandomName()

		Expect(cf.Cf("push", appName, "-p", helpers.NewAssets().Dora).Wait(CF_PUSH_TIMEOUT)).To(Exit(0))
	})

	AfterEach(func() {
		Expect(cf.Cf("delete", appName, "-f").Wait(DEFAULT_TIMEOUT)).To(Exit(0))
	})

	Context("cf logs", func() {
		var logs *Session

		BeforeEach(func() {
			logs = cf.Cf("logs", appName)
		})

		AfterEach(func() {
			// logs might be nil if the BeforeEach panics
			if logs != nil {
				logs.Interrupt().Wait(DEFAULT_TIMEOUT)
			}
		})

		It("exercises basic loggregator behavior", func() {
			Eventually(logs, DEFAULT_TIMEOUT).Should(Say("Connected, tailing logs for app"))

			Expect(helpers.CurlAppRoot(appName)).To(ContainSubstring("Hi, I'm Dora!"))

			expectedLogMessage := fmt.Sprintf("OUT %s.%s", appName, helpers.LoadConfig().AppsDomain)
			Eventually(logs, (DEFAULT_TIMEOUT + time.Minute)).Should(Say(expectedLogMessage))
		})
	})

	Context("cf logs --recent", func() {
		It("makes loggregator buffer and dump log messages", func() {
			logs := cf.Cf("logs", appName, "--recent").Wait(DEFAULT_TIMEOUT)
			Expect(logs).To(Exit(0))
			Expect(logs).To(Say("Connected, dumping recent logs for app"))
			Expect(logs).To(Say("OUT Created app"))
			Expect(logs).To(Say("OUT Starting app instance"))
		})
	})
})
<|endoftext|>


<commit_before>package onceevery

import (
	"sync"
	"time"
)

// OnceEvery is an object that will perform exactly one action every given
// interval.
type OnceEvery struct {
	Interval time.Duration
	ticker   *time.Ticker
	once     sync.Once
	mu       sync.Mutex
	stopped  chan bool
}

// NewOnceEvery creates a new OnceEvery struct
func New(d time.Duration) *OnceEvery {
	return &OnceEvery{
		Interval: d,
		stopped:  make(chan bool, 1),
	}
}

// Do calls the function f if and only if Do hits the given periodic interval.
// In other words Do can be called multiple times during the interval but it
// gets called only once if it hits the interval tick. So if the interval is
// 10 seconds, and a total of 100 calls are made during this period, once will
// be called it every 10 seconds.
func (o *OnceEvery) Do(f func()) {
	if f == nil {
		panic("passed function is nil")
	}

	o.mu.Lock()
	if o.ticker == nil {
		o.ticker = time.NewTicker(o.Interval)
	}
	o.mu.Unlock()

	go func() {
		o.once.Do(func() {
			f() // call it once first

			for {
				select {
				case <-o.ticker.C:
					f()
				case <-o.stopped:
					return
				}
			}
		})
	}()
}

// Stop stops the ticker. No other call made with Do will be called anymore.
func (o *OnceEvery) Stop() {
	o.mu.Lock()
	if o.ticker != nil {
		o.ticker.Stop()
	}
	o.mu.Unlock()

	select {
	case <-o.stopped:
	default:
		close(o.stopped)
	}
}
<commit_msg>onceevery: fix another typo<commit_after>package onceevery

import (
	"sync"
	"time"
)

// OnceEvery is an object that will perform exactly one action every given
// interval.
type OnceEvery struct {
	Interval time.Duration
	ticker   *time.Ticker
	once     sync.Once
	mu       sync.Mutex
	stopped  chan bool
}

// NewOnceEvery creates a new OnceEvery struct
func New(d time.Duration) *OnceEvery {
	return &OnceEvery{
		Interval: d,
		stopped:  make(chan bool, 1),
	}
}

// Do calls the function f if and only if Do hits the given periodic interval.
// In other words Do can be called multiple times during the interval but it
// gets called only once if it hits the interval tick. So if the interval is
// 10 seconds, and a total of 100 calls are made during this period, f will
// be called it every 10 seconds.
func (o *OnceEvery) Do(f func()) {
	if f == nil {
		panic("passed function is nil")
	}

	o.mu.Lock()
	if o.ticker == nil {
		o.ticker = time.NewTicker(o.Interval)
	}
	o.mu.Unlock()

	go func() {
		o.once.Do(func() {
			f() // call it once first

			for {
				select {
				case <-o.ticker.C:
					f()
				case <-o.stopped:
					return
				}
			}
		})
	}()
}

// Stop stops the ticker. No other call made with Do will be called anymore.
func (o *OnceEvery) Stop() {
	o.mu.Lock()
	if o.ticker != nil {
		o.ticker.Stop()
	}
	o.mu.Unlock()

	select {
	case <-o.stopped:
	default:
		close(o.stopped)
	}
}
<|endoftext|>


<commit_before>package urlcheck

import "errors"
import "gopkg.in/yaml.v1"

// Manifest contains multiple Servers, each having Tests and Scenarios.
type Manifest []Server

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	var allerrors []error
	for _, server := range m {
		err := server.Test()
		if err != nil {
			allerrors = append(allerrors, err)
		}
	}

	if len(allerrors) > 0 {
		errorstr := ""
		for i, err := range allerrors {
			if i > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
		}
		return errors.New(errorstr)
	}
	return nil
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<commit_msg>Manifest: empty manifest is a failure<commit_after>package urlcheck

import "errors"
import "gopkg.in/yaml.v1"

// Manifest contains multiple Servers, each having Tests and Scenarios.
type Manifest []Server

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	var allerrors []error
	for _, server := range m {
		err := server.Test()
		if err != nil {
			allerrors = append(allerrors, err)
		}
	}

	if len(allerrors) > 0 {
		errorstr := ""
		for i, err := range allerrors {
			if i > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
		}
		return errors.New(errorstr)
	}
	return nil
}

func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/msoap/raphanus"
	tgbotapi "gopkg.in/telegram-bot-api.v2"
)

const (
	// Version - current version
	Version = "1.2"

	// DefaultBotTimeout - bot default timeout
	DefaultBotTimeout = 60

	// MessagesQueueSize - size of channel for bot messages
	MessagesQueueSize = 10

	// MaxMessageLength - max length of one bot message
	MaxMessageLength = 4096

	// SecondsForAutoSaveUsersToDB - save users to file every 1 min (if need)
	SecondsForAutoSaveUsersToDB = 60

	// DBFileName - DB json name
	DBFileName = "shell2telegram.json"

	// shell2telegram command name for get plain text without /command
	cmdPlainText = "/:plain_text"
)

// Command - one user command
type Command struct {
	shellCmd    string   // shell command
	description string   // command description for list in /help (/cmd:desc="Command name")
	vars        []string // environment vars for user text, split by `/s+` to vars (/cmd:vars=SUBCOMMAND,ARGS)
	isMarkdown  bool     // send message in markdown format
}

// Commands - list of all commands
type Commands map[string]Command

// Config - config struct
type Config struct {
	token                  string   // bot token
	botTimeout             int      // bot timeout
	predefinedAllowedUsers []string // telegram users who are allowed to chat with the bot
	predefinedRootUsers    []string // telegram users, who confirms new users in their private chat
	description            string   // description of bot
	usersDB                string   // file for store users
	cache                  int      // caching command out (in seconds)
	shTimeout              int      // timeout for execute shell command (in seconds)
	addExit                bool     // adding /shell2telegram exit command
	allowAll               bool     // allow all user (DANGEROUS!)
	logCommands            bool     // logging all commands
	persistentUsers        bool     // load/save users from file
	isPublicBot            bool     // bot is public (dont add /auth* commands)
}

// message types
const (
	msgIsText int8 = iota
	msgIsPhoto
)

// BotMessage - record for send via channel for send message to telegram chat
type BotMessage struct {
	message     string
	fileName    string
	photo       []byte
	chatID      int
	messageType int8
	isMarkdown  bool
}

// ----------------------------------------------------------------------------
// get config
func getConfig() (commands Commands, appConfig Config, err error) {
	flag.StringVar(&appConfig.token, "tb-token", "", "setting bot token (or set TB_TOKEN variable)")
	flag.BoolVar(&appConfig.addExit, "add-exit", false, "adding \"/shell2telegram exit\" command for terminate bot (for roots only)")
	flag.IntVar(&appConfig.botTimeout, "timeout", DefaultBotTimeout, "setting timeout for bot")
	flag.BoolVar(&appConfig.allowAll, "allow-all", false, "allow all users (DANGEROUS!)")
	flag.BoolVar(&appConfig.logCommands, "log-commands", false, "logging all commands")
	flag.StringVar(&appConfig.description, "description", "", "setting description of bot")
	flag.BoolVar(&appConfig.persistentUsers, "persistent_users", false, "load/save users from file (default ~/.config/shell2telegram.json)")
	flag.StringVar(&appConfig.usersDB, "users_db", "", "file for store users")
	flag.IntVar(&appConfig.cache, "cache", 0, "caching command out (in seconds)")
	flag.BoolVar(&appConfig.isPublicBot, "public", false, "bot is public (dont add /auth* commands)")
	flag.IntVar(&appConfig.shTimeout, "sh-timeout", 0, "set timeout for execute shell command (in seconds)")
	logFilename := flag.String("log", "", "log filename, default - STDOUT")
	predefinedAllowedUsers := flag.String("allow-users", "", "telegram users who are allowed to chat with the bot (\"user1,user2\")")
	predefinedRootUsers := flag.String("root-users", "", "telegram users, who confirms new users in their private chat (\"user1,user2\")")
	version := flag.Bool("version", false, "get version")

	flag.Usage = func() {
		fmt.Printf("usage: %s [options] %s\n%s\n%s\n\noptions:\n",
			os.Args[0],
			`/chat_command "shell command" /chat_command2 "shell command2"`,
			"All text after /chat_command will be sent to STDIN of shell command.",
			"If chat command is /:plain_text - get user message without any /command (for private chats only)",
		)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	if *version {
		fmt.Println(Version)
		os.Exit(0)
	}

	// setup log file
	if len(*logFilename) > 0 {
		fhLog, err := os.OpenFile(*logFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fhLog)
	}

	// setup users and roots
	if *predefinedAllowedUsers != "" {
		appConfig.predefinedAllowedUsers = strings.Split(*predefinedAllowedUsers, ",")
	}
	if *predefinedRootUsers != "" {
		appConfig.predefinedRootUsers = strings.Split(*predefinedRootUsers, ",")
	}

	commands = Commands{}
	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return commands, appConfig, fmt.Errorf("error: need pairs of /chat-command and shell-command")
	}

	for i := 0; i < len(args); i += 2 {
		path, command, err := parseBotCommand(args[i], args[i+1]) // (/path, shell_command)
		if err != nil {
			return commands, appConfig, err
		}
		commands[path] = command
	}

	if appConfig.token == "" {
		if appConfig.token = os.Getenv("TB_TOKEN"); appConfig.token == "" {
			return commands, appConfig, fmt.Errorf("TB_TOKEN environment var not found. See https://core.telegram.org/bots#botfather for more information")
		}
	}

	return commands, appConfig, nil
}

// ----------------------------------------------------------------------------
func sendMessage(messageSignal chan<- BotMessage, chatID int, message []byte, isMarkdown bool) {
	go func() {
		var fileName string
		fileType := http.DetectContentType(message)
		switch fileType {
		case "image/png":
			fileName = "file.png"
		case "image/jpeg":
			fileName = "file.jpeg"
		case "image/gif":
			fileName = "file.gif"
		case "image/bmp":
			fileName = "file.bmp"
		default:
			fileName = "message"
		}

		if fileName == "message" {

			// is text message
			messageString := string(message)
			var messagesList []string

			if len(messageString) <= MaxMessageLength {
				messagesList = []string{messageString}
			} else {
				messagesList = splitStringLinesBySize(messageString, MaxMessageLength)
			}

			for _, messageChunk := range messagesList {
				messageSignal <- BotMessage{
					chatID:      chatID,
					messageType: msgIsText,
					message:     messageChunk,
					isMarkdown:  isMarkdown,
				}
			}

		} else {
			// is image
			messageSignal <- BotMessage{
				chatID:      chatID,
				messageType: msgIsPhoto,
				fileName:    fileName,
				photo:       message,
			}
		}
	}()
}

// ----------------------------------------------------------------------------
func main() {
	commands, appConfig, err := getConfig()
	if err != nil {
		log.Fatal(err)
	}

	bot, err := tgbotapi.NewBotAPI(appConfig.token)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Authorized on bot account: @%s", bot.Self.UserName)

	tgbotConfig := tgbotapi.NewUpdate(0)
	tgbotConfig.Timeout = appConfig.botTimeout
	botUpdatesChan, err := bot.GetUpdatesChan(tgbotConfig)
	if err != nil {
		log.Fatal(err)
	}

	users := NewUsers(appConfig)
	messageSignal := make(chan BotMessage, MessagesQueueSize)
	vacuumTicker := time.Tick(SecondsForOldUsersBeforeVacuum * time.Second)
	saveToBDTicker := make(<-chan time.Time)
	exitSignal := make(chan struct{})
	systemExitSignal := make(chan os.Signal)
	signal.Notify(systemExitSignal, os.Interrupt, os.Kill)

	if appConfig.persistentUsers {
		saveToBDTicker = time.Tick(SecondsForAutoSaveUsersToDB * time.Second)
	}

	var cache raphanus.DB
	if appConfig.cache > 0 {
		cache = raphanus.New("", 0)
	}

	// all /shell2telegram sub-commands handlers
	internalCommands := map[string]func(Ctx) string{
		"stat":              cmdShell2telegramStat,
		"ban":               cmdShell2telegramBan,
		"search":            cmdShell2telegramSearch,
		"desc":              cmdShell2telegramDesc,
		"rm":                cmdShell2telegramRm,
		"exit":              cmdShell2telegramExit,
		"version":           cmdShell2telegramVersion,
		"broadcast_to_root": cmdShell2telegramBroadcastToRoot,
		"message_to_user":   cmdShell2telegramMessageToUser,
	}

	doExit := false
	for !doExit {
		select {
		case telegramUpdate := <-botUpdatesChan:

			var messageCmd, messageArgs string
			allUserMessage := telegramUpdate.Message.Text
			if len(allUserMessage) > 0 && allUserMessage[0] == '/' {
				messageCmd, messageArgs = splitStringHalfBySpace(allUserMessage)
			} else {
				messageCmd, messageArgs = cmdPlainText, allUserMessage
			}

			allowPlainText := false
			if _, ok := commands[cmdPlainText]; ok {
				allowPlainText = true
			}

			replayMsg := ""

			if len(messageCmd) > 0 && (messageCmd != cmdPlainText || allowPlainText) {

				users.AddNew(telegramUpdate.Message)
				userID := telegramUpdate.Message.From.ID
				allowExec := appConfig.allowAll || users.IsAuthorized(userID)

				ctx := Ctx{
					appConfig:     &appConfig,
					users:         &users,
					commands:      commands,
					userID:        userID,
					allowExec:     allowExec,
					messageCmd:    messageCmd,
					messageArgs:   messageArgs,
					messageSignal: messageSignal,
					chatID:        telegramUpdate.Message.Chat.ID,
					exitSignal:    exitSignal,
					cache:         &cache,
				}

				switch {
				// commands .................................
				case !appConfig.isPublicBot && (messageCmd == "/auth" || messageCmd == "/authroot"):
					replayMsg = cmdAuth(ctx)

				case messageCmd == "/help":
					replayMsg = cmdHelp(ctx)

				case messageCmd == "/shell2telegram" && users.IsRoot(userID):
					var messageSubCmd string
					messageSubCmd, messageArgs = splitStringHalfBySpace(messageArgs)
					ctx.messageArgs = messageArgs
					if cmdHandler, ok := internalCommands[messageSubCmd]; ok {
						replayMsg = cmdHandler(ctx)
					} else {
						replayMsg = "Sub-command not found"
					}

				case allowExec && (allowPlainText && messageCmd == cmdPlainText || messageCmd[0] == '/'):
					cmdUser(ctx)

				} // switch for commands

				if appConfig.logCommands {
					log.Printf("%s: %s", users.String(userID), allUserMessage)
				}

				sendMessage(messageSignal, telegramUpdate.Message.Chat.ID, []byte(replayMsg), false)
			}

		case botMessage := <-messageSignal:
			switch {
			case botMessage.messageType == msgIsText && !stringIsEmpty(botMessage.message):
				messageConfig := tgbotapi.NewMessage(botMessage.chatID, botMessage.message)
				if botMessage.isMarkdown {
					messageConfig.ParseMode = tgbotapi.ModeMarkdown
				}
				_, err = bot.Send(messageConfig)
			case botMessage.messageType == msgIsPhoto && len(botMessage.photo) > 0:
				bytesPhoto := tgbotapi.FileBytes{Name: botMessage.fileName, Bytes: botMessage.photo}
				_, err = bot.Send(tgbotapi.NewPhotoUpload(botMessage.chatID, bytesPhoto))
			}

			if err != nil {
				log.Print("Bot send message error: ", err)
			}

		case <-saveToBDTicker:
			users.SaveToDB(appConfig.usersDB)

		case <-vacuumTicker:
			users.ClearOldUsers()

		case <-systemExitSignal:
			go func() {
				exitSignal <- struct{}{}
			}()

		case <-exitSignal:
			if appConfig.persistentUsers {
				users.needSaveDB = true
				users.SaveToDB(appConfig.usersDB)
			}
			doExit = true
		}
	}
}
<commit_msg>Update version number<commit_after>package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"time"

	"github.com/msoap/raphanus"
	tgbotapi "gopkg.in/telegram-bot-api.v2"
)

const (
	// Version - current version
	Version = "1.4"

	// DefaultBotTimeout - bot default timeout
	DefaultBotTimeout = 60

	// MessagesQueueSize - size of channel for bot messages
	MessagesQueueSize = 10

	// MaxMessageLength - max length of one bot message
	MaxMessageLength = 4096

	// SecondsForAutoSaveUsersToDB - save users to file every 1 min (if need)
	SecondsForAutoSaveUsersToDB = 60

	// DBFileName - DB json name
	DBFileName = "shell2telegram.json"

	// shell2telegram command name for get plain text without /command
	cmdPlainText = "/:plain_text"
)

// Command - one user command
type Command struct {
	shellCmd    string   // shell command
	description string   // command description for list in /help (/cmd:desc="Command name")
	vars        []string // environment vars for user text, split by `/s+` to vars (/cmd:vars=SUBCOMMAND,ARGS)
	isMarkdown  bool     // send message in markdown format
}

// Commands - list of all commands
type Commands map[string]Command

// Config - config struct
type Config struct {
	token                  string   // bot token
	botTimeout             int      // bot timeout
	predefinedAllowedUsers []string // telegram users who are allowed to chat with the bot
	predefinedRootUsers    []string // telegram users, who confirms new users in their private chat
	description            string   // description of bot
	usersDB                string   // file for store users
	cache                  int      // caching command out (in seconds)
	shTimeout              int      // timeout for execute shell command (in seconds)
	addExit                bool     // adding /shell2telegram exit command
	allowAll               bool     // allow all user (DANGEROUS!)
	logCommands            bool     // logging all commands
	persistentUsers        bool     // load/save users from file
	isPublicBot            bool     // bot is public (dont add /auth* commands)
}

// message types
const (
	msgIsText int8 = iota
	msgIsPhoto
)

// BotMessage - record for send via channel for send message to telegram chat
type BotMessage struct {
	message     string
	fileName    string
	photo       []byte
	chatID      int
	messageType int8
	isMarkdown  bool
}

// ----------------------------------------------------------------------------
// get config
func getConfig() (commands Commands, appConfig Config, err error) {
	flag.StringVar(&appConfig.token, "tb-token", "", "setting bot token (or set TB_TOKEN variable)")
	flag.BoolVar(&appConfig.addExit, "add-exit", false, "adding \"/shell2telegram exit\" command for terminate bot (for roots only)")
	flag.IntVar(&appConfig.botTimeout, "timeout", DefaultBotTimeout, "setting timeout for bot")
	flag.BoolVar(&appConfig.allowAll, "allow-all", false, "allow all users (DANGEROUS!)")
	flag.BoolVar(&appConfig.logCommands, "log-commands", false, "logging all commands")
	flag.StringVar(&appConfig.description, "description", "", "setting description of bot")
	flag.BoolVar(&appConfig.persistentUsers, "persistent_users", false, "load/save users from file (default ~/.config/shell2telegram.json)")
	flag.StringVar(&appConfig.usersDB, "users_db", "", "file for store users")
	flag.IntVar(&appConfig.cache, "cache", 0, "caching command out (in seconds)")
	flag.BoolVar(&appConfig.isPublicBot, "public", false, "bot is public (dont add /auth* commands)")
	flag.IntVar(&appConfig.shTimeout, "sh-timeout", 0, "set timeout for execute shell command (in seconds)")
	logFilename := flag.String("log", "", "log filename, default - STDOUT")
	predefinedAllowedUsers := flag.String("allow-users", "", "telegram users who are allowed to chat with the bot (\"user1,user2\")")
	predefinedRootUsers := flag.String("root-users", "", "telegram users, who confirms new users in their private chat (\"user1,user2\")")
	version := flag.Bool("version", false, "get version")

	flag.Usage = func() {
		fmt.Printf("usage: %s [options] %s\n%s\n%s\n\noptions:\n",
			os.Args[0],
			`/chat_command "shell command" /chat_command2 "shell command2"`,
			"All text after /chat_command will be sent to STDIN of shell command.",
			"If chat command is /:plain_text - get user message without any /command (for private chats only)",
		)
		flag.PrintDefaults()
		os.Exit(0)
	}
	flag.Parse()

	if *version {
		fmt.Println(Version)
		os.Exit(0)
	}

	// setup log file
	if len(*logFilename) > 0 {
		fhLog, err := os.OpenFile(*logFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
		if err != nil {
			log.Fatalf("error opening log file: %v", err)
		}
		log.SetOutput(fhLog)
	}

	// setup users and roots
	if *predefinedAllowedUsers != "" {
		appConfig.predefinedAllowedUsers = strings.Split(*predefinedAllowedUsers, ",")
	}
	if *predefinedRootUsers != "" {
		appConfig.predefinedRootUsers = strings.Split(*predefinedRootUsers, ",")
	}

	commands = Commands{}
	// need >= 2 arguments and count of it must be even
	args := flag.Args()
	if len(args) < 2 || len(args)%2 == 1 {
		return commands, appConfig, fmt.Errorf("error: need pairs of /chat-command and shell-command")
	}

	for i := 0; i < len(args); i += 2 {
		path, command, err := parseBotCommand(args[i], args[i+1]) // (/path, shell_command)
		if err != nil {
			return commands, appConfig, err
		}
		commands[path] = command
	}

	if appConfig.token == "" {
		if appConfig.token = os.Getenv("TB_TOKEN"); appConfig.token == "" {
			return commands, appConfig, fmt.Errorf("TB_TOKEN environment var not found. See https://core.telegram.org/bots#botfather for more information")
		}
	}

	return commands, appConfig, nil
}

// ----------------------------------------------------------------------------
func sendMessage(messageSignal chan<- BotMessage, chatID int, message []byte, isMarkdown bool) {
	go func() {
		var fileName string
		fileType := http.DetectContentType(message)
		switch fileType {
		case "image/png":
			fileName = "file.png"
		case "image/jpeg":
			fileName = "file.jpeg"
		case "image/gif":
			fileName = "file.gif"
		case "image/bmp":
			fileName = "file.bmp"
		default:
			fileName = "message"
		}

		if fileName == "message" {

			// is text message
			messageString := string(message)
			var messagesList []string

			if len(messageString) <= MaxMessageLength {
				messagesList = []string{messageString}
			} else {
				messagesList = splitStringLinesBySize(messageString, MaxMessageLength)
			}

			for _, messageChunk := range messagesList {
				messageSignal <- BotMessage{
					chatID:      chatID,
					messageType: msgIsText,
					message:     messageChunk,
					isMarkdown:  isMarkdown,
				}
			}

		} else {
			// is image
			messageSignal <- BotMessage{
				chatID:      chatID,
				messageType: msgIsPhoto,
				fileName:    fileName,
				photo:       message,
			}
		}
	}()
}

// ----------------------------------------------------------------------------
func main() {
	commands, appConfig, err := getConfig()
	if err != nil {
		log.Fatal(err)
	}

	bot, err := tgbotapi.NewBotAPI(appConfig.token)
	if err != nil {
		log.Fatal(err)
	}

	log.Printf("Authorized on bot account: @%s", bot.Self.UserName)

	tgbotConfig := tgbotapi.NewUpdate(0)
	tgbotConfig.Timeout = appConfig.botTimeout
	botUpdatesChan, err := bot.GetUpdatesChan(tgbotConfig)
	if err != nil {
		log.Fatal(err)
	}

	users := NewUsers(appConfig)
	messageSignal := make(chan BotMessage, MessagesQueueSize)
	vacuumTicker := time.Tick(SecondsForOldUsersBeforeVacuum * time.Second)
	saveToBDTicker := make(<-chan time.Time)
	exitSignal := make(chan struct{})
	systemExitSignal := make(chan os.Signal)
	signal.Notify(systemExitSignal, os.Interrupt, os.Kill)

	if appConfig.persistentUsers {
		saveToBDTicker = time.Tick(SecondsForAutoSaveUsersToDB * time.Second)
	}

	var cache raphanus.DB
	if appConfig.cache > 0 {
		cache = raphanus.New("", 0)
	}

	// all /shell2telegram sub-commands handlers
	internalCommands := map[string]func(Ctx) string{
		"stat":              cmdShell2telegramStat,
		"ban":               cmdShell2telegramBan,
		"search":            cmdShell2telegramSearch,
		"desc":              cmdShell2telegramDesc,
		"rm":                cmdShell2telegramRm,
		"exit":              cmdShell2telegramExit,
		"version":           cmdShell2telegramVersion,
		"broadcast_to_root": cmdShell2telegramBroadcastToRoot,
		"message_to_user":   cmdShell2telegramMessageToUser,
	}

	doExit := false
	for !doExit {
		select {
		case telegramUpdate := <-botUpdatesChan:

			var messageCmd, messageArgs string
			allUserMessage := telegramUpdate.Message.Text
			if len(allUserMessage) > 0 && allUserMessage[0] == '/' {
				messageCmd, messageArgs = splitStringHalfBySpace(allUserMessage)
			} else {
				messageCmd, messageArgs = cmdPlainText, allUserMessage
			}

			allowPlainText := false
			if _, ok := commands[cmdPlainText]; ok {
				allowPlainText = true
			}

			replayMsg := ""

			if len(messageCmd) > 0 && (messageCmd != cmdPlainText || allowPlainText) {

				users.AddNew(telegramUpdate.Message)
				userID := telegramUpdate.Message.From.ID
				allowExec := appConfig.allowAll || users.IsAuthorized(userID)

				ctx := Ctx{
					appConfig:     &appConfig,
					users:         &users,
					commands:      commands,
					userID:        userID,
					allowExec:     allowExec,
					messageCmd:    messageCmd,
					messageArgs:   messageArgs,
					messageSignal: messageSignal,
					chatID:        telegramUpdate.Message.Chat.ID,
					exitSignal:    exitSignal,
					cache:         &cache,
				}

				switch {
				// commands .................................
				case !appConfig.isPublicBot && (messageCmd == "/auth" || messageCmd == "/authroot"):
					replayMsg = cmdAuth(ctx)

				case messageCmd == "/help":
					replayMsg = cmdHelp(ctx)

				case messageCmd == "/shell2telegram" && users.IsRoot(userID):
					var messageSubCmd string
					messageSubCmd, messageArgs = splitStringHalfBySpace(messageArgs)
					ctx.messageArgs = messageArgs
					if cmdHandler, ok := internalCommands[messageSubCmd]; ok {
						replayMsg = cmdHandler(ctx)
					} else {
						replayMsg = "Sub-command not found"
					}

				case allowExec && (allowPlainText && messageCmd == cmdPlainText || messageCmd[0] == '/'):
					cmdUser(ctx)

				} // switch for commands

				if appConfig.logCommands {
					log.Printf("%s: %s", users.String(userID), allUserMessage)
				}

				sendMessage(messageSignal, telegramUpdate.Message.Chat.ID, []byte(replayMsg), false)
			}

		case botMessage := <-messageSignal:
			switch {
			case botMessage.messageType == msgIsText && !stringIsEmpty(botMessage.message):
				messageConfig := tgbotapi.NewMessage(botMessage.chatID, botMessage.message)
				if botMessage.isMarkdown {
					messageConfig.ParseMode = tgbotapi.ModeMarkdown
				}
				_, err = bot.Send(messageConfig)
			case botMessage.messageType == msgIsPhoto && len(botMessage.photo) > 0:
				bytesPhoto := tgbotapi.FileBytes{Name: botMessage.fileName, Bytes: botMessage.photo}
				_, err = bot.Send(tgbotapi.NewPhotoUpload(botMessage.chatID, bytesPhoto))
			}

			if err != nil {
				log.Print("Bot send message error: ", err)
			}

		case <-saveToBDTicker:
			users.SaveToDB(appConfig.usersDB)

		case <-vacuumTicker:
			users.ClearOldUsers()

		case <-systemExitSignal:
			go func() {
				exitSignal <- struct{}{}
			}()

		case <-exitSignal:
			if appConfig.persistentUsers {
				users.needSaveDB = true
				users.SaveToDB(appConfig.usersDB)
			}
			doExit = true
		}
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/lomik/carbon-clickhouse/carbon"
	"github.com/lomik/carbon-clickhouse/helper/RowBinary"
	"github.com/lomik/zapwriter"
	"github.com/uber-go/zap"

	_ "net/http/pprof"
)

// Version of carbon-clickhouse
const Version = "0.2"

func httpServe(addr string) (func(), error) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return nil, err
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		return nil, err
	}

	go http.Serve(listener, nil)
	return func() { listener.Close() }, nil
}

func main() {
	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/carbon-clickhouse/carbon-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	printVersion := flag.Bool("version", false, "Print version")
	cat := flag.String("cat", "", "Print RowBinary file in TabSeparated format")
	bincat := flag.String("recover", "", "Read all good records from corrupted data file. Write binary data to stdout")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *cat != "" {
		reader, err := RowBinary.NewReader(*cat)
		if err != nil {
			log.Fatal(err)
		}

		for {
			metric, err := reader.ReadRecord()
			if err != nil {
				if err == io.EOF {
					return
				}
				log.Fatal(err)
			}

			fmt.Printf("%s\t%#v\t%d\t%s\t%d\n",
				string(metric),
				reader.Value(),
				reader.Timestamp(),
				reader.DaysString(),
				reader.Version(),
			)
		}
	}

	if *bincat != "" {
		reader, err := RowBinary.NewReader(*bincat)
		if err != nil {
			log.Fatal(err)
		}

		io.Copy(os.Stdout, reader)
		return
	}

	cfg := carbon.NewConfig()

	if *printDefaultConfig {
		if err = carbon.PrintConfig(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	if err = carbon.ParseConfig(*configFile, cfg); err != nil {
		log.Fatal(err)
	}

	if *checkConfig {
		// check before logging init
		if _, err = carbon.New(cfg, zap.New(zap.NullEncoder())); err != nil {
			log.Fatal(err)
		}
		return
	}

	zapOutput, err := zapwriter.New(cfg.Logging.File)
	if err != nil {
		log.Fatal(err)
	}

	var logLevel zap.Level
	if err = logLevel.UnmarshalText([]byte(cfg.Logging.Level)); err != nil {
		log.Fatal(err)
	}

	dynamicLevel := zap.DynamicLevel()
	dynamicLevel.SetLevel(logLevel)

	logger := zap.New(
		zapwriter.NewMixedEncoder(),
		zap.AddCaller(),
		zap.Output(zapOutput),
		dynamicLevel,
	)

	app, err := carbon.New(cfg, logger)

	/* CONFIG end */

	// pprof
	if cfg.Pprof.Enabled {
		_, err = httpServe(cfg.Pprof.Listen)
		if err != nil {
			logger.Fatal("pprof listen failed", zap.Error(err))
		}
	}

	if err = app.Start(); err != nil {
		logger.Fatal("app start failed", zap.Error(err))
	} else {
		logger.Info("app started")
	}

	// go func() {
	// 	c := make(chan os.Signal, 1)
	// 	signal.Notify(c, syscall.SIGHUP)
	// 	for {
	// 		<-c
	// 		logrus.Info("HUP received. Reload config")
	// 		if err := app.ReloadConfig(); err != nil {
	// 			logrus.Errorf("Config reload failed: %s", err.Error())
	// 		} else {
	// 			logrus.Info("Config successfully reloaded")
	// 		}
	// 	}
	// }()

	app.Loop()

	logger.Info("app stopped")
}
<commit_msg>version 0.2.1<commit_after>package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"os"

	"github.com/lomik/carbon-clickhouse/carbon"
	"github.com/lomik/carbon-clickhouse/helper/RowBinary"
	"github.com/lomik/zapwriter"
	"github.com/uber-go/zap"

	_ "net/http/pprof"
)

// Version of carbon-clickhouse
const Version = "0.2.1"

func httpServe(addr string) (func(), error) {
	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return nil, err
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		return nil, err
	}

	go http.Serve(listener, nil)
	return func() { listener.Close() }, nil
}

func main() {
	var err error

	/* CONFIG start */

	configFile := flag.String("config", "/etc/carbon-clickhouse/carbon-clickhouse.conf", "Filename of config")
	printDefaultConfig := flag.Bool("config-print-default", false, "Print default config")
	checkConfig := flag.Bool("check-config", false, "Check config and exit")
	printVersion := flag.Bool("version", false, "Print version")
	cat := flag.String("cat", "", "Print RowBinary file in TabSeparated format")
	bincat := flag.String("recover", "", "Read all good records from corrupted data file. Write binary data to stdout")

	flag.Parse()

	if *printVersion {
		fmt.Print(Version)
		return
	}

	if *cat != "" {
		reader, err := RowBinary.NewReader(*cat)
		if err != nil {
			log.Fatal(err)
		}

		for {
			metric, err := reader.ReadRecord()
			if err != nil {
				if err == io.EOF {
					return
				}
				log.Fatal(err)
			}

			fmt.Printf("%s\t%#v\t%d\t%s\t%d\n",
				string(metric),
				reader.Value(),
				reader.Timestamp(),
				reader.DaysString(),
				reader.Version(),
			)
		}
	}

	if *bincat != "" {
		reader, err := RowBinary.NewReader(*bincat)
		if err != nil {
			log.Fatal(err)
		}

		io.Copy(os.Stdout, reader)
		return
	}

	cfg := carbon.NewConfig()

	if *printDefaultConfig {
		if err = carbon.PrintConfig(cfg); err != nil {
			log.Fatal(err)
		}
		return
	}

	if err = carbon.ParseConfig(*configFile, cfg); err != nil {
		log.Fatal(err)
	}

	if *checkConfig {
		// check before logging init
		if _, err = carbon.New(cfg, zap.New(zap.NullEncoder())); err != nil {
			log.Fatal(err)
		}
		return
	}

	zapOutput, err := zapwriter.New(cfg.Logging.File)
	if err != nil {
		log.Fatal(err)
	}

	var logLevel zap.Level
	if err = logLevel.UnmarshalText([]byte(cfg.Logging.Level)); err != nil {
		log.Fatal(err)
	}

	dynamicLevel := zap.DynamicLevel()
	dynamicLevel.SetLevel(logLevel)

	logger := zap.New(
		zapwriter.NewMixedEncoder(),
		zap.AddCaller(),
		zap.Output(zapOutput),
		dynamicLevel,
	)

	app, err := carbon.New(cfg, logger)

	/* CONFIG end */

	// pprof
	if cfg.Pprof.Enabled {
		_, err = httpServe(cfg.Pprof.Listen)
		if err != nil {
			logger.Fatal("pprof listen failed", zap.Error(err))
		}
	}

	if err = app.Start(); err != nil {
		logger.Fatal("app start failed", zap.Error(err))
	} else {
		logger.Info("app started")
	}

	// go func() {
	// 	c := make(chan os.Signal, 1)
	// 	signal.Notify(c, syscall.SIGHUP)
	// 	for {
	// 		<-c
	// 		logrus.Info("HUP received. Reload config")
	// 		if err := app.ReloadConfig(); err != nil {
	// 			logrus.Errorf("Config reload failed: %s", err.Error())
	// 		} else {
	// 			logrus.Info("Config successfully reloaded")
	// 		}
	// 	}
	// }()

	app.Loop()

	logger.Info("app stopped")
}
<|endoftext|>


<commit_before>package rabbithole

import (
	"net/url"
)

// ShovelStatus contains the configuration of a shovel
type ShovelStatus struct {
	// Shovel name
	Name string `json:"name"`
	// Virtual host this shovel belongs to
	Vhost string `json:"vhost"`
	// Type of this shovel
	Type string `json:"type"`
	// State of this shovel
	State string `json:"state"`
	// Timestamp of this shovel
	Timestamp string `json:"timestamp"`
}

//
// GET /api/shovels/{vhost}
//

// ListShovelStatus returns status of all shovels in a vhost
func (c *Client) ListShovelStatus(vhost string) (rec []ShovelStatus, err error) {
	req, err := newGETRequest(c, "shovels/"+url.PathEscape(vhost))
	if err != nil {
		return []ShovelStatus{}, err
	}

	if err = executeAndParseRequest(c, req, &rec); err != nil {
		return []ShovelStatus{}, err
	}

	return rec, nil
}
<commit_msg>fixed comment<commit_after>package rabbithole

import (
	"net/url"
)

// ShovelStatus contains the status of a shovel
type ShovelStatus struct {
	// Shovel name
	Name string `json:"name"`
	// Virtual host this shovel belongs to
	Vhost string `json:"vhost"`
	// Type of this shovel
	Type string `json:"type"`
	// State of this shovel
	State string `json:"state"`
	// Timestamp of this shovel
	Timestamp string `json:"timestamp"`
}

//
// GET /api/shovels/{vhost}
//

// ListShovelStatus returns status of all shovels in a vhost
func (c *Client) ListShovelStatus(vhost string) (rec []ShovelStatus, err error) {
	req, err := newGETRequest(c, "shovels/"+url.PathEscape(vhost))
	if err != nil {
		return []ShovelStatus{}, err
	}

	if err = executeAndParseRequest(c, req, &rec); err != nil {
		return []ShovelStatus{}, err
	}

	return rec, nil
}
<|endoftext|>