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

/*
Package inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool, 1),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	w.done <- true
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	for {
		n, err := syscall.Read(w.fd, buf[:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			// The syscall.Close can be slow.  Close
			// w.Event first.
			close(w.Event)
			err := syscall.Close(w.fd)
			if err != nil {
				w.Error <- os.NewSyscallError("close", err)
			}
			close(w.Error)
			return
		}
		if n < 0 {
			w.Error <- os.NewSyscallError("read", err)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(Event)
			event.Mask = uint32(raw.Mask)
			event.Cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}
			// Send the event on the events channel
			w.Event <- event

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value != 0 {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<commit_msg>Inotify: fix read hanging and watcher close<commit_after>// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	w.done <- true

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	stop := make(chan bool)
	go func() {
		var done bool

		for !done {
			go func() {
				done = <-w.done
			}()

			for !done {
				n, err := syscall.Read(w.fd, buf[:])
				// If EOF
				if n == 0 {
					w.done <- true
					return
				}
				if n < 0 {
					w.Error <- os.NewSyscallError("read", err)
					continue
				}
				if n < syscall.SizeofInotifyEvent {
					w.Error <- errors.New("inotify: short read in readEvents()")
					continue
				}

				var offset uint32 = 0
				// We don't know how many events we just read into the buffer
				// While the offset points to at least one whole event...
				for offset <= uint32(n-syscall.SizeofInotifyEvent) && !done {
					// Point "raw" to the event in the buffer
					raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
					event := new(Event)
					event.Mask = uint32(raw.Mask)
					event.Cookie = uint32(raw.Cookie)
					nameLen := uint32(raw.Len)
					// If the event happened to the watched directory or the watched file, the kernel
					// doesn't append the filename to the event, but we would like to always fill the
					// the "Name" field with a valid filename. We retrieve the path of the watch from
					// the "paths" map.
					w.mu.Lock()
					event.Name = w.paths[int(raw.Wd)]
					w.mu.Unlock()
					if nameLen > 0 {
						// Point "bytes" at the first byte of the filename
						bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
						// The filename is padded with NUL bytes. TrimRight() gets rid of those.
						event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
					}
					// Send the event on the events channel
					w.Event <- event

					// Move to the next event in the buffer
					offset += syscall.SizeofInotifyEvent + nameLen
				}
			}
		}
	}()

	<-stop
	// The syscall.Close can be slow.  Close
	// w.Event first.
	close(w.Event)
	err := syscall.Close(w.fd)
	if err != nil {
		w.Error <- os.NewSyscallError("close", err)
	}
	close(w.Error)
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value != 0 {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<|endoftext|>


<commit_before>package telegram

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"github.com/sirupsen/logrus"

	"yubari/pixiv"
)

const (
	tgDeleteTube = "tg_delete"
	tgPixivTube  = "tg_pixiv"

	pixivSplitWidth = 10000000
)

var (
	rePixivFileName = regexp.MustCompile(`(?P<id>\d+)_p(?P<seq>d+)\.(?P<ext>\w+)`)
)

type Config struct {
	Token          string  `json:"token"`
	SelfID         int64   `json:"selfID"`
	AdmissionID    int64   `json:"admissionID"`
	WhitelistChats []int64 `json:"whitelistChats"`
	ComicPath      string  `json:"comicPath"`
	DeleteDelay    string  `json:"deleteDelay"`
}

type DownloadPixiv struct {
	ChatID    int64
	MessageID int
	PixivID   uint64
	Text      string
}

type Bot struct {
	Name           string
	SelfID         int64
	AdmissionID    int64
	WhitelistChats []int64
	ComicPath      string
	PixivPath      string
	TwitterImgPath string
	DeleteDelay    time.Duration
	Client         *tgbotapi.BotAPI
	Queue          *bt.Pool
	logger         *logrus.Logger
	redis          *redis.Client
	es             *elasticsearch7.Client
}

func NewBot(cfg *Config) (b *Bot, err error) {
	bot, err := tgbotapi.NewBotAPI(cfg.Token)
	if err != nil {
		return nil, fmt.Errorf("tg bot init failed: %+v", err)
	}
	delay, err := time.ParseDuration(cfg.DeleteDelay)
	if err != nil {
		return nil, fmt.Errorf("delete delay error: %+v", err)
	}

	b = &Bot{
		Name:           bot.Self.UserName,
		SelfID:         cfg.SelfID,
		AdmissionID:    cfg.AdmissionID,
		WhitelistChats: cfg.WhitelistChats,
		ComicPath:      cfg.ComicPath,
		DeleteDelay:    delay,
		Client:         bot,
	}
	return
}

func (b *Bot) WithLogger(logger *logrus.Logger) *Bot {
	b.logger = logger
	return b
}

func (b *Bot) WithRedis(rds *redis.Client) *Bot {
	b.redis = rds
	return b
}

func (b *Bot) WithPixivImg(imgPath string) *Bot {
	b.PixivPath = imgPath
	return b
}

func (b *Bot) WithTwitterImg(imgPath string) *Bot {
	b.TwitterImgPath = imgPath
	return b
}

func (b *Bot) WithQueue(queue *bt.Pool) *Bot {
	b.Queue = queue
	return b
}

func (b *Bot) WithES(es *elasticsearch7.Client) *Bot {
	b.es = es
	return b
}

func (b *Bot) putQueue(msg []byte, tube string) {
	conn, err := b.Queue.Get()
	if err != nil {
		b.logger.Errorf("%+v: %s", err, string(msg))
		return
	}
	conn.Use(tube)
	_, err = conn.Put(msg, 1, b.DeleteDelay, time.Minute)
	if err != nil {
		b.logger.Errorf("%+v", err)
		return
	}
}

func (b *Bot) isAuthedChat(c *tgbotapi.Chat) bool {
	for _, w := range b.WhitelistChats {
		if c.ID == w {
			return true
		}
	}
	return false
}

func (b *Bot) Send(chat int64, msg string) (tgbotapi.Message, error) {
	b.logger.Debugf("[%d]%s", chat, msg)
	message := tgbotapi.NewMessage(chat, msg)
	message.DisableNotification = true
	return b.Client.Send(message)
}

func (b *Bot) GetUserName(chatID int64, userID int) (name string, err error) {
	cacheKey := fmt.Sprintf("tg:user:%d", userID)
	cache, err := b.redis.Get(cacheKey).Result()
	if err == nil {
		name = cache
		return
	} else {
		if err != redis.Nil {
			return
		}
	}
	member, err := b.Client.GetChatMember(tgbotapi.ChatConfigWithUser{
		ChatID: chatID,
		UserID: userID,
	})
	if err != nil {
		return
	}
	name = member.User.String()
	if name != "" {
		b.redis.Set(cacheKey, name, 0)
	}
	return
}

func (b *Bot) SendPixivCandidate(target int64, id uint64) {
	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⭕", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "like")),
		tgbotapi.NewInlineKeyboardButtonData("❌", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "diss")),
	)
	msg := tgbotapi.NewMessage(target, pixiv.URLWithID(id))
	msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(row)
	msg.DisableNotification = true
	_, err := b.Client.Send(msg)
	if err != nil {
		b.logger.Errorf("%+v", err)
	}
}

func (b *Bot) startDownloadPixiv() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgPixivTube)
		job, err := conn.Reserve(60 * time.Second)
		if err != nil {
			time.Sleep(3 * time.Second)
			continue
		}
		msg := &DownloadPixiv{}
		err = json.Unmarshal(job.Body, msg)
		if err != nil {
			b.logger.Errorf("%+v", err)
			err = conn.Bury(job.ID, 0)
			if err != nil {
				b.logger.Errorf("%+v", err)
			}
			time.Sleep(3 * time.Second)
			continue
		}

		sizes, errs := pixiv.Download(msg.PixivID, b.PixivPath)
		for i := range sizes {
			if errs[i] != nil {
				err = errs[i]
				break
			}
			if sizes[i] == 0 {
				continue
			}
			b.logger.Debugf("download pixiv %d_p%d: %s", msg.PixivID, i, ByteCountIEC(sizes[i]))
			msg.Text += fmt.Sprintf("\np%d - %s", i, ByteCountIEC(sizes[i]))
		}
		if err != nil {
			b.logger.Errorf("%+v", err)
			continue
		}

		updateTextMsg := tgbotapi.NewEditMessageText(
			msg.ChatID,
			msg.MessageID,
			msg.Text,
		)
		_, err = b.Client.Send(updateTextMsg)
		if err != nil {
			b.logger.Errorf("error update message text %s", err)
		}

		err = conn.Delete(job.ID)
		if err != nil {
			b.logger.Errorf("delete job error: %+v", err)
			time.Sleep(3 * time.Second)
		}
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) startDeleteMessage() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Get()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgDeleteTube)
		job, err := conn.Reserve(60 * time.Second)
		if err != nil {
			time.Sleep(3 * time.Second)
			continue
		}

		func() {
			var err error
			defer func() {
				if err != nil {
					b.logger.Errorf("%+v", err)
					if e := conn.Bury(job.ID, 0); e != nil {
						b.logger.Errorf("%+v", err)
					}
					time.Sleep(3 * time.Second)
				} else {
					if e := conn.Delete(job.ID); e != nil {
						b.logger.Errorf("%+v", err)
						time.Sleep(3 * time.Second)
					}
				}
			}()

			msg := &tgbotapi.Message{}
			err = json.Unmarshal(job.Body, msg)
			if err != nil {
				return
			}

			if msg.Chat == nil {
				err = fmt.Errorf("err msg with no chat: %+v", msg)
				return
			}
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    msg.Chat.ID,
				MessageID: msg.MessageID,
			}
			b.logger.Infof("del:[%s]{%s}", getMsgTitle(msg), strconv.Quote(msg.Text))
			_, err = b.Client.DeleteMessage(delMsg)

		}()
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) Start() {
	go b.startDeleteMessage()
	go b.startDownloadPixiv()

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 30
	for {
		updates, err := b.Client.GetUpdatesChan(u)
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		var message *tgbotapi.Message
		for update := range updates {
			if update.Message != nil {
				message = update.Message
			} else if update.EditedMessage != nil {
				message = update.EditedMessage
			} else if update.CallbackQuery != nil {
				b.logger.Infof(
					"recv:(%d)[%s]reaction:{%s}",
					update.CallbackQuery.Message.Chat.ID,
					update.CallbackQuery.From.String(),
					update.CallbackQuery.Data,
				)
				data := strings.SplitN(update.CallbackQuery.Data, ":", 2)
				switch data[0] {
				case "comic", "pic", "pixiv":
					go onReaction(b, update.CallbackQuery)
				case "pixivCandidate":
					if !b.isAuthedChat(update.CallbackQuery.Message.Chat) {
						b.logger.Warning("reaction from illegal chat, ignore")
						break
					}
					go onReactionCandidate(b, update.CallbackQuery)
				case "search":
					go onReactionSearch(b, update.CallbackQuery)
				default:
				}
				continue
			} else {
				continue
			}

			if !b.checkInWhitelist(message.Chat.ID) {
				continue
			}
			if message.Chat.IsGroup() {
				b.logger.Infof(
					"recv:(%d)[%s:%s]{%s}",
					message.Chat.ID,
					message.Chat.Title,
					message.From.String(),
					strconv.Quote(message.Text))
			} else {
				b.logger.Infof(
					"recv:(%d)[%s]{%s}",
					message.Chat.ID,
					message.From.String(),
					strconv.Quote(message.Text),
				)
			}
			if message.IsCommand() {
				switch message.Command() {
				case "start":
					go onStart(b, message)
				case "roll":
					go onRoll(b, message)
				case "comic":
					go onComic(b, message)
				case "pic":
					go onPic(b, message)
				case "pixiv":
					go onPixiv(b, message)
				case "search":
					go onSearch(b, message)
				default:
					b.logger.Infof("ignore unknown cmd: %+v", message.Command())
					continue
				}
			} else {
				if message.Text == "" {
					continue
				}
				go checkRepeat(b, message)
				go checkPixiv(b, message)
				go checkSave(b, message)
			}
		}
		b.logger.Warning("tg bot restarted.")
		time.Sleep(3 * time.Second)
	}
}

func (b *Bot) checkInWhitelist(id int64) bool {
	for _, c := range b.WhitelistChats {
		if c == id {
			return true
		}
	}
	b.logger.Debugf("ignore msg from %d", id)
	return false
}

func (b *Bot) probate(_type, _id string) error {
	b.logger.Infof("%s: %s", _type, _id)
	switch _type {
	case "comic":
		fileName := "nhentai.net@" + _id + ".epub"
		return os.Rename(
			filepath.Join(b.ComicPath, fileName),
			filepath.Join(b.ComicPath, "probation", fileName),
		)
	case "pic":
		return os.Rename(
			filepath.Join(b.TwitterImgPath, _id),
			filepath.Join(b.TwitterImgPath, "probation", _id),
		)
	case "pixiv":
		return os.Rename(
			filepath.Join(b.PixivPath, _id),
			filepath.Join(b.PixivPath, "probation", _id),
		)
	default:
		return fmt.Errorf("prohibit unkown type")
	}
}

func (b *Bot) setChatAction(chatID int64, action string) error {
	a := tgbotapi.NewChatAction(chatID, action)
	_, err := b.Client.Send(a)
	if err != nil {
		b.logger.Errorf("set action %s failed: %+v", action, err)
	}
	return err
}

func getMsgTitle(m *tgbotapi.Message) string {
	if m.Chat.IsGroup() {
		return m.Chat.Title
	}
	return m.From.String()
}
<commit_msg>fix: dial for watch queue<commit_after>package telegram

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	elasticsearch7 "github.com/elastic/go-elasticsearch/v7"
	"github.com/go-redis/redis"
	tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
	bt "github.com/ikool-cn/gobeanstalk-connection-pool"
	"github.com/sirupsen/logrus"

	"yubari/pixiv"
)

const (
	tgDeleteTube = "tg_delete"
	tgPixivTube  = "tg_pixiv"

	pixivSplitWidth = 10000000
)

var (
	rePixivFileName = regexp.MustCompile(`(?P<id>\d+)_p(?P<seq>d+)\.(?P<ext>\w+)`)
)

type Config struct {
	Token          string  `json:"token"`
	SelfID         int64   `json:"selfID"`
	AdmissionID    int64   `json:"admissionID"`
	WhitelistChats []int64 `json:"whitelistChats"`
	ComicPath      string  `json:"comicPath"`
	DeleteDelay    string  `json:"deleteDelay"`
}

type DownloadPixiv struct {
	ChatID    int64
	MessageID int
	PixivID   uint64
	Text      string
}

type Bot struct {
	Name           string
	SelfID         int64
	AdmissionID    int64
	WhitelistChats []int64
	ComicPath      string
	PixivPath      string
	TwitterImgPath string
	DeleteDelay    time.Duration
	Client         *tgbotapi.BotAPI
	Queue          *bt.Pool
	logger         *logrus.Logger
	redis          *redis.Client
	es             *elasticsearch7.Client
}

func NewBot(cfg *Config) (b *Bot, err error) {
	bot, err := tgbotapi.NewBotAPI(cfg.Token)
	if err != nil {
		return nil, fmt.Errorf("tg bot init failed: %+v", err)
	}
	delay, err := time.ParseDuration(cfg.DeleteDelay)
	if err != nil {
		return nil, fmt.Errorf("delete delay error: %+v", err)
	}

	b = &Bot{
		Name:           bot.Self.UserName,
		SelfID:         cfg.SelfID,
		AdmissionID:    cfg.AdmissionID,
		WhitelistChats: cfg.WhitelistChats,
		ComicPath:      cfg.ComicPath,
		DeleteDelay:    delay,
		Client:         bot,
	}
	return
}

func (b *Bot) WithLogger(logger *logrus.Logger) *Bot {
	b.logger = logger
	return b
}

func (b *Bot) WithRedis(rds *redis.Client) *Bot {
	b.redis = rds
	return b
}

func (b *Bot) WithPixivImg(imgPath string) *Bot {
	b.PixivPath = imgPath
	return b
}

func (b *Bot) WithTwitterImg(imgPath string) *Bot {
	b.TwitterImgPath = imgPath
	return b
}

func (b *Bot) WithQueue(queue *bt.Pool) *Bot {
	b.Queue = queue
	return b
}

func (b *Bot) WithES(es *elasticsearch7.Client) *Bot {
	b.es = es
	return b
}

func (b *Bot) putQueue(msg []byte, tube string) {
	conn, err := b.Queue.Get()
	if err != nil {
		b.logger.Errorf("%+v: %s", err, string(msg))
		return
	}
	conn.Use(tube)
	_, err = conn.Put(msg, 1, b.DeleteDelay, time.Minute)
	if err != nil {
		b.logger.Errorf("%+v", err)
		return
	}
}

func (b *Bot) isAuthedChat(c *tgbotapi.Chat) bool {
	for _, w := range b.WhitelistChats {
		if c.ID == w {
			return true
		}
	}
	return false
}

func (b *Bot) Send(chat int64, msg string) (tgbotapi.Message, error) {
	b.logger.Debugf("[%d]%s", chat, msg)
	message := tgbotapi.NewMessage(chat, msg)
	message.DisableNotification = true
	return b.Client.Send(message)
}

func (b *Bot) GetUserName(chatID int64, userID int) (name string, err error) {
	cacheKey := fmt.Sprintf("tg:user:%d", userID)
	cache, err := b.redis.Get(cacheKey).Result()
	if err == nil {
		name = cache
		return
	} else {
		if err != redis.Nil {
			return
		}
	}
	member, err := b.Client.GetChatMember(tgbotapi.ChatConfigWithUser{
		ChatID: chatID,
		UserID: userID,
	})
	if err != nil {
		return
	}
	name = member.User.String()
	if name != "" {
		b.redis.Set(cacheKey, name, 0)
	}
	return
}

func (b *Bot) SendPixivCandidate(target int64, id uint64) {
	row := tgbotapi.NewInlineKeyboardRow(
		tgbotapi.NewInlineKeyboardButtonData("⭕", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "like")),
		tgbotapi.NewInlineKeyboardButtonData("❌", buildReactionData("pixivCandidate", strconv.FormatUint(id, 10), "diss")),
	)
	msg := tgbotapi.NewMessage(target, pixiv.URLWithID(id))
	msg.ReplyMarkup = tgbotapi.NewInlineKeyboardMarkup(row)
	msg.DisableNotification = true
	_, err := b.Client.Send(msg)
	if err != nil {
		b.logger.Errorf("%+v", err)
	}
}

func (b *Bot) startDownloadPixiv() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Dial()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgPixivTube)
		job, err := conn.Reserve(60 * time.Second)
		if err != nil {
			time.Sleep(3 * time.Second)
			continue
		}
		msg := &DownloadPixiv{}
		err = json.Unmarshal(job.Body, msg)
		if err != nil {
			b.logger.Errorf("%+v", err)
			err = conn.Bury(job.ID, 0)
			if err != nil {
				b.logger.Errorf("%+v", err)
			}
			time.Sleep(3 * time.Second)
			continue
		}

		sizes, errs := pixiv.Download(msg.PixivID, b.PixivPath)
		for i := range sizes {
			if errs[i] != nil {
				err = errs[i]
				break
			}
			if sizes[i] == 0 {
				continue
			}
			b.logger.Debugf("download pixiv %d_p%d: %s", msg.PixivID, i, ByteCountIEC(sizes[i]))
			msg.Text += fmt.Sprintf("\np%d - %s", i, ByteCountIEC(sizes[i]))
		}
		if err != nil {
			b.logger.Errorf("%+v", err)
			continue
		}

		updateTextMsg := tgbotapi.NewEditMessageText(
			msg.ChatID,
			msg.MessageID,
			msg.Text,
		)
		_, err = b.Client.Send(updateTextMsg)
		if err != nil {
			b.logger.Errorf("error update message text %s", err)
		}

		err = conn.Delete(job.ID)
		if err != nil {
			b.logger.Errorf("delete job error: %+v", err)
			time.Sleep(3 * time.Second)
		}
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) startDeleteMessage() {
	time.Sleep(10 * time.Second)
	for {
		conn, err := b.Queue.Dial()
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		conn.Watch(tgDeleteTube)
		job, err := conn.Reserve(60 * time.Second)
		if err != nil {
			time.Sleep(3 * time.Second)
			continue
		}

		func() {
			var err error
			defer func() {
				if err != nil {
					b.logger.Errorf("%+v", err)
					if e := conn.Bury(job.ID, 0); e != nil {
						b.logger.Errorf("%+v", err)
					}
					time.Sleep(3 * time.Second)
				} else {
					if e := conn.Delete(job.ID); e != nil {
						b.logger.Errorf("%+v", err)
						time.Sleep(3 * time.Second)
					}
				}
			}()

			msg := &tgbotapi.Message{}
			err = json.Unmarshal(job.Body, msg)
			if err != nil {
				return
			}

			if msg.Chat == nil {
				err = fmt.Errorf("err msg with no chat: %+v", msg)
				return
			}
			delMsg := tgbotapi.DeleteMessageConfig{
				ChatID:    msg.Chat.ID,
				MessageID: msg.MessageID,
			}
			b.logger.Infof("del:[%s]{%s}", getMsgTitle(msg), strconv.Quote(msg.Text))
			_, err = b.Client.DeleteMessage(delMsg)

		}()
		b.Queue.Release(conn, false)
	}
}

func (b *Bot) Start() {
	go b.startDeleteMessage()
	go b.startDownloadPixiv()

	u := tgbotapi.NewUpdate(0)
	u.Timeout = 30
	for {
		updates, err := b.Client.GetUpdatesChan(u)
		if err != nil {
			b.logger.Errorf("%+v", err)
			time.Sleep(3 * time.Second)
			continue
		}
		var message *tgbotapi.Message
		for update := range updates {
			if update.Message != nil {
				message = update.Message
			} else if update.EditedMessage != nil {
				message = update.EditedMessage
			} else if update.CallbackQuery != nil {
				b.logger.Infof(
					"recv:(%d)[%s]reaction:{%s}",
					update.CallbackQuery.Message.Chat.ID,
					update.CallbackQuery.From.String(),
					update.CallbackQuery.Data,
				)
				data := strings.SplitN(update.CallbackQuery.Data, ":", 2)
				switch data[0] {
				case "comic", "pic", "pixiv":
					go onReaction(b, update.CallbackQuery)
				case "pixivCandidate":
					if !b.isAuthedChat(update.CallbackQuery.Message.Chat) {
						b.logger.Warning("reaction from illegal chat, ignore")
						break
					}
					go onReactionCandidate(b, update.CallbackQuery)
				case "search":
					go onReactionSearch(b, update.CallbackQuery)
				default:
				}
				continue
			} else {
				continue
			}

			if !b.checkInWhitelist(message.Chat.ID) {
				continue
			}
			if message.Chat.IsGroup() {
				b.logger.Infof(
					"recv:(%d)[%s:%s]{%s}",
					message.Chat.ID,
					message.Chat.Title,
					message.From.String(),
					strconv.Quote(message.Text))
			} else {
				b.logger.Infof(
					"recv:(%d)[%s]{%s}",
					message.Chat.ID,
					message.From.String(),
					strconv.Quote(message.Text),
				)
			}
			if message.IsCommand() {
				switch message.Command() {
				case "start":
					go onStart(b, message)
				case "roll":
					go onRoll(b, message)
				case "comic":
					go onComic(b, message)
				case "pic":
					go onPic(b, message)
				case "pixiv":
					go onPixiv(b, message)
				case "search":
					go onSearch(b, message)
				default:
					b.logger.Infof("ignore unknown cmd: %+v", message.Command())
					continue
				}
			} else {
				if message.Text == "" {
					continue
				}
				go checkRepeat(b, message)
				go checkPixiv(b, message)
				go checkSave(b, message)
			}
		}
		b.logger.Warning("tg bot restarted.")
		time.Sleep(3 * time.Second)
	}
}

func (b *Bot) checkInWhitelist(id int64) bool {
	for _, c := range b.WhitelistChats {
		if c == id {
			return true
		}
	}
	b.logger.Debugf("ignore msg from %d", id)
	return false
}

func (b *Bot) probate(_type, _id string) error {
	b.logger.Infof("%s: %s", _type, _id)
	switch _type {
	case "comic":
		fileName := "nhentai.net@" + _id + ".epub"
		return os.Rename(
			filepath.Join(b.ComicPath, fileName),
			filepath.Join(b.ComicPath, "probation", fileName),
		)
	case "pic":
		return os.Rename(
			filepath.Join(b.TwitterImgPath, _id),
			filepath.Join(b.TwitterImgPath, "probation", _id),
		)
	case "pixiv":
		return os.Rename(
			filepath.Join(b.PixivPath, _id),
			filepath.Join(b.PixivPath, "probation", _id),
		)
	default:
		return fmt.Errorf("prohibit unkown type")
	}
}

func (b *Bot) setChatAction(chatID int64, action string) error {
	a := tgbotapi.NewChatAction(chatID, action)
	_, err := b.Client.Send(a)
	if err != nil {
		b.logger.Errorf("set action %s failed: %+v", action, err)
	}
	return err
}

func getMsgTitle(m *tgbotapi.Message) string {
	if m.Chat.IsGroup() {
		return m.Chat.Title
	}
	return m.From.String()
}
<|endoftext|>


<commit_before>package integer_sample

import (
	"fmt"
	"math/rand"
)

type IntegerSample struct {
	Sample []string
	Size int
	candidate int
	count int
}

// Print prints the values of the sample to STDOUT.
func (sample *IntegerSample) Print() {
	for _, line := range sample.Sample {
		fmt.Println(line)
	}
}

// SampleLine performs a classic 'resevoir sample`.
// Choose a number, N, between 0 and , if N is >=  we return;
// if N is < len(`sample`) we replace it with `value`
func (sample *IntegerSample) SampleLine(line string) {

	if sample.count < sample.Size {
		goto ADD_SAMPLE
	}

	sample.candidate = rand.Intn(sample.count)
	if sample.candidate < sample.Size {
		sample.Sample[sample.candidate] = line
		goto INCREMENT_COUNT
	}

	return

ADD_SAMPLE:
	sample.Sample[sample.count] = line
	goto INCREMENT_COUNT

INCREMENT_COUNT:
	sample.count++

	return
}
<commit_msg>edit comments<commit_after>package integer_sample

import (
	"fmt"
	"math/rand"
)

type IntegerSample struct {
	Sample []string
	Size int
	candidate int
	count int
}

// Print prints the values of the sample to STDOUT.
func (sample *IntegerSample) Print() {
	for _, line := range sample.Sample {
		fmt.Println(line)
	}
}

// SampleLine performs a classic 'resevoir sample'.
// Choose a number, N, between 0 and , if N is >= sample.Size
// we return; if N is < sample.Size we replace it with `line` at
// the Nth index of sample.Sample.
func (sample *IntegerSample) SampleLine(line string) {

	if sample.count < sample.Size {
		goto ADD_SAMPLE
	}

	sample.candidate = rand.Intn(sample.count)
	if sample.candidate < sample.Size {
		sample.Sample[sample.candidate] = line
		goto INCREMENT_COUNT
	}

	return

ADD_SAMPLE:
	sample.Sample[sample.count] = line
	goto INCREMENT_COUNT

INCREMENT_COUNT:
	sample.count++

	return
}
<|endoftext|>


<commit_before>package mux

import (
	"io"

	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/serial"
)

// ReadMetadata reads FrameMetadata from the given reader.
func ReadMetadata(reader io.Reader) (*FrameMetadata, error) {
	metaLen, err := serial.ReadUint16(reader)
	if err != nil {
		return nil, err
	}
	if metaLen > 512 {
		return nil, newError("invalid metalen ", metaLen).AtError()
	}

	b := buf.New()
	defer b.Release()

	if err := b.Reset(buf.ReadFullFrom(reader, int(metaLen))); err != nil {
		return nil, err
	}
	return ReadFrameFrom(b)
}

// PacketReader is an io.Reader that reads whole chunk of Mux frames every time.
type PacketReader struct {
	reader io.Reader
	eof    bool
}

// NewPacketReader creates a new PacketReader.
func NewPacketReader(reader io.Reader) *PacketReader {
	return &PacketReader{
		reader: reader,
		eof:    false,
	}
}

// ReadMultiBuffer implements buf.Reader.
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
	if r.eof {
		return nil, io.EOF
	}

	size, err := serial.ReadUint16(r.reader)
	if err != nil {
		return nil, err
	}

	b := buf.NewSize(uint32(size))
	if err := b.AppendSupplier(buf.ReadFullFrom(r.reader, int(size))); err != nil {
		b.Release()
		return nil, err
	}
	r.eof = true
	return buf.NewMultiBufferValue(b), nil
}

// StreamReader reads Mux frame as a stream.
type StreamReader struct {
	reader   *buf.BufferedReader
	leftOver int
}

// NewStreamReader creates a new StreamReader.
func NewStreamReader(reader *buf.BufferedReader) *StreamReader {
	return &StreamReader{
		reader:   reader,
		leftOver: -1,
	}
}

// ReadMultiBuffer implmenets buf.Reader.
func (r *StreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
	if r.leftOver == 0 {
		r.leftOver = -1
		return nil, io.EOF
	}

	if r.leftOver == -1 {
		size, err := serial.ReadUint16(r.reader)
		if err != nil {
			return nil, err
		}
		r.leftOver = int(size)
	}

	mb, err := r.reader.ReadAtMost(r.leftOver)
	r.leftOver -= mb.Len()
	return mb, err
}
<commit_msg>int to int32<commit_after>package mux

import (
	"io"

	"v2ray.com/core/common/buf"
	"v2ray.com/core/common/serial"
)

// ReadMetadata reads FrameMetadata from the given reader.
func ReadMetadata(reader io.Reader) (*FrameMetadata, error) {
	metaLen, err := serial.ReadUint16(reader)
	if err != nil {
		return nil, err
	}
	if metaLen > 512 {
		return nil, newError("invalid metalen ", metaLen).AtError()
	}

	b := buf.New()
	defer b.Release()

	if err := b.Reset(buf.ReadFullFrom(reader, int(metaLen))); err != nil {
		return nil, err
	}
	return ReadFrameFrom(b)
}

// PacketReader is an io.Reader that reads whole chunk of Mux frames every time.
type PacketReader struct {
	reader io.Reader
	eof    bool
}

// NewPacketReader creates a new PacketReader.
func NewPacketReader(reader io.Reader) *PacketReader {
	return &PacketReader{
		reader: reader,
		eof:    false,
	}
}

// ReadMultiBuffer implements buf.Reader.
func (r *PacketReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
	if r.eof {
		return nil, io.EOF
	}

	size, err := serial.ReadUint16(r.reader)
	if err != nil {
		return nil, err
	}

	b := buf.NewSize(uint32(size))
	if err := b.AppendSupplier(buf.ReadFullFrom(r.reader, int(size))); err != nil {
		b.Release()
		return nil, err
	}
	r.eof = true
	return buf.NewMultiBufferValue(b), nil
}

// StreamReader reads Mux frame as a stream.
type StreamReader struct {
	reader   *buf.BufferedReader
	leftOver int32
}

// NewStreamReader creates a new StreamReader.
func NewStreamReader(reader *buf.BufferedReader) *StreamReader {
	return &StreamReader{
		reader:   reader,
		leftOver: -1,
	}
}

// ReadMultiBuffer implmenets buf.Reader.
func (r *StreamReader) ReadMultiBuffer() (buf.MultiBuffer, error) {
	if r.leftOver == 0 {
		r.leftOver = -1
		return nil, io.EOF
	}

	if r.leftOver == -1 {
		size, err := serial.ReadUint16(r.reader)
		if err != nil {
			return nil, err
		}
		r.leftOver = int32(size)
	}

	mb, err := r.reader.ReadAtMost(int(r.leftOver))
	r.leftOver -= int32(mb.Len())
	return mb, err
}
<|endoftext|>


<commit_before>package integration

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"code.cloudfoundry.org/cli/integration/helpers"

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

var _ = Describe("Push", func() {
	Context("when the environment is set up correctly", func() {
		var (
			orgName   string
			spaceName string
		)

		BeforeEach(func() {
			orgName = helpers.NewOrgName()
			spaceName = helpers.PrefixedRandomName("SPACE")

			setupCF(orgName, spaceName)
		})

		Context("when the app has over 260 character paths", func() {
			var tmpDir string

			BeforeEach(func() {
				var err error
				tmpDir, err = ioutil.TempDir("", "")
				Expect(err).ToNot(HaveOccurred())

				dirName := "dir_name"
				dirNames := []string{}
				for i := 0; i < 32; i++ { // minimum 300 chars, including separators
					dirNames = append(dirNames, dirName)
				}

				fullPath := filepath.Join(tmpDir, filepath.Join(dirNames...))
				if runtime.GOOS == "windows" {
					// `\\?\` is used to skip Windows' file name processor, which imposes
					// length limits. Search MSDN for 'Maximum Path Length Limitation' for
					// more.
					fullPath = `\\?\` + fullPath
				}
				err = os.MkdirAll(fullPath, os.ModeDir|os.ModePerm)
				Expect(err).NotTo(HaveOccurred())

				err = ioutil.WriteFile(filepath.Join(fullPath, "index.html"), []byte("hello world"), 0666)
				Expect(err).ToNot(HaveOccurred())
			})

			It("successfully pushes the app", func() {
				defer os.RemoveAll(tmpDir)
				appName := helpers.PrefixedRandomName("APP")
				session := helpers.CF("push", appName, "-p", tmpDir, "-b", "staticfile_buildpack")
				Eventually(session).Should(Say("1 of 1 instances running"))
				Eventually(session).Should(Say("App %s was started using this command", appName))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("when pushing with manifest routes and specifying the -n flag", func() {
			var (
				quotaName     string
				appDir        string
				manifestPath  string
				privateDomain helpers.Domain
				sharedDomain  helpers.Domain
				tcpDomain     helpers.Domain
			)

			BeforeEach(func() {
				quotaName = helpers.PrefixedRandomName("INTEGRATION-QUOTA")

				session := helpers.CF("create-quota", quotaName, "-m", "10G", "-r", "10", "--reserved-route-ports", "4")
				Eventually(session).Should(Exit(0))
				session = helpers.CF("set-quota", orgName, quotaName)
				Eventually(session).Should(Exit(0))

				privateDomain = helpers.NewDomain(orgName, helpers.DomainName("private"))
				privateDomain.Create()
				sharedDomain = helpers.NewDomain(orgName, helpers.DomainName("shared"))
				sharedDomain.CreateShared()
				tcpDomain = helpers.NewDomain(orgName, helpers.DomainName("tcp"))
				tcpDomain.CreateWithRouterGroup("default-tcp")

				var err error
				appDir, err = ioutil.TempDir("", "simple-app")
				Expect(err).ToNot(HaveOccurred())
				manifestContents := []byte(fmt.Sprintf(`
---
applications:
- name: app-with-routes
  memory: 100M
  instances: 1
  path: .
  routes:
  - route: %s
  - route: %s
  - route: manifest-host.%s/path
  - route: %s:1100
`, privateDomain.Name, sharedDomain.Name, sharedDomain.Name, tcpDomain.Name))
				manifestPath = filepath.Join(appDir, "manifest.yml")
				err = ioutil.WriteFile(manifestPath, manifestContents, 0666)
				Expect(err).ToNot(HaveOccurred())

				err = ioutil.WriteFile(filepath.Join(appDir, "index.html"), []byte("hello world"), 0666)
				Expect(err).ToNot(HaveOccurred())
			})

			It("should set or replace the route's hostname with the flag value", func() {
				defer os.RemoveAll(appDir)
				var session *Session
				session = helpers.CF("push", helpers.PrefixedRandomName("APP"), "-p", appDir, "-n", "flag-hostname", "-b", "staticfile_buildpack", "-f", manifestPath)

				Eventually(session).Should(Say("Creating route flag-hostname.%s...\nOK", privateDomain.Name))
				Eventually(session).Should(Say("Creating route flag-hostname.%s...\nOK", sharedDomain.Name))
				Eventually(session).Should(Say("Creating route flag-hostname.%s/path...\nOK", sharedDomain.Name))
				Eventually(session).Should(Say("Creating route %s:1100...\nOK", tcpDomain.Name))
				Eventually(session).Should(Exit(0))
			})
		})
	})
})
<commit_msg>skipping test as it hasn't actually worked in a long time<commit_after>package integration

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"

	"code.cloudfoundry.org/cli/integration/helpers"

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

var _ = Describe("Push", func() {
	Context("when the environment is set up correctly", func() {
		var (
			orgName   string
			spaceName string
		)

		BeforeEach(func() {
			orgName = helpers.NewOrgName()
			spaceName = helpers.PrefixedRandomName("SPACE")

			setupCF(orgName, spaceName)
		})

		Context("when the app has over 260 character paths", func() {
			var tmpDir string

			BeforeEach(func() {
				Skip("Unskip when #134888875 is complete")
				var err error
				tmpDir, err = ioutil.TempDir("", "")
				Expect(err).ToNot(HaveOccurred())

				dirName := "dir_name"
				dirNames := []string{}
				for i := 0; i < 32; i++ { // minimum 300 chars, including separators
					dirNames = append(dirNames, dirName)
				}

				fullPath := filepath.Join(tmpDir, filepath.Join(dirNames...))
				if runtime.GOOS == "windows" {
					// `\\?\` is used to skip Windows' file name processor, which imposes
					// length limits. Search MSDN for 'Maximum Path Length Limitation' for
					// more.
					fullPath = `\\?\` + fullPath
				}
				err = os.MkdirAll(fullPath, os.ModeDir|os.ModePerm)
				Expect(err).NotTo(HaveOccurred())

				err = ioutil.WriteFile(filepath.Join(fullPath, "index.html"), []byte("hello world"), 0666)
				Expect(err).ToNot(HaveOccurred())
			})

			It("successfully pushes the app", func() {
				defer os.RemoveAll(tmpDir)
				appName := helpers.PrefixedRandomName("APP")
				session := helpers.CF("push", appName, "-p", tmpDir, "-b", "staticfile_buildpack")
				Eventually(session).Should(Say("1 of 1 instances running"))
				Eventually(session).Should(Say("App %s was started using this command", appName))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("when pushing with manifest routes and specifying the -n flag", func() {
			var (
				quotaName     string
				appDir        string
				manifestPath  string
				privateDomain helpers.Domain
				sharedDomain  helpers.Domain
				tcpDomain     helpers.Domain
			)

			BeforeEach(func() {
				quotaName = helpers.PrefixedRandomName("INTEGRATION-QUOTA")

				session := helpers.CF("create-quota", quotaName, "-m", "10G", "-r", "10", "--reserved-route-ports", "4")
				Eventually(session).Should(Exit(0))
				session = helpers.CF("set-quota", orgName, quotaName)
				Eventually(session).Should(Exit(0))

				privateDomain = helpers.NewDomain(orgName, helpers.DomainName("private"))
				privateDomain.Create()
				sharedDomain = helpers.NewDomain(orgName, helpers.DomainName("shared"))
				sharedDomain.CreateShared()
				tcpDomain = helpers.NewDomain(orgName, helpers.DomainName("tcp"))
				tcpDomain.CreateWithRouterGroup("default-tcp")

				var err error
				appDir, err = ioutil.TempDir("", "simple-app")
				Expect(err).ToNot(HaveOccurred())
				manifestContents := []byte(fmt.Sprintf(`
---
applications:
- name: app-with-routes
  memory: 100M
  instances: 1
  path: .
  routes:
  - route: %s
  - route: %s
  - route: manifest-host.%s/path
  - route: %s:1100
`, privateDomain.Name, sharedDomain.Name, sharedDomain.Name, tcpDomain.Name))
				manifestPath = filepath.Join(appDir, "manifest.yml")
				err = ioutil.WriteFile(manifestPath, manifestContents, 0666)
				Expect(err).ToNot(HaveOccurred())

				err = ioutil.WriteFile(filepath.Join(appDir, "index.html"), []byte("hello world"), 0666)
				Expect(err).ToNot(HaveOccurred())
			})

			It("should set or replace the route's hostname with the flag value", func() {
				defer os.RemoveAll(appDir)
				var session *Session
				session = helpers.CF("push", helpers.PrefixedRandomName("APP"), "-p", appDir, "-n", "flag-hostname", "-b", "staticfile_buildpack", "-f", manifestPath)

				Eventually(session).Should(Say("Creating route flag-hostname.%s...\nOK", privateDomain.Name))
				Eventually(session).Should(Say("Creating route flag-hostname.%s...\nOK", sharedDomain.Name))
				Eventually(session).Should(Say("Creating route flag-hostname.%s/path...\nOK", sharedDomain.Name))
				Eventually(session).Should(Say("Creating route %s:1100...\nOK", tcpDomain.Name))
				Eventually(session).Should(Exit(0))
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	ent "github.com/fclairamb/m2mp/go/m2mp-db/entities"
	mq "github.com/fclairamb/m2mp/go/m2mp-messaging"
	pr "github.com/fclairamb/m2mp/go/m2mp-protocol"
	"net"
	"strings"
	"time"
)

type ClientHandler struct {
	Id                      int
	daddy                   *Server
	connectionTime          time.Time
	device                  *ent.Device
	deviceChannelTranslator *ent.DeviceChannelTrans
	Conn                    *pr.ProtoHandler
	connRecv                chan interface{}
	LogLevel                int
	lastReceivedData        time.Time
	lastSentData            time.Time
	ticker                  *time.Ticker
	pingCounter             byte
	//connSend         chan interface{}
}

func NewClientHandler(daddy *Server, id int, conn net.Conn) *ClientHandler {
	now := time.Now().UTC()
	ch := &ClientHandler{
		daddy:            daddy,
		Id:               id,
		Conn:             pr.NewProtoHandlerServer(conn),
		connectionTime:   now,
		LogLevel:         9,
		connRecv:         make(chan interface{}, 3),
		ticker:           time.NewTicker(time.Second * 30),
		lastReceivedData: now,
		lastSentData:     now,
		//connSend:       make(chan interface{}, 3),
	}

	return ch
}

func (ch *ClientHandler) Start() {
	if ch.LogLevel >= 3 {
		log.Debug("Added %s", ch)
	}
	go ch.runRecv()
	go ch.runCoreHandling()

	{
		m := mq.NewMessageEvent("device_connected")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		ch.daddy.SendMessage(m)
	}
}

func (ch *ClientHandler) Close() error {
	return ch.Conn.Conn.Close()
}

func (ch *ClientHandler) end() {
	ch.daddy.removeClientHandler(ch)
	if ch.LogLevel >= 3 {
		log.Debug("Removed %s", ch)
	}
	ch.ticker.Stop()

	{
		m := mq.NewMessageEvent("device_disconnected")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		m.Data.Set("connection_time", fmt.Sprint(int64(time.Now().UTC().Sub(ch.connectionTime).Seconds())))
		if ch.device != nil {
			m.Data.Set("device_id", ch.device.Id())
		}
		ch.daddy.SendMessage(m)
	}
}

func (ch *ClientHandler) runRecv() {
	defer ch.end()
	for {
		msg := ch.Conn.Recv()
		ch.connRecv <- msg
		switch msg.(type) {
		// If this is a disconnection event, then we should quit the go routine
		case *pr.EventDisconnected:
			{
				return
			}
		}
	}
}

func (ch *ClientHandler) Send(m interface{}) error {
	err := ch.Conn.Send(m)
	ch.lastSentData = time.Now().UTC()
	return err
}

func (ch *ClientHandler) receivedData() {
	ch.lastReceivedData = time.Now().UTC()
}

func (ch *ClientHandler) considerCurrentStatus() {
	now := time.Now().UTC()
	if ch.LogLevel >= 5 {
		log.Debug("%s - Considering current status (%s)", ch, now)
	}
	if now.Sub(ch.lastReceivedData) > time.Duration(time.Minute*15) &&
		now.Sub(ch.lastSentData) > time.Duration(time.Second*30) {
		ch.Send(&pr.MessagePingRequest{Data: ch.pingCounter})
		ch.pingCounter += 1
	}
}

func (ch *ClientHandler) runCoreHandling() {
	for {
		select {
		// We receive all events coming from the connection, the ticker (and later the server)
		case msg := <-ch.connRecv:
			{
				switch m := msg.(type) {
				case *pr.MessageDataSimple:
					ch.receivedData()
					ch.handleData(m)
				case *pr.MessageDataArray:
					ch.receivedData()
					ch.handleDataArray(m)
				case *pr.MessageIdentRequest:
					ch.receivedData()
					ch.handleIdentRequest(m)
				case *pr.MessagePingRequest:
					{
						ch.receivedData()
						ch.Conn.Send(&pr.MessagePingResponse{Data: m.Data})
					}
					// If this is a disconnection event, we should quit the current go routine
				case *pr.EventDisconnected:
					{
						return
					}
				}

			}
		case <-ch.ticker.C:
			{
				if ch.LogLevel >= 5 {
					log.Debug("%s - Tick", ch)
				}
			}
		}

		ch.considerCurrentStatus()
	}
}

func (ch *ClientHandler) handleIdentRequest(m *pr.MessageIdentRequest) error {
	var err error
	ch.device, err = ent.NewDeviceByIdentCreate(m.Ident)
	if err != nil {
		log.Warning("%s --> (error) %s", ch, err)
	}

	// OK
	if err == nil {
		err = ch.Send(&pr.MessageIdentResponse{Ok: true})
		if err == nil {
			err = ch.justIdentified()
		}
		return err
	} else {
		return ch.Send(&pr.MessageIdentResponse{Ok: false})
	}
}

func (ch *ClientHandler) checkCapacities() error {
	status := ch.device.Status("cap")
	if status == "" {
		msg := pr.NewMessageDataArray("_sta")
		msg.AddString("g")
		msg.AddString("cap")
		return ch.Send(msg)
	}
	return nil
}

func (ch *ClientHandler) getDeviceChannelTranslator() *ent.DeviceChannelTrans {
	if ch.deviceChannelTranslator == nil && ch.device != nil {
		ch.deviceChannelTranslator = ent.NewDeviceChannelTrans(ch.device)
	}
	return ch.deviceChannelTranslator
}

func (ch *ClientHandler) justIdentified() error {
	err := ch.sendSettings()

	if err == nil {
		err = ch.sendCommands()
	}

	if err == nil {
		err = ch.checkCapacities()
	}

	if err == nil {
		err = ch.checkSettings()
	}

	{
		m := mq.NewMessageEvent("device_identified")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		m.Data.Set("device_id", ch.device.Id())
		ch.daddy.SendMessage(m)
	}

	return err
}

func (ch *ClientHandler) handleData(msg *pr.MessageDataSimple) error {
	//if par.LogLevel >= 7 {
	//	log.Print(ch, " --> \"", msg.Channel, "\" : ", msg.Data)
	//}

	tokens := strings.SplitN(msg.Channel, ":", 2)

	switch tokens[0] {
	case "echo": // echo is just replied
		{
			ch.Conn.Send(msg)
		}

	default:
		{
			if dct := ch.getDeviceChannelTranslator(); dct != nil {
				if target := dct.GetTarget(msg.Channel); target != nil {
					msg := mq.NewJsonWrapper()
					msg.SetTo(*target)
					msg.SetFrom(fmt.Sprintf(":conn:%d", ch.Id))
					msg.SetCall("data_simple")
					ch.daddy.SendMessage(msg)
					log.Debug("Sending %s", msg)
				} else if tokens[0] == "sen" {
					ch.device.SaveTSTime(msg.Channel, time.Now().UTC(), string(msg.Data))
				}
			}
		}
	}

	return nil
}

func (ch *ClientHandler) handleDataArray(msg *pr.MessageDataArray) error {

	if ch.LogLevel >= 7 {
		log.Debug("%s --> \"%s\" : %s", ch, msg.Channel, msg.Data)
	}

	if msg.Channel == "_set" {
		return ch.handleDataArraySettings(msg)
	} else if msg.Channel == "_sta" {
		return ch.handleDataArrayStatus(msg)
	} else if msg.Channel == "_cmd" {
		return ch.handleDataArrayCommand(msg)
	}

	return nil
}

func (ch *ClientHandler) handleDataArraySettings(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if strings.Contains(requestType, "g") {
		for i := 1; i < len(msg.Data); i++ {
			v := string(msg.Data[i])
			tokens := strings.SplitN(v, "=", 2)
			if len(tokens) == 2 {
				ch.device.AckSetting(tokens[0], tokens[1])
			}
		}
	}
	return nil
}

func (ch *ClientHandler) handleDataArrayStatus(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if strings.Contains(requestType, "g") {
		for i := 1; i < len(msg.Data); i++ {
			v := string(msg.Data[i])
			tokens := strings.SplitN(v, "=", 2)
			if len(tokens) == 2 {
				ch.device.SetStatus(tokens[0], tokens[1])
			}
		}
	}
	return nil
}

func (ch *ClientHandler) handleDataArrayCommand(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if strings.Contains(requestType, "a") { // acknowledge
		if len(msg.Data) >= 2 {
			cmdId := string(msg.Data[1])
			if len(msg.Data) > 2 {
				var buffer bytes.Buffer
				for _, d := range msg.Data[2:] {
					buffer.WriteString(string(d))
					buffer.WriteString("\n")
				}
				ch.device.AckCommandWithResponse(cmdId, buffer.String())
			} else {
				ch.device.AckCommand(cmdId)
			}
		}
	}

	return nil
}

func (ch *ClientHandler) sendSettings() error {
	msg := pr.NewMessageDataArray("_set")
	msg.AddString("sg")
	c := 0
	for k, v := range ch.device.SettingsToSend() {
		msg.AddString(fmt.Sprintf("%s=%s", k, v))
		c += 1
	}
	if c != 0 {
		return ch.Send(msg)
	} else {
		return nil
	}
}

func (ch *ClientHandler) checkSettings() error {
	if len(ch.device.Settings()) == 0 {
		msg := pr.NewMessageDataArray("_set")
		msg.AddString("ga")
		return ch.Send(msg)
	}
	return nil
}

func (ch *ClientHandler) sendCommands() error {

	for k, v := range ch.device.Commands() {
		msg := pr.NewMessageDataArray("_cmd")
		msg.AddString("e")
		msg.AddString(k)
		for _, s := range strings.Split(v, "\n") {
			msg.AddString(s)
		}
		if err := ch.Send(msg); err != nil {
			log.Debug("%s - Send( %s ): %s", ch, msg.Channel, msg.Data)
			return err
		}
	}
	return nil
}

func (ch *ClientHandler) String() string {
	return fmt.Sprintf("CH{Id=%d,Conn=%v}", ch.Id, ch.Conn)
}
<commit_msg>Added get-all from client settings management code.<commit_after>package main

import (
	"bytes"
	"fmt"
	ent "github.com/fclairamb/m2mp/go/m2mp-db/entities"
	mq "github.com/fclairamb/m2mp/go/m2mp-messaging"
	pr "github.com/fclairamb/m2mp/go/m2mp-protocol"
	"net"
	"strings"
	"time"
)

type ClientHandler struct {
	Id                      int
	daddy                   *Server
	connectionTime          time.Time
	device                  *ent.Device
	deviceChannelTranslator *ent.DeviceChannelTrans
	Conn                    *pr.ProtoHandler
	connRecv                chan interface{}
	LogLevel                int
	lastReceivedData        time.Time
	lastSentData            time.Time
	ticker                  *time.Ticker
	pingCounter             byte
	//connSend         chan interface{}
}

func NewClientHandler(daddy *Server, id int, conn net.Conn) *ClientHandler {
	now := time.Now().UTC()
	ch := &ClientHandler{
		daddy:            daddy,
		Id:               id,
		Conn:             pr.NewProtoHandlerServer(conn),
		connectionTime:   now,
		LogLevel:         9,
		connRecv:         make(chan interface{}, 3),
		ticker:           time.NewTicker(time.Second * 30),
		lastReceivedData: now,
		lastSentData:     now,
		//connSend:       make(chan interface{}, 3),
	}

	return ch
}

func (ch *ClientHandler) Start() {
	if ch.LogLevel >= 3 {
		log.Debug("Added %s", ch)
	}
	go ch.runRecv()
	go ch.runCoreHandling()

	{
		m := mq.NewMessageEvent("device_connected")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		ch.daddy.SendMessage(m)
	}
}

func (ch *ClientHandler) Close() error {
	return ch.Conn.Conn.Close()
}

func (ch *ClientHandler) end() {
	ch.daddy.removeClientHandler(ch)
	if ch.LogLevel >= 3 {
		log.Debug("Removed %s", ch)
	}
	ch.ticker.Stop()

	{
		m := mq.NewMessageEvent("device_disconnected")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		m.Data.Set("connection_time", fmt.Sprint(int64(time.Now().UTC().Sub(ch.connectionTime).Seconds())))
		if ch.device != nil {
			m.Data.Set("device_id", ch.device.Id())
		}
		ch.daddy.SendMessage(m)
	}
}

func (ch *ClientHandler) runRecv() {
	defer ch.end()
	for {
		msg := ch.Conn.Recv()
		ch.connRecv <- msg
		switch msg.(type) {
		// If this is a disconnection event, then we should quit the go routine
		case *pr.EventDisconnected:
			{
				return
			}
		}
	}
}

func (ch *ClientHandler) Send(m interface{}) error {
	err := ch.Conn.Send(m)
	ch.lastSentData = time.Now().UTC()
	return err
}

func (ch *ClientHandler) receivedData() {
	ch.lastReceivedData = time.Now().UTC()
}

func (ch *ClientHandler) considerCurrentStatus() {
	now := time.Now().UTC()
	if ch.LogLevel >= 5 {
		log.Debug("%s - Considering current status (%s)", ch, now)
	}
	if now.Sub(ch.lastReceivedData) > time.Duration(time.Minute*15) &&
		now.Sub(ch.lastSentData) > time.Duration(time.Second*30) {
		ch.Send(&pr.MessagePingRequest{Data: ch.pingCounter})
		ch.pingCounter += 1
	}
}

func (ch *ClientHandler) runCoreHandling() {
	for {
		select {
		// We receive all events coming from the connection, the ticker (and later the server)
		case msg := <-ch.connRecv:
			{
				switch m := msg.(type) {
				case *pr.MessageDataSimple:
					ch.receivedData()
					ch.handleData(m)
				case *pr.MessageDataArray:
					ch.receivedData()
					ch.handleDataArray(m)
				case *pr.MessageIdentRequest:
					ch.receivedData()
					ch.handleIdentRequest(m)
				case *pr.MessagePingRequest:
					{
						ch.receivedData()
						ch.Conn.Send(&pr.MessagePingResponse{Data: m.Data})
					}
					// If this is a disconnection event, we should quit the current go routine
				case *pr.EventDisconnected:
					{
						return
					}
				}

			}
		case <-ch.ticker.C:
			{
				if ch.LogLevel >= 5 {
					log.Debug("%s - Tick", ch)
				}
			}
		}

		ch.considerCurrentStatus()
	}
}

func (ch *ClientHandler) handleIdentRequest(m *pr.MessageIdentRequest) error {
	var err error
	ch.device, err = ent.NewDeviceByIdentCreate(m.Ident)
	if err != nil {
		log.Warning("%s --> (error) %s", ch, err)
	}

	// OK
	if err == nil {
		err = ch.Send(&pr.MessageIdentResponse{Ok: true})
		if err == nil {
			err = ch.justIdentified()
		}
		return err
	} else {
		return ch.Send(&pr.MessageIdentResponse{Ok: false})
	}
}

func (ch *ClientHandler) checkCapacities() error {
	status := ch.device.Status("cap")
	if status == "" {
		msg := pr.NewMessageDataArray("_sta")
		msg.AddString("g")
		msg.AddString("cap")
		return ch.Send(msg)
	}
	return nil
}

func (ch *ClientHandler) getDeviceChannelTranslator() *ent.DeviceChannelTrans {
	if ch.deviceChannelTranslator == nil && ch.device != nil {
		ch.deviceChannelTranslator = ent.NewDeviceChannelTrans(ch.device)
	}
	return ch.deviceChannelTranslator
}

func (ch *ClientHandler) justIdentified() error {
	err := ch.sendSettingsToSend()

	if err == nil {
		err = ch.sendCommands()
	}

	if err == nil {
		err = ch.checkCapacities()
	}

	if err == nil {
		err = ch.checkSettings()
	}

	{
		m := mq.NewMessageEvent("device_identified")
		m.Data.Set("source", ch.Conn.Conn.RemoteAddr().String())
		m.Data.Set("connection_id", fmt.Sprint(ch.Id))
		m.Data.Set("device_id", ch.device.Id())
		ch.daddy.SendMessage(m)
	}

	return err
}

func (ch *ClientHandler) handleData(msg *pr.MessageDataSimple) error {
	//if par.LogLevel >= 7 {
	//	log.Print(ch, " --> \"", msg.Channel, "\" : ", msg.Data)
	//}

	tokens := strings.SplitN(msg.Channel, ":", 2)

	switch tokens[0] {
	case "echo": // echo is just replied
		{
			ch.Conn.Send(msg)
		}

	default:
		{
			if dct := ch.getDeviceChannelTranslator(); dct != nil {
				if target := dct.GetTarget(msg.Channel); target != nil {
					msg := mq.NewJsonWrapper()
					msg.SetTo(*target)
					msg.SetFrom(fmt.Sprintf(":conn:%d", ch.Id))
					msg.SetCall("data_simple")
					ch.daddy.SendMessage(msg)
					log.Debug("Sending %s", msg)
				} else if tokens[0] == "sen" {
					ch.device.SaveTSTime(msg.Channel, time.Now().UTC(), string(msg.Data))
				}
			}
		}
	}

	return nil
}

func (ch *ClientHandler) handleDataArray(msg *pr.MessageDataArray) error {

	if ch.LogLevel >= 7 {
		log.Debug("%s --> \"%s\" : %s", ch, msg.Channel, msg.Data)
	}

	if msg.Channel == "_set" {
		return ch.handleDataArraySettings(msg)
	} else if msg.Channel == "_sta" {
		return ch.handleDataArrayStatus(msg)
	} else if msg.Channel == "_cmd" {
		return ch.handleDataArrayCommand(msg)
	}

	return nil
}

func (ch *ClientHandler) handleDataArraySettings(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if requestType == "ga" {
		return ch.sendSettingsAll()
	}

	if strings.Contains(requestType, "g") {
		for i := 1; i < len(msg.Data); i++ {
			v := string(msg.Data[i])
			tokens := strings.SplitN(v, "=", 2)
			if len(tokens) == 2 {
				ch.device.AckSetting(tokens[0], tokens[1])
			}
		}
	}
	return nil
}

func (ch *ClientHandler) handleDataArrayStatus(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if strings.Contains(requestType, "g") {
		for i := 1; i < len(msg.Data); i++ {
			v := string(msg.Data[i])
			tokens := strings.SplitN(v, "=", 2)
			if len(tokens) == 2 {
				ch.device.SetStatus(tokens[0], tokens[1])
			}
		}
	}
	return nil
}

func (ch *ClientHandler) handleDataArrayCommand(msg *pr.MessageDataArray) error {
	if ch.device == nil {
		log.Warning("We don't have a device yet !")
		return nil
	}

	requestType := string(msg.Data[0])

	if strings.Contains(requestType, "a") { // acknowledge
		if len(msg.Data) >= 2 {
			cmdId := string(msg.Data[1])
			if len(msg.Data) > 2 {
				var buffer bytes.Buffer
				for _, d := range msg.Data[2:] {
					buffer.WriteString(string(d))
					buffer.WriteString("\n")
				}
				ch.device.AckCommandWithResponse(cmdId, buffer.String())
			} else {
				ch.device.AckCommand(cmdId)
			}
		}
	}

	return nil
}

func (ch *ClientHandler) sendSettings(settings map[string]string) error {
	msg := pr.NewMessageDataArray("_set")
	msg.AddString("sg")
	c := 0
	for k, v := range settings {
		msg.AddString(fmt.Sprintf("%s=%s", k, v))
		c += 1
	}
	if c != 0 {
		return ch.Send(msg)
	} else {
		return nil
	}
}

func (ch *ClientHandler) sendSettingsToSend() error {
	return ch.sendSettings(ch.device.SettingsToSend())
}

func (ch *ClientHandler) sendSettingsAll() error {
	log.Info("%s - Sending all settings", ch)
	return ch.sendSettings(ch.device.Settings())
}

func (ch *ClientHandler) checkSettings() error {
	if len(ch.device.Settings()) == 0 {
		msg := pr.NewMessageDataArray("_set")
		msg.AddString("ga")
		return ch.Send(msg)
	}
	return nil
}

func (ch *ClientHandler) sendCommands() error {

	for k, v := range ch.device.Commands() {
		msg := pr.NewMessageDataArray("_cmd")
		msg.AddString("e")
		msg.AddString(k)
		for _, s := range strings.Split(v, "\n") {
			msg.AddString(s)
		}
		if err := ch.Send(msg); err != nil {
			log.Debug("%s - Send( %s ): %s", ch, msg.Channel, msg.Data)
			return err
		}
	}
	return nil
}

func (ch *ClientHandler) String() string {
	return fmt.Sprintf("CH{Id=%d,Conn=%v}", ch.Id, ch.Conn)
}
<|endoftext|>


<commit_before>package cipher

import (
	"fmt"
	"strings"
	"unicode"
)

type caesar struct {
	distance int
}
type shift struct {
	distance int
}
type vigenere struct {
	key string
}

func NewCaesar() Cipher {
	return caesar{
		distance: 3,
	}
}

func (c caesar) Encode(s string) (encoded string) {
	return NewShift(c.distance).Encode(s)
}

func (c caesar) Decode(s string) (decoded string) {
	return NewShift(-c.distance).Encode(s)
}

func NewShift(distance int) Cipher {
	if distance >= 26 || distance == 0 || distance <= -26 {
		return nil
	}
	return shift{distance: distance}
}

func (c shift) Encode(input string) (encoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, c.distance))
		}
	}
	return encoded
}

func (c shift) Decode(input string) (decoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -c.distance))
		}
	}
	return decoded
}

func NewVigenere(key string) Cipher {
	if !isValid(key) {
		return nil
	}
	return vigenere{
		key: key,
	}
}

func (v vigenere) Encode(input string) (encoded string) {
	for i, r := range stripFormatting(input) {
		distance := getDistance(v.key, i)
		if unicode.IsLetter(r) {
			fmt.Printf("r: %v, distance: %v, shifted: %v\n", string(r), distance, string(shiftLetter(r, distance)))
			encoded += string(shiftLetter(r, distance))
		}
	}
	return encoded
}

func (v vigenere) Decode(input string) (decoded string) {
	for i, r := range stripFormatting(input) {
		distance := getDistance(v.key, i)
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -distance))
		}
	}
	return decoded
}

func shiftLetter(letter rune, distance int) (shifted rune) {
	// HACKHACK go % operator returns a negative number for -2 % 26 so add 26 then modulo 26
	// https://stackoverflow.com/questions/43018206/modulo-of-negative-integers-in-go
	d := rune(((int(letter-97)+distance)%26)+26)%26 + 97
	return rune(d)
}

func getDistance(key string, index int) (distance int) {
	position := index % len(key)
	distance = int([]rune(key)[position] - 97)
	fmt.Printf("key %v, position %v, distance %v\n", key, position, distance)
	return distance
}

func stripFormatting(input string) (stripped string) {
	for _, c := range strings.ToLower(input) {
		if unicode.IsLetter(c) {
			stripped += string(c)
		}
	}
	return stripped
}

func isValid(key string) bool {
	if key == "" {
		return false
	}
	if key == "a" || key == "aa" {
		return false
	}
	if stripFormatting(key) != key {
		return false
	}
	return true
}
<commit_msg>Remove debug line<commit_after>package cipher

import (
	"fmt"
	"strings"
	"unicode"
)

type caesar struct {
	distance int
}
type shift struct {
	distance int
}
type vigenere struct {
	key string
}

func NewCaesar() Cipher {
	return caesar{
		distance: 3,
	}
}

func (c caesar) Encode(s string) (encoded string) {
	return NewShift(c.distance).Encode(s)
}

func (c caesar) Decode(s string) (decoded string) {
	return NewShift(-c.distance).Encode(s)
}

func NewShift(distance int) Cipher {
	if distance >= 26 || distance == 0 || distance <= -26 {
		return nil
	}
	return shift{distance: distance}
}

func (c shift) Encode(input string) (encoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, c.distance))
		}
	}
	return encoded
}

func (c shift) Decode(input string) (decoded string) {
	for _, r := range strings.ToLower(input) {
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -c.distance))
		}
	}
	return decoded
}

func NewVigenere(key string) Cipher {
	if !isValid(key) {
		return nil
	}
	return vigenere{
		key: key,
	}
}

func (v vigenere) Encode(input string) (encoded string) {
	for i, r := range stripFormatting(input) {
		distance := getDistance(v.key, i)
		if unicode.IsLetter(r) {
			encoded += string(shiftLetter(r, distance))
		}
	}
	return encoded
}

func (v vigenere) Decode(input string) (decoded string) {
	for i, r := range stripFormatting(input) {
		distance := getDistance(v.key, i)
		if unicode.IsLetter(r) {
			decoded += string(shiftLetter(r, -distance))
		}
	}
	return decoded
}

func shiftLetter(letter rune, distance int) (shifted rune) {
	// HACKHACK go % operator returns a negative number for -2 % 26 so add 26 then modulo 26
	// https://stackoverflow.com/questions/43018206/modulo-of-negative-integers-in-go
	d := rune(((int(letter-97)+distance)%26)+26)%26 + 97
	return rune(d)
}

func getDistance(key string, index int) (distance int) {
	position := index % len(key)
	distance = int([]rune(key)[position] - 97)
	fmt.Printf("key %v, position %v, distance %v\n", key, position, distance)
	return distance
}

func stripFormatting(input string) (stripped string) {
	for _, c := range strings.ToLower(input) {
		if unicode.IsLetter(c) {
			stripped += string(c)
		}
	}
	return stripped
}

func isValid(key string) bool {
	if key == "" {
		return false
	}
	if key == "a" || key == "aa" {
		return false
	}
	if stripFormatting(key) != key {
		return false
	}
	return true
}
<|endoftext|>


<commit_before>package main

import (
	"koding/db/mongodb/modelhelper"
	"time"
)

// request arguments
type requestArgs struct {
	MachineId string `json:"machineId"`
	Reason    string `json:"reason"`
}

func stopVm(machineId, username, reason string) error {
	if controller.Klient == nil {
		Log.Debug("Klient not initialized. Not stopping: %s", machineId)
		return nil
	}

	_, err := controller.Klient.Tell("stop", &requestArgs{
		MachineId: machineId, Reason: reason,
	})

	return err
}

var BlockDuration = time.Hour * 24 * 365

func blockUserAndDestroyVm(machineId, username, reason string) error {
	machines, err := modelhelper.GetMachinesForUsername(username)
	if err != nil {
		return err
	}

	if controller.Klient != nil {
		for _, machine := range machines {
			_, err := controller.Klient.Tell("destroy", &requestArgs{
				MachineId: machine.ObjectId.Hex()},
			)

			if err != nil {
				Log.Error(err.Error())
			}
		}
	}

	return modelhelper.BlockUser(username, reason, BlockDuration)
}
<commit_msg>vmwatcher: stop vms for blocked users, not destroy them<commit_after>package main

import (
	"koding/db/mongodb/modelhelper"
	"time"
)

// request arguments
type requestArgs struct {
	MachineId string `json:"machineId"`
	Reason    string `json:"reason"`
}

func stopVm(machineId, username, reason string) error {
	if controller.Klient == nil {
		Log.Debug("Klient not initialized. Not stopping: %s", machineId)
		return nil
	}

	_, err := controller.Klient.Tell("stop", &requestArgs{
		MachineId: machineId, Reason: reason,
	})

	return err
}

var BlockDuration = time.Hour * 24 * 365

func blockUserAndDestroyVm(machineId, username, reason string) error {
	machines, err := modelhelper.GetMachinesForUsername(username)
	if err != nil {
		return err
	}

	if controller.Klient != nil {
		for _, machine := range machines {
			_, err := controller.Klient.Tell("stop", &requestArgs{
				MachineId: machine.ObjectId.Hex()},
			)

			if err != nil {
				Log.Error(err.Error())
			}
		}
	}

	return modelhelper.BlockUser(username, reason, BlockDuration)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"socialapi/models"
)

func testMessageOperations() {
	post, err := createPost(CHANNEL_ID, ACCOUNT_ID)
	if err != nil {
		fmt.Println("error while creating post", err)
		err = nil
	}
	_, err = updatePost(post)
	if err != nil {
		fmt.Println("error while creating post", err)
		err = nil
	}

	post2, err := getPost(post.Id)
	if err != nil {
		fmt.Println("error while getting the post", err)
		err = nil
	}

	if post2.CreatedAt.Second() != post.CreatedAt.Second() {
		fmt.Println("post created ats are not same")
	}
}

func createPost(channelId, accountId int64) (*models.ChannelMessage, error) {
	return createPostWithBody(channelId, accountId, "create a message")
}

func createPostWithBody(channelId, accountId int64, body string) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	cm.Body = body
	cm.AccountId = accountId

	url := fmt.Sprintf("/channel/%d/message", channelId)
	cmI, err := sendModel("POST", url, cm)
	if err != nil {
		return nil, err
	}
	return cmI.(*models.ChannelMessage), nil
}

func updatePost(cm *models.ChannelMessage) (*models.ChannelMessage, error) {
	cm.Body = "after update"

	url := fmt.Sprintf("/message/%d", cm.Id)
	cmI, err := sendModel("POST", url, cm)
	if err != nil {
		return nil, err
	}

	return cmI.(*models.ChannelMessage), nil
}

func getPost(id int64) (*models.ChannelMessage, error) {

	url := fmt.Sprintf("/message/%d", id)
	cm := models.NewChannelMessage()
	cmI, err := sendModel("GET", url, cm)
	if err != nil {
		return nil, err
	}
	return cmI.(*models.ChannelMessage), nil
}

func deletePost(id int64, accountId int64, groupName string) error {
	url := fmt.Sprintf("/message/%d?accountId=%d&groupName=%s", id, accountId, groupName)
	_, err := sendRequest("DELETE", url, nil)
	return err
}
<commit_msg>Social: change signature for getPost function<commit_after>package main

import (
	"fmt"
	"socialapi/models"
)

func testMessageOperations() {
	post, err := createPost(CHANNEL_ID, ACCOUNT_ID)
	if err != nil {
		fmt.Println("error while creating post", err)
		err = nil
	}
	_, err = updatePost(post)
	if err != nil {
		fmt.Println("error while creating post", err)
		err = nil
	}
}

func createPost(channelId, accountId int64) (*models.ChannelMessage, error) {
	return createPostWithBody(channelId, accountId, "create a message")
}

func createPostWithBody(channelId, accountId int64, body string) (*models.ChannelMessage, error) {
	cm := models.NewChannelMessage()
	cm.Body = body
	cm.AccountId = accountId

	url := fmt.Sprintf("/channel/%d/message", channelId)
	cmI, err := sendModel("POST", url, cm)
	if err != nil {
		return nil, err
	}
	return cmI.(*models.ChannelMessage), nil
}

func updatePost(cm *models.ChannelMessage) (*models.ChannelMessage, error) {
	cm.Body = "after update"

	url := fmt.Sprintf("/message/%d", cm.Id)
	cmI, err := sendModel("POST", url, cm)
	if err != nil {
		return nil, err
	}

	return cmI.(*models.ChannelMessage), nil
}

func getPost(id int64, accountId int64, groupName string) (*models.ChannelMessage, error) {
	url := fmt.Sprintf("/message/%d?accountId=%d&groupName=%s", id)
	cm := models.NewChannelMessage()
	cmI, err := sendModel("GET", url, cm)
	if err != nil {
		return nil, err
	}
	return cmI.(*models.ChannelMessage), nil
}

func deletePost(id int64, accountId int64, groupName string) error {
	url := fmt.Sprintf("/message/%d?accountId=%d&groupName=%s", id, accountId, groupName)
	_, err := sendRequest("DELETE", url, nil)
	return err
}
<|endoftext|>


<commit_before>// Package events defines the structures used for events dispatched from the
// wrangler package.
package events

import (
	"github.com/youtube/vitess/go/event"
	"github.com/youtube/vitess/go/vt/topo"
)

// Reparent is an event that describes a single step in the reparent process.
type Reparent struct {
	ShardInfo topo.ShardInfo

	OldMaster, NewMaster topo.Tablet

	Status string
}

// UpdateStatus sets a new status and then dispatches the event.
func (r *Reparent) UpdateStatus(status string) {
	r.Status = status

	// make a copy since we're calling Dispatch asynchronously
	ev := *r
	go event.Dispatch(&ev)
}
<commit_msg>Add eventID to Reparent event for grouping.<commit_after>// Package events defines the structures used for events dispatched from the
// wrangler package.
package events

import (
	"time"

	"github.com/youtube/vitess/go/event"
	"github.com/youtube/vitess/go/vt/topo"
)

// Reparent is an event that describes a single step in the reparent process.
type Reparent struct {
	ShardInfo topo.ShardInfo

	OldMaster, NewMaster topo.Tablet

	Status string

	// eventID is used to group the steps of a single reparent in progress.
	// It is set internally the first time UpdateStatus() is called.
	eventID int64
}

// UpdateStatus sets a new status and then dispatches the event.
func (r *Reparent) UpdateStatus(status string) {
	r.Status = status

	// initialize event ID
	if r.eventID == 0 {
		r.eventID = time.Now().UnixNano()
	}

	// make a copy since we're calling Dispatch asynchronously
	ev := *r
	go event.Dispatch(&ev)
}
<|endoftext|>


<commit_before>package pagerduty

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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.
 */

import (
	"fmt"
	"log"
	"strings"

	"github.com/netflix/hal-9001/hal"
)

const PageUsage = `!page <alias> [optional message]

Send an alert via Pagerduty with an optional custom message.

Aliases that have a comma-separated list of service keys will result in one page going to each service key when the alias is paged.

!page core
!page core <message>
!pagecore HELP ME YOU ARE MY ONLY HOPE

!page add <alias> <service key>
!page add <alias> <service key>,<service_key>,<service_key>,...
!page rm <alias>
!page list
`

const PageDefaultMessage = `HAL: your presence is requested in the chat room.`

func page(msg hal.Evt) {
	parts := msg.BodyAsArgv()

	// detect concatenated command + team name & split them
	// e.g. "!pagecore" -> {"!page", "core"}
	if strings.HasPrefix(parts[0], "!page") && len(parts[0]) > 5 {
		team := strings.TrimPrefix(parts[0], "!page")
		parts = append([]string{"!page", team}, parts[1:]...)
	}

	// should be 2 parts now, "!page" and the target team
	if parts[0] != "!page" || len(parts) < 2 {
		msg.Reply(PageUsage)
		return
	}

	switch parts[1] {
	case "h", "help":
		msg.Reply(PageUsage)
	case "add":
		addAlias(msg, parts[2:])
	case "rm":
		rmAlias(msg, parts[2:])
	case "list":
		listAlias(msg)
	default:
		pageAlias(msg, parts[1:])
	}
}

func pageAlias(msg hal.Evt, parts []string) {
	pageMessage := PageDefaultMessage
	if len(parts) > 1 {
		pageMessage = strings.Join(parts, " ")
	}

	// map alias name to PD token via prefs
	key := aliasKey(parts[0])
	pref := msg.AsPref().FindKey(key).One()

	// make sure the query succeeded
	if !pref.Success {
		log.Printf("Unable to access preferences: %#q", pref.Error)
		msg.Replyf("Unable to access preferences: %#q", pref.Error)
		return
	}

	// if qpref.Get returned the default, the alias was not found
	if pref.Value == "" {
		msg.Replyf("Alias %q not recognized. Try !page add <alias> <service key>", parts[0])
		return
	}

	// make sure the hal secrets are set up
	token, err := getSecrets()
	if err != nil {
		msg.Error(err)
		return
	}

	// the value can be a list of tokens, separated by commas
	for _, svckey := range strings.Split(pref.Value, ",") {
		// create the event and send it
		pde := NewTrigger(svckey, pageMessage) // in ./pagerduty.go
		resp, err := pde.Send(token)
		if err != nil {
			msg.Replyf("Error while communicating with Pagerduty. %d %s", resp.StatusCode, resp.Message)
			return
		}

		log.Printf("Pagerduty response message: %s\n", resp.Message)
	}

	msg.Replyf("Notification sent.")
}

func addAlias(msg hal.Evt, parts []string) {
	if len(parts) < 2 {
		msg.Replyf("!page add requires 2 arguments, e.g. !page add sysadmins XXXXXXX")
		return
	} else if len(parts) > 2 {
		keys := strings.Replace(strings.Join(parts[1:], ","), ",,", ",", len(parts)-2)
		parts = []string{parts[0], keys}
	}

	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	pref.Key = aliasKey(parts[0])
	pref.Value = parts[1]

	err := pref.Set()
	if err != nil {
		msg.Replyf("Write failed: %s", err)
	} else {
		msg.Replyf("Added alias: %q -> %q", parts[0], parts[1])
	}
}

func rmAlias(msg hal.Evt, parts []string) {
	if len(parts) != 1 {
		msg.Replyf("!page rm requires 1 argument, e.g. !page rm sysadmins")
		return
	}

	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	pref.Key = aliasKey(parts[0])
	pref.Delete()

	msg.Replyf("Removed alias %q", parts[0])
}

func listAlias(msg hal.Evt) {
	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	prefs := pref.GetPrefs()
	data := prefs.Table()
	msg.ReplyTable(data[0], data[1:])
}

func aliasKey(alias string) string {
	return fmt.Sprintf("alias.%s", alias)
}
<commit_msg>improve error messages for pagerduty !page plugin<commit_after>package pagerduty

/*
 * Copyright 2016 Albert P. Tobey <atobey@netflix.com>
 *
 * 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.
 */

import (
	"fmt"
	"log"
	"strings"

	"github.com/netflix/hal-9001/hal"
)

const PageUsage = `!page <alias> [optional message]

Send an alert via Pagerduty with an optional custom message.

Aliases that have a comma-separated list of service keys will result in one page going to each service key when the alias is paged.

!page core
!page core <message>
!pagecore HELP ME YOU ARE MY ONLY HOPE

!page add <alias> <service key>
!page add <alias> <service key>,<service_key>,<service_key>,...
!page rm <alias>
!page list
`

const PageDefaultMessage = `HAL: your presence is requested in the chat room.`

func page(msg hal.Evt) {
	parts := msg.BodyAsArgv()

	// detect concatenated command + team name & split them
	// e.g. "!pagecore" -> {"!page", "core"}
	if strings.HasPrefix(parts[0], "!page") && len(parts[0]) > 5 {
		team := strings.TrimPrefix(parts[0], "!page")
		parts = append([]string{"!page", team}, parts[1:]...)
	}

	// should be 2 parts now, "!page" and the target team
	if parts[0] != "!page" || len(parts) < 2 {
		msg.Reply(PageUsage)
		return
	}

	switch parts[1] {
	case "h", "help":
		msg.Reply(PageUsage)
	case "add":
		addAlias(msg, parts[2:])
	case "rm":
		rmAlias(msg, parts[2:])
	case "list":
		listAlias(msg)
	default:
		pageAlias(msg, parts[1:])
	}
}

func pageAlias(msg hal.Evt, parts []string) {
	pageMessage := PageDefaultMessage
	if len(parts) > 1 {
		pageMessage = strings.Join(parts, " ")
	}

	// map alias name to PD token via prefs
	key := aliasKey(parts[0])
	pref := msg.AsPref().FindKey(key).One()

	// make sure the query succeeded
	if !pref.Success {
		if pref.Error != nil {
			msg.Replyf("Unable to access preferences: %#q", pref.Error)
		} else {
			msg.Replyf("Alias %q is not configured. Try !page add %s <pagerduty integration key>", parts[0], parts[0])
		}
		return
	}

	// if qpref.Get returned the default, the alias was not found
	if pref.Value == "" {
		msg.Replyf("Alias %q is not configured. Try !page add %s <pagerduty integration key>", parts[0], parts[0])
		return
	}

	// make sure the hal secrets are set up
	token, err := getSecrets()
	if err != nil {
		msg.Error(err)
		return
	}

	// the value can be a list of tokens, separated by commas
	for _, svckey := range strings.Split(pref.Value, ",") {
		// create the event and send it
		pde := NewTrigger(svckey, pageMessage) // in ./pagerduty.go
		resp, err := pde.Send(token)
		if err != nil {
			msg.Replyf("Error while communicating with Pagerduty. %d %s", resp.StatusCode, resp.Message)
			return
		}

		log.Printf("Pagerduty response message: %s\n", resp.Message)
	}

	msg.Replyf("Notification sent.")
}

func addAlias(msg hal.Evt, parts []string) {
	if len(parts) < 2 {
		msg.Replyf("!page add requires 2 arguments, e.g. !page add sysadmins XXXXXXX")
		return
	} else if len(parts) > 2 {
		keys := strings.Replace(strings.Join(parts[1:], ","), ",,", ",", len(parts)-2)
		parts = []string{parts[0], keys}
	}

	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	pref.Key = aliasKey(parts[0])
	pref.Value = parts[1]

	err := pref.Set()
	if err != nil {
		msg.Replyf("Write failed: %s", err)
	} else {
		msg.Replyf("Added alias: %q -> %q", parts[0], parts[1])
	}
}

func rmAlias(msg hal.Evt, parts []string) {
	if len(parts) != 1 {
		msg.Replyf("!page rm requires 1 argument, e.g. !page rm sysadmins")
		return
	}

	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	pref.Key = aliasKey(parts[0])
	pref.Delete()

	msg.Replyf("Removed alias %q", parts[0])
}

func listAlias(msg hal.Evt) {
	pref := msg.AsPref()
	pref.User = "" // filled in by AsPref and unwanted
	prefs := pref.GetPrefs()
	data := prefs.Table()
	msg.ReplyTable(data[0], data[1:])
}

func aliasKey(alias string) string {
	return fmt.Sprintf("alias.%s", alias)
}
<|endoftext|>


<commit_before>package gitignore

import (
	"bytes"
	"io/ioutil"
	"os"
	"os/user"
	"strings"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-git/v5/plumbing/format/config"
	gioutil "github.com/go-git/go-git/v5/utils/ioutil"
)

const (
	commentPrefix = "#"
	coreSection   = "core"
	eol           = "\n"
	excludesfile  = "excludesfile"
	gitDir        = ".git"
	gitignoreFile = ".gitignore"
	gitconfigFile = ".gitconfig"
	systemFile    = "/etc/gitconfig"
)

// readIgnoreFile reads a specific git ignore file.
func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
	f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
	if err == nil {
		defer f.Close()

		if data, err := ioutil.ReadAll(f); err == nil {
			for _, s := range strings.Split(string(data), eol) {
				if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
					ps = append(ps, ParsePattern(s, path))
				}
			}
		}
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	return
}

// ReadPatterns reads gitignore patterns recursively traversing through the directory
// structure. The result is in the ascending order of priority (last higher).
func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
	ps, _ = readIgnoreFile(fs, path, gitignoreFile)

	var fis []os.FileInfo
	fis, err = fs.ReadDir(fs.Join(path...))
	if err != nil {
		return
	}

	for _, fi := range fis {
		if fi.IsDir() && fi.Name() != gitDir {
			var subps []Pattern
			subps, err = ReadPatterns(fs, append(path, fi.Name()))
			if err != nil {
				return
			}

			if len(subps) > 0 {
				ps = append(ps, subps...)
			}
		}
	}

	return
}

func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) {
	f, err := fs.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	defer gioutil.CheckClose(f, &err)

	b, err := ioutil.ReadAll(f)
	if err != nil {
		return
	}

	d := config.NewDecoder(bytes.NewBuffer(b))

	raw := config.New()
	if err = d.Decode(raw); err != nil {
		return
	}

	s := raw.Section(coreSection)
	efo := s.Options.Get(excludesfile)
	if efo == "" {
		return nil, nil
	}

	ps, err = readIgnoreFile(fs, nil, efo)
	if os.IsNotExist(err) {
		return nil, nil
	}

	return
}

// LoadGlobalPatterns loads gitignore patterns from from the gitignore file
// declared in a user's ~/.gitconfig file.  If the ~/.gitconfig file does not
// exist the function will return nil.  If the core.excludesfile property
// is not declared, the function will return nil.  If the file pointed to by
// the core.excludesfile property does not exist, the function will return nil.
//
// The function assumes fs is rooted at the root filesystem.
func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
	usr, err := user.Current()
	if err != nil {
		return
	}

	return loadPatterns(fs, fs.Join(usr.HomeDir, gitconfigFile))
}

// LoadSystemPatterns loads gitignore patterns from from the gitignore file
// declared in a system's /etc/gitconfig file.  If the ~/.gitconfig file does
// not exist the function will return nil.  If the core.excludesfile property
// is not declared, the function will return nil.  If the file pointed to by
// the core.excludesfile property does not exist, the function will return nil.
//
// The function assumes fs is rooted at the root filesystem.
func LoadSystemPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
	return loadPatterns(fs, systemFile)
}
<commit_msg>replace ReadAll by bufio.scanner<commit_after>package gitignore

import (
	"bufio"
	"bytes"
	"io/ioutil"
	"os"
	"os/user"
	"strings"

	"github.com/go-git/go-billy/v5"
	"github.com/go-git/go-git/v5/plumbing/format/config"
	gioutil "github.com/go-git/go-git/v5/utils/ioutil"
)

const (
	commentPrefix = "#"
	coreSection   = "core"
	excludesfile  = "excludesfile"
	gitDir        = ".git"
	gitignoreFile = ".gitignore"
	gitconfigFile = ".gitconfig"
	systemFile    = "/etc/gitconfig"
)

// readIgnoreFile reads a specific git ignore file.
func readIgnoreFile(fs billy.Filesystem, path []string, ignoreFile string) (ps []Pattern, err error) {
	f, err := fs.Open(fs.Join(append(path, ignoreFile)...))
	if err == nil {
		defer f.Close()

		scanner := bufio.NewScanner(f)
		for scanner.Scan() {
			s := scanner.Text()
			if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
				ps = append(ps, ParsePattern(s, path))
			}
		}
	} else if !os.IsNotExist(err) {
		return nil, err
	}

	return
}

// ReadPatterns reads gitignore patterns recursively traversing through the directory
// structure. The result is in the ascending order of priority (last higher).
func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
	ps, _ = readIgnoreFile(fs, path, gitignoreFile)

	var fis []os.FileInfo
	fis, err = fs.ReadDir(fs.Join(path...))
	if err != nil {
		return
	}

	for _, fi := range fis {
		if fi.IsDir() && fi.Name() != gitDir {
			var subps []Pattern
			subps, err = ReadPatterns(fs, append(path, fi.Name()))
			if err != nil {
				return
			}

			if len(subps) > 0 {
				ps = append(ps, subps...)
			}
		}
	}

	return
}

func loadPatterns(fs billy.Filesystem, path string) (ps []Pattern, err error) {
	f, err := fs.Open(path)
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}
		return nil, err
	}

	defer gioutil.CheckClose(f, &err)

	b, err := ioutil.ReadAll(f)
	if err != nil {
		return
	}

	d := config.NewDecoder(bytes.NewBuffer(b))

	raw := config.New()
	if err = d.Decode(raw); err != nil {
		return
	}

	s := raw.Section(coreSection)
	efo := s.Options.Get(excludesfile)
	if efo == "" {
		return nil, nil
	}

	ps, err = readIgnoreFile(fs, nil, efo)
	if os.IsNotExist(err) {
		return nil, nil
	}

	return
}

// LoadGlobalPatterns loads gitignore patterns from from the gitignore file
// declared in a user's ~/.gitconfig file.  If the ~/.gitconfig file does not
// exist the function will return nil.  If the core.excludesfile property
// is not declared, the function will return nil.  If the file pointed to by
// the core.excludesfile property does not exist, the function will return nil.
//
// The function assumes fs is rooted at the root filesystem.
func LoadGlobalPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
	usr, err := user.Current()
	if err != nil {
		return
	}

	return loadPatterns(fs, fs.Join(usr.HomeDir, gitconfigFile))
}

// LoadSystemPatterns loads gitignore patterns from from the gitignore file
// declared in a system's /etc/gitconfig file.  If the ~/.gitconfig file does
// not exist the function will return nil.  If the core.excludesfile property
// is not declared, the function will return nil.  If the file pointed to by
// the core.excludesfile property does not exist, the function will return nil.
//
// The function assumes fs is rooted at the root filesystem.
func LoadSystemPatterns(fs billy.Filesystem) (ps []Pattern, err error) {
	return loadPatterns(fs, systemFile)
}
<|endoftext|>