<commit_before>package levant

import (
	"strings"
	"sync"

	nomad "github.com/hashicorp/nomad/api"
	"github.com/jrasell/levant/logging"
)

// checkFailedDeployment helps log information about deployment failures.
func (c *nomadClient) checkFailedDeployment(depID *string) {

	var allocIDS []string

	allocs, _, err := c.nomad.Deployments().Allocations(*depID, nil)
	if err != nil {
		logging.Error("levant/failure_inspector: unable to query deployment allocations for deployment %s",
			depID)
	}

	// Iterate the allocations on the deployment and create a list of each allocID
	// to inspect.
	for _, alloc := range allocs {
		allocIDS = append(allocIDS, alloc.ID)
	}

	// Setup a waitgroup so the function doesn't return until all allocations have
	// been inspected.
	var wg sync.WaitGroup
	wg.Add(len(allocIDS))

	// Inspect each allocation.
	for _, id := range allocIDS {
		logging.Debug("levant/failure_inspector: launching allocation inspector for alloc %v", id)
		go c.allocInspector(&id, &wg)
	}

	wg.Wait()
}

// allocInspector inspects an allocations events to log any useful information
// which may help debug deployment failures.
func (c *nomadClient) allocInspector(allocID *string, wg *sync.WaitGroup) {

	// Inform the wait group we have finished our task upon completion.
	defer wg.Done()

	resp, _, err := c.nomad.Allocations().Info(*allocID, nil)
	if err != nil {
		logging.Error("levant/failure_inspector: unable to query alloc %v: %v", allocID, err)
	}

	// Iterate each each Task and Event to log any relevant information which may
	// help debug deployment failures.
	for _, task := range resp.TaskStates {
		for _, event := range task.Events {
			switch event.Type {
			case nomad.TaskDriverFailure:
				logging.Error("levant/failure_inspector: allocation %v incurred %v due to %s",
					*allocID, nomad.TaskDriverFailure, strings.TrimSpace(event.DriverError))
			case nomad.TaskGenericMessage:
				logging.Error("levant/failure_inspector: allocation %v incurred %v due to %s",
					*allocID, nomad.TaskGenericMessage, strings.TrimSpace(event.Message))
			case nomad.TaskArtifactDownloadFailed:
				logging.Error("levant/failure_inspector: allocation %v incurred %v due to %s",
					*allocID, nomad.TaskArtifactDownloadFailed, strings.TrimSpace(event.DownloadError))
			}
		}
	}
}
<commit_msg>Update failure_inspector to cover more cases.<commit_after>package levant

import (
	"fmt"
	"strings"
	"sync"

	"github.com/jrasell/levant/logging"
	nomad "github.com/jrasell/nomad/api"
)

// checkFailedDeployment helps log information about deployment failures.
func (c *nomadClient) checkFailedDeployment(depID *string) {

	var allocIDS []string

	allocs, _, err := c.nomad.Deployments().Allocations(*depID, nil)
	if err != nil {
		logging.Error("levant/failure_inspector: unable to query deployment allocations for deployment %s",
			depID)
	}

	// Iterate the allocations on the deployment and create a list of each allocID
	// to inspect.
	for _, alloc := range allocs {
		allocIDS = append(allocIDS, alloc.ID)
	}

	// Setup a waitgroup so the function doesn't return until all allocations have
	// been inspected.
	var wg sync.WaitGroup
	wg.Add(len(allocIDS))

	// Inspect each allocation.
	for _, id := range allocIDS {
		logging.Debug("levant/failure_inspector: launching allocation inspector for alloc %v", id)
		go c.allocInspector(&id, &wg)
	}

	wg.Wait()
}

// allocInspector inspects an allocations events to log any useful information
// which may help debug deployment failures.
func (c *nomadClient) allocInspector(allocID *string, wg *sync.WaitGroup) {

	// Inform the wait group we have finished our task upon completion.
	defer wg.Done()

	resp, _, err := c.nomad.Allocations().Info(*allocID, nil)
	if err != nil {
		logging.Error("levant/failure_inspector: unable to query alloc %v: %v", allocID, err)
	}

	// Iterate each each Task and Event to log any relevant information which may
	// help debug deployment failures.
	for _, task := range resp.TaskStates {
		for _, event := range task.Events {

			var desc string

			switch event.Type {
			case nomad.TaskFailedValidation:
				if event.ValidationError != "" {
					desc = event.ValidationError
				} else {
					desc = "validation of task failed"
				}
			case nomad.TaskSetupFailure:
				if event.SetupError != "" {
					desc = event.SetupError
				} else {
					desc = "task setup failed"
				}
			case nomad.TaskDriverFailure:
				if event.DriverError != "" {
					desc = event.DriverError
				} else {
					desc = "failed to start task"
				}
			case nomad.TaskArtifactDownloadFailed:
				if event.DownloadError != "" {
					desc = event.DownloadError
				} else {
					desc = "the task failed to download artifacts"
				}
			case nomad.TaskKilling:
				if event.KillReason != "" {
					desc = fmt.Sprintf("the task was killed: %v", event.KillReason)
				} else if event.KillTimeout != 0 {
					desc = fmt.Sprintf("sent interrupt, waiting %v before force killing", event.KillTimeout)
				} else {
					desc = "the task was sent interrupt"
				}
			case nomad.TaskKilled:
				if event.KillError != "" {
					desc = event.KillError
				} else {
					desc = "the task was successfully killed"
				}
			case nomad.TaskTerminated:
				var parts []string
				parts = append(parts, fmt.Sprintf("exit Code %d", event.ExitCode))

				if event.Signal != 0 {
					parts = append(parts, fmt.Sprintf("signal %d", event.Signal))
				}

				if event.Message != "" {
					parts = append(parts, fmt.Sprintf("exit message %q", event.Message))
				}
				desc = strings.Join(parts, ", ")
			case nomad.TaskNotRestarting:
				if event.RestartReason != "" {
					desc = event.RestartReason
				} else {
					desc = "the task exceeded restart policy"
				}
			case nomad.TaskSiblingFailed:
				if event.FailedSibling != "" {
					desc = fmt.Sprintf("task's sibling %q failed", event.FailedSibling)
				} else {
					desc = "task's sibling failed"
				}
			case nomad.TaskLeaderDead:
				desc = "leader task in group is dead"
			}

			// If we have matched and have an updated desc then log the appropriate
			// information.
			if desc != "" {
				logging.Error("levant/failure_inspector: alloc %s incurred event %s because %s",
					*allocID, strings.ToLower(event.Type), strings.TrimSpace(desc))
			}
		}
	}
}
<|endoftext|>


<commit_before>package plugins

import (
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"strings"

	"github.com/belak/irc"
	"github.com/belak/seabird/bot"
	"github.com/belak/seabird/mux"
)

type PastebinPlugin struct {
	Key string
}

func init() {
	bot.RegisterPlugin("net_tools", NewNetToolsPlugin)
}

func NewNetToolsPlugin(b *bot.Bot, m *mux.CommandMux) error {
	p := &PastebinPlugin{}

	b.Config("pastebin", p)

	m.Event("dig", p.Dig)
	m.Event("ping", p.Ping)
	m.Event("traceroute", p.Traceroute)
	m.Event("whois", p.Whois)
	m.Event("dnscheck", p.DnsCheck)

	return nil
}

func (p *PastebinPlugin) Dig(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		addrs, err := net.LookupHost(e.Trailing())
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		if len(addrs) == 0 {
			c.MentionReply(e, "No results found")
			return
		}

		c.MentionReply(e, addrs[0])

		if len(addrs) > 1 {
			for _, addr := range addrs[1:] {
				c.Writef("NOTICE %s :%s", e.Identity.Nick, addr)
			}
		}
	}()
}

func (p *PastebinPlugin) Ping(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Host required")
			return
		}

		out, err := exec.Command("ping", "-c1", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		arr := strings.Split(string(out), "\n")
		if len(arr) < 2 {
			c.MentionReply(e, "Error retrieving ping results")
			return
		}

		c.MentionReply(e, arr[1])
	}()
}

func (p *PastebinPlugin) Traceroute(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Host required")
			return
		}

		out, err := exec.Command("traceroute", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		hc := http.Client{}

		form := url.Values{}
		form.Add("api_dev_key", p.Key)
		form.Add("api_option", "paste")
		form.Add("api_paste_code", string(out))
		req, err := http.NewRequest("POST", "http://pastebin.com/api/api_post.php", strings.NewReader(form.Encode()))
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

		resp, err := hc.Do(req)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		c.MentionReply(e, "%s", body)
	}()
}

func (p *PastebinPlugin) Whois(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		out, err := exec.Command("whois", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		hc := http.Client{}

		form := url.Values{}
		form.Add("api_dev_key", p.Key)
		form.Add("api_option", "paste")
		form.Add("api_paste_code", string(out))
		req, err := http.NewRequest("POST", "http://pastebin.com/api/api_post.php", strings.NewReader(form.Encode()))
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

		resp, err := hc.Do(req)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		c.MentionReply(e, "%s", body)
	}()
}

func (p *PastebinPlugin) DnsCheck(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		c.MentionReply(e, "https://www.whatsmydns.net/#A/" + e.Trailing())
	}()
}
<commit_msg>I hate gofmt<commit_after>package plugins

import (
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os/exec"
	"strings"

	"github.com/belak/irc"
	"github.com/belak/seabird/bot"
	"github.com/belak/seabird/mux"
)

type PastebinPlugin struct {
	Key string
}

func init() {
	bot.RegisterPlugin("net_tools", NewNetToolsPlugin)
}

func NewNetToolsPlugin(b *bot.Bot, m *mux.CommandMux) error {
	p := &PastebinPlugin{}

	b.Config("pastebin", p)

	m.Event("dig", p.Dig)
	m.Event("ping", p.Ping)
	m.Event("traceroute", p.Traceroute)
	m.Event("whois", p.Whois)
	m.Event("dnscheck", p.DnsCheck)

	return nil
}

func (p *PastebinPlugin) Dig(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		addrs, err := net.LookupHost(e.Trailing())
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		if len(addrs) == 0 {
			c.MentionReply(e, "No results found")
			return
		}

		c.MentionReply(e, addrs[0])

		if len(addrs) > 1 {
			for _, addr := range addrs[1:] {
				c.Writef("NOTICE %s :%s", e.Identity.Nick, addr)
			}
		}
	}()
}

func (p *PastebinPlugin) Ping(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Host required")
			return
		}

		out, err := exec.Command("ping", "-c1", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		arr := strings.Split(string(out), "\n")
		if len(arr) < 2 {
			c.MentionReply(e, "Error retrieving ping results")
			return
		}

		c.MentionReply(e, arr[1])
	}()
}

func (p *PastebinPlugin) Traceroute(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Host required")
			return
		}

		out, err := exec.Command("traceroute", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		hc := http.Client{}

		form := url.Values{}
		form.Add("api_dev_key", p.Key)
		form.Add("api_option", "paste")
		form.Add("api_paste_code", string(out))
		req, err := http.NewRequest("POST", "http://pastebin.com/api/api_post.php", strings.NewReader(form.Encode()))
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

		resp, err := hc.Do(req)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		c.MentionReply(e, "%s", body)
	}()
}

func (p *PastebinPlugin) Whois(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		out, err := exec.Command("whois", e.Trailing()).Output()
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		hc := http.Client{}

		form := url.Values{}
		form.Add("api_dev_key", p.Key)
		form.Add("api_option", "paste")
		form.Add("api_paste_code", string(out))
		req, err := http.NewRequest("POST", "http://pastebin.com/api/api_post.php", strings.NewReader(form.Encode()))
		req.Header.Add("Content-Type", "application/x-www-form-urlencoded")

		resp, err := hc.Do(req)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}
		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			c.MentionReply(e, "%s", err)
			return
		}

		c.MentionReply(e, "%s", body)
	}()
}

func (p *PastebinPlugin) DnsCheck(c *irc.Client, e *irc.Event) {
	go func() {
		if e.Trailing() == "" {
			c.MentionReply(e, "Domain required")
			return
		}

		c.MentionReply(e, "https://www.whatsmydns.net/#A/"+e.Trailing())
	}()
}
<|endoftext|>


<commit_before>package chunk

import (
	"fmt"
	"math"

	. "github.com/claudetech/loggo/default"

	"time"

	"github.com/dweidenfeld/plexdrive/drive"
)

// Manager manages chunks on disk
type Manager struct {
	ChunkPath      string
	ChunkSize      int64
	LoadAhead      int
	Timeout        time.Duration
	TimeoutRetries int
	downloader     *Downloader
	queue          chan *Request
	queue2         chan *Request
	storage        *Storage
}

// Request represents a chunk request
type Request struct {
	id          string
	object      *drive.APIObject
	preload     bool
	offsetStart int64
	offsetEnd   int64
	response    chan Response
}

// Response represents a chunk responses
type Response struct {
	err   error
	bytes []byte
}

// NewManager creates a new chunk manager
func NewManager(
	chunkPath string,
	chunkSize int64,
	loadAhead,
	threads int,
	client *drive.Client,
	maxChunks int,
	timeout time.Duration,
	timeoutRetries int) (*Manager, error) {

	if "" == chunkPath {
		return nil, fmt.Errorf("Path to chunk file must not be empty")
	}
	if chunkSize < 4096 {
		return nil, fmt.Errorf("Chunk size must not be < 4096")
	}
	if chunkSize%1024 != 0 {
		return nil, fmt.Errorf("Chunk size must be divideable by 1024")
	}
	if maxChunks < 2 || maxChunks < loadAhead {
		return nil, fmt.Errorf("max-chunk must be greater than 2 and bigger than the load ahead value")
	}

	downloader, err := NewDownloader(threads, client)
	if nil != err {
		return nil, err
	}

	manager := Manager{
		ChunkPath:      chunkPath,
		ChunkSize:      chunkSize,
		LoadAhead:      loadAhead,
		Timeout:        timeout,
		TimeoutRetries: timeoutRetries,
		downloader:     downloader,
		queue:          make(chan *Request, 20),
		queue2:         make(chan *Request, 80),
		storage:        NewStorage(chunkPath, chunkSize, maxChunks),
	}

	if err := manager.storage.Clear(); nil != err {
		return nil, err
	}

	for i := 0; i < threads; i++ {
		go manager.thread(i)
	}

	return &manager, nil
}

// GetChunk loads one chunk and starts the preload for the next chunks
func (m *Manager) GetChunk(object *drive.APIObject, offset, size int64) ([]byte, error) {
	chunkOffset := offset % m.ChunkSize
	offsetStart := offset - chunkOffset
	offsetEnd := offsetStart + m.ChunkSize
	id := fmt.Sprintf("%v:%v", object.ObjectID, offsetStart)

	response := make(chan Response)
	m.queue <- &Request{
		id:          id,
		object:      object,
		offsetStart: offsetStart,
		offsetEnd:   offsetEnd,
		preload:     false,
		response:    response,
	}

	for i := m.ChunkSize; i < (m.ChunkSize * int64(m.LoadAhead+1)); i += m.ChunkSize {
		aheadOffsetStart := offsetStart + i
		aheadOffsetEnd := aheadOffsetStart + m.ChunkSize
		if uint64(aheadOffsetStart) < object.Size && uint64(aheadOffsetEnd) < object.Size {
			id := fmt.Sprintf("%v:%v", object.ObjectID, aheadOffsetStart)
			m.queue2 <- &Request{
				id:          id,
				object:      object,
				offsetStart: aheadOffsetStart,
				offsetEnd:   aheadOffsetEnd,
				preload:     true,
			}
		}
	}

	res := <-response
	if nil != res.err {
		return nil, res.err
	}

	sOffset := int64(math.Min(float64(len(res.bytes)), float64(chunkOffset)))
	eOffset := int64(math.Min(float64(len(res.bytes)), float64(chunkOffset+size)))
	return res.bytes[sOffset:eOffset], nil
}

func (m *Manager) thread(threadID int) {
	for {
		select {
		case req := <-m.queue:
			m.checkChunk(req, threadID)
		case req := <-m.queue2:
			m.checkChunk(req, threadID)
		}
	}
}

func (m *Manager) checkChunk(req *Request, threadID int) {
	if chunk, exists := m.storage.LoadOrCreate(req.id); exists {
		if nil != req.response {
			req.response <- Response{bytes: chunk}
			close(req.response)
		}
		return
	}

	Log.Debugf("Requesting object %v (%v) bytes %v - %v from API (preload: %v | thread: %v)",
		req.object.ObjectID, req.object.Name, req.offsetStart, req.offsetEnd, req.preload, threadID)

	bytes, err := m.downloader.Download(req)
	if nil != err {
		Log.Warningf("%v", err)
		m.storage.Error(req.id)

		if nil != req.response {
			req.response <- Response{err: err}
			close(req.response)
		}
	}

	if nil != req.response {
		req.response <- Response{bytes: bytes}
		close(req.response)
	}

	if err := m.storage.Store(req.id, bytes); nil != err {
		Log.Warningf("%v", err)
	}
}
<commit_msg>error bugfix<commit_after>package chunk

import (
	"fmt"
	"math"

	. "github.com/claudetech/loggo/default"

	"time"

	"github.com/dweidenfeld/plexdrive/drive"
)

// Manager manages chunks on disk
type Manager struct {
	ChunkPath      string
	ChunkSize      int64
	LoadAhead      int
	Timeout        time.Duration
	TimeoutRetries int
	downloader     *Downloader
	queue          chan *Request
	queue2         chan *Request
	storage        *Storage
}

// Request represents a chunk request
type Request struct {
	id          string
	object      *drive.APIObject
	preload     bool
	offsetStart int64
	offsetEnd   int64
	response    chan Response
}

// Response represents a chunk responses
type Response struct {
	err   error
	bytes []byte
}

// NewManager creates a new chunk manager
func NewManager(
	chunkPath string,
	chunkSize int64,
	loadAhead,
	threads int,
	client *drive.Client,
	maxChunks int,
	timeout time.Duration,
	timeoutRetries int) (*Manager, error) {

	if "" == chunkPath {
		return nil, fmt.Errorf("Path to chunk file must not be empty")
	}
	if chunkSize < 4096 {
		return nil, fmt.Errorf("Chunk size must not be < 4096")
	}
	if chunkSize%1024 != 0 {
		return nil, fmt.Errorf("Chunk size must be divideable by 1024")
	}
	if maxChunks < 2 || maxChunks < loadAhead {
		return nil, fmt.Errorf("max-chunk must be greater than 2 and bigger than the load ahead value")
	}

	downloader, err := NewDownloader(threads, client)
	if nil != err {
		return nil, err
	}

	manager := Manager{
		ChunkPath:      chunkPath,
		ChunkSize:      chunkSize,
		LoadAhead:      loadAhead,
		Timeout:        timeout,
		TimeoutRetries: timeoutRetries,
		downloader:     downloader,
		queue:          make(chan *Request, 20),
		queue2:         make(chan *Request, 80),
		storage:        NewStorage(chunkPath, chunkSize, maxChunks),
	}

	if err := manager.storage.Clear(); nil != err {
		return nil, err
	}

	for i := 0; i < threads; i++ {
		go manager.thread(i)
	}

	return &manager, nil
}

// GetChunk loads one chunk and starts the preload for the next chunks
func (m *Manager) GetChunk(object *drive.APIObject, offset, size int64) ([]byte, error) {
	chunkOffset := offset % m.ChunkSize
	offsetStart := offset - chunkOffset
	offsetEnd := offsetStart + m.ChunkSize
	id := fmt.Sprintf("%v:%v", object.ObjectID, offsetStart)

	response := make(chan Response)
	m.queue <- &Request{
		id:          id,
		object:      object,
		offsetStart: offsetStart,
		offsetEnd:   offsetEnd,
		preload:     false,
		response:    response,
	}

	for i := m.ChunkSize; i < (m.ChunkSize * int64(m.LoadAhead+1)); i += m.ChunkSize {
		aheadOffsetStart := offsetStart + i
		aheadOffsetEnd := aheadOffsetStart + m.ChunkSize
		if uint64(aheadOffsetStart) < object.Size && uint64(aheadOffsetEnd) < object.Size {
			id := fmt.Sprintf("%v:%v", object.ObjectID, aheadOffsetStart)
			m.queue2 <- &Request{
				id:          id,
				object:      object,
				offsetStart: aheadOffsetStart,
				offsetEnd:   aheadOffsetEnd,
				preload:     true,
			}
		}
	}

	res := <-response
	if nil != res.err {
		return nil, res.err
	}

	sOffset := int64(math.Min(float64(len(res.bytes)), float64(chunkOffset)))
	eOffset := int64(math.Min(float64(len(res.bytes)), float64(chunkOffset+size)))
	return res.bytes[sOffset:eOffset], nil
}

func (m *Manager) thread(threadID int) {
	for {
		select {
		case req := <-m.queue:
			m.checkChunk(req, threadID)
		case req := <-m.queue2:
			m.checkChunk(req, threadID)
		}
	}
}

func (m *Manager) checkChunk(req *Request, threadID int) {
	if chunk, exists := m.storage.LoadOrCreate(req.id); exists {
		if nil != req.response {
			req.response <- Response{bytes: chunk}
			close(req.response)
		}
		return
	}

	Log.Debugf("Requesting object %v (%v) bytes %v - %v from API (preload: %v | thread: %v)",
		req.object.ObjectID, req.object.Name, req.offsetStart, req.offsetEnd, req.preload, threadID)

	bytes, err := m.downloader.Download(req)
	if nil != err {
		Log.Warningf("%v", err)
		m.storage.Error(req.id)

		if nil != req.response {
			req.response <- Response{err: err}
			close(req.response)
		}
		return
	}

	if nil != req.response {
		req.response <- Response{bytes: bytes}
		close(req.response)
	}

	if err := m.storage.Store(req.id, bytes); nil != err {
		Log.Warningf("%v", err)
	}
}
<|endoftext|>


<commit_before>package main

import "fmt"
import "github.com/grafana/grafana/pkg/log"

type aggSetting struct {
	span      uint32 // in seconds, controls how many input points go into an aggregated point.
	chunkSpan uint32 // duration of chunk of aggregated metric for storage, controls how many aggregated points go into 1 chunk
	numChunks uint32 // number of chunks to keep in memory. remember, for a query from now until 3 months ago, we will end up querying the memory server as well.
	ttl       uint32 // how many seconds to keep the chunk in cassandra
}

type aggSettingsSpanAsc []aggSetting

func (a aggSettingsSpanAsc) Len() int           { return len(a) }
func (a aggSettingsSpanAsc) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a aggSettingsSpanAsc) Less(i, j int) bool { return a[i].span < a[j].span }

// see description for Aggregator and unit tests
func aggBoundary(ts uint32, span uint32) uint32 {
	return ts + span - ((ts-1)%span + 1)
}

// receives data and builds aggregations
// implementation detail: all points with timestamps t1, t2, t3, t4, t5 get aggregated into a point with ts t5,
// IOW an aggregation point reflects the data in the timeframe preceeding it.
type Aggregator struct {
	key             string // of the metric this aggregator corresponds to
	span            uint32
	currentBoundary uint32 // working on this chunk
	agg             *Aggregation
	minMetric       *AggMetric
	maxMetric       *AggMetric
	sumMetric       *AggMetric
	cntMetric       *AggMetric
}

func NewAggregator(store Store, key string, aggSpan, aggChunkSpan, aggNumChunks uint32, ttl uint32) *Aggregator {
	return &Aggregator{
		key:       key,
		span:      aggSpan,
		agg:       NewAggregation(),
		minMetric: NewAggMetric(store, fmt.Sprintf("%s_min_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		maxMetric: NewAggMetric(store, fmt.Sprintf("%s_max_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		sumMetric: NewAggMetric(store, fmt.Sprintf("%s_sum_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		cntMetric: NewAggMetric(store, fmt.Sprintf("%s_cnt_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
	}
}
func (agg *Aggregator) flush() string {
	agg.minMetric.Add(agg.currentBoundary, agg.agg.min)
	agg.maxMetric.Add(agg.currentBoundary, agg.agg.max)
	agg.sumMetric.Add(agg.currentBoundary, agg.agg.sum)
	agg.cntMetric.Add(agg.currentBoundary, agg.agg.cnt)
	msg := fmt.Sprintf("flushed cnt %v sum %f min %f max %f, reset the block", agg.agg.cnt, agg.agg.sum, agg.agg.min, agg.agg.max)
	agg.agg.Reset()
	return msg
}

func (agg *Aggregator) Add(ts uint32, val float64) {
	boundary := aggBoundary(ts, agg.span)

	if boundary == agg.currentBoundary {
		agg.agg.Add(val)
		if ts == boundary {
			log.Debug("aggregator %d Add(): added to aggregation block, %s because this was the last point for the block", agg.span, agg.flush())
		} else {
			log.Debug("aggregator %d Add(): added to aggregation block", agg.span)
		}
	} else if boundary > agg.currentBoundary {
		var msg string
		// store current totals as a new point in their series
		// if the cnt is still 0, the numbers are invalid, not to be flushed and we can simply reuse the aggregation
		if agg.agg.cnt != 0 {
			msg = agg.flush() + "and added new point"
		} else {
			msg = "added point to still-unused aggregation block"
		}
		agg.currentBoundary = boundary
		agg.agg.Add(val)
		log.Debug("aggregator %d Add(): %s", agg.span, msg)
	} else {
		panic("aggregator: boundary < agg.currentBoundary. ts > lastSeen should already have been asserted")
	}
}
<commit_msg>disable these log.Debugs<commit_after>package main

import "fmt"

type aggSetting struct {
	span      uint32 // in seconds, controls how many input points go into an aggregated point.
	chunkSpan uint32 // duration of chunk of aggregated metric for storage, controls how many aggregated points go into 1 chunk
	numChunks uint32 // number of chunks to keep in memory. remember, for a query from now until 3 months ago, we will end up querying the memory server as well.
	ttl       uint32 // how many seconds to keep the chunk in cassandra
}

type aggSettingsSpanAsc []aggSetting

func (a aggSettingsSpanAsc) Len() int           { return len(a) }
func (a aggSettingsSpanAsc) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a aggSettingsSpanAsc) Less(i, j int) bool { return a[i].span < a[j].span }

// see description for Aggregator and unit tests
func aggBoundary(ts uint32, span uint32) uint32 {
	return ts + span - ((ts-1)%span + 1)
}

// receives data and builds aggregations
// implementation detail: all points with timestamps t1, t2, t3, t4, t5 get aggregated into a point with ts t5,
// IOW an aggregation point reflects the data in the timeframe preceeding it.
type Aggregator struct {
	key             string // of the metric this aggregator corresponds to
	span            uint32
	currentBoundary uint32 // working on this chunk
	agg             *Aggregation
	minMetric       *AggMetric
	maxMetric       *AggMetric
	sumMetric       *AggMetric
	cntMetric       *AggMetric
}

func NewAggregator(store Store, key string, aggSpan, aggChunkSpan, aggNumChunks uint32, ttl uint32) *Aggregator {
	return &Aggregator{
		key:       key,
		span:      aggSpan,
		agg:       NewAggregation(),
		minMetric: NewAggMetric(store, fmt.Sprintf("%s_min_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		maxMetric: NewAggMetric(store, fmt.Sprintf("%s_max_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		sumMetric: NewAggMetric(store, fmt.Sprintf("%s_sum_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
		cntMetric: NewAggMetric(store, fmt.Sprintf("%s_cnt_%d", key, aggSpan), aggChunkSpan, aggNumChunks, ttl),
	}
}
func (agg *Aggregator) flush() {
	agg.minMetric.Add(agg.currentBoundary, agg.agg.min)
	agg.maxMetric.Add(agg.currentBoundary, agg.agg.max)
	agg.sumMetric.Add(agg.currentBoundary, agg.agg.sum)
	agg.cntMetric.Add(agg.currentBoundary, agg.agg.cnt)
	//msg := fmt.Sprintf("flushed cnt %v sum %f min %f max %f, reset the block", agg.agg.cnt, agg.agg.sum, agg.agg.min, agg.agg.max)
	agg.agg.Reset()
}

func (agg *Aggregator) Add(ts uint32, val float64) {
	boundary := aggBoundary(ts, agg.span)

	if boundary == agg.currentBoundary {
		agg.agg.Add(val)
		if ts == boundary {
			agg.flush()
			//log.Debug("aggregator %d Add(): added to aggregation block, %s because this was the last point for the block", agg.span, agg.flush())
			//} else {
			//		log.Debug("aggregator %d Add(): added to aggregation block", agg.span)
			//	}
		}
	} else if boundary > agg.currentBoundary {
		// store current totals as a new point in their series
		// if the cnt is still 0, the numbers are invalid, not to be flushed and we can simply reuse the aggregation
		if agg.agg.cnt != 0 {
			agg.flush()
			//		msg = agg.flush() + "and added new point"
			//	} else {
			//		msg = "added point to still-unused aggregation block"
		}
		agg.currentBoundary = boundary
		agg.agg.Add(val)
		//	log.Debug("aggregator %d Add(): %s", agg.span, msg)
	} else {
		panic("aggregator: boundary < agg.currentBoundary. ts > lastSeen should already have been asserted")
	}
}
<|endoftext|>


<commit_before>package pop

import (
	"fmt"
	"os/exec"
	"strconv"

	_ "github.com/lib/pq"
	"github.com/markbates/going/clam"
	. "github.com/markbates/pop/columns"
)

type PostgreSQL struct {
	translateCache    map[string]string
	ConnectionDetails *ConnectionDetails
}

func (p *PostgreSQL) Details() *ConnectionDetails {
	return p.ConnectionDetails
}

func (p *PostgreSQL) Create(store Store, model *Model, cols Columns) error {
	cols.Remove("id")
	id := struct {
		ID int `db:"id"`
	}{}
	w := cols.Writeable()
	query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) returning id", model.TableName(), w.String(), w.SymbolizedString())
	Log(query)
	stmt, err := store.PrepareNamed(query)
	if err != nil {
		return err
	}
	err = stmt.Get(&id, model.Value)
	if err == nil {
		model.setID(id.ID)
	}
	return err
}

func (p *PostgreSQL) Update(store Store, model *Model, cols Columns) error {
	return genericUpdate(store, model, cols)
}

func (p *PostgreSQL) Destroy(store Store, model *Model) error {
	return genericDestroy(store, model)
}

func (p *PostgreSQL) SelectOne(store Store, model *Model, query Query) error {
	return genericSelectOne(store, model, query)
}

func (p *PostgreSQL) SelectMany(store Store, models *Model, query Query) error {
	return genericSelectMany(store, models, query)
}

func (p *PostgreSQL) CreateDB() error {
	// createdb -h db -p 5432 -U postgres enterprise_development
	deets := p.ConnectionDetails
	cmd := exec.Command("createdb", "-e", "-h", deets.Host, "-p", deets.Port, "-U", deets.User, deets.Database)
	return clam.RunAndListen(cmd, func(s string) {
		fmt.Println(s)
	})
}

func (p *PostgreSQL) DropDB() error {
	deets := p.ConnectionDetails
	cmd := exec.Command("dropdb", "-e", "-h", deets.Host, "-p", deets.Port, "-U", deets.User, deets.Database)
	return clam.RunAndListen(cmd, func(s string) {
		fmt.Println(s)
	})
}

func (m *PostgreSQL) URL() string {
	c := m.ConnectionDetails
	if c.URL != "" {
		return c.URL
	}

	s := "postgres://%s:%s@%s:%s/%s?sslmode=disable"
	return fmt.Sprintf(s, c.User, c.Password, c.Host, c.Port, c.Database)
}

func (p *PostgreSQL) TranslateSQL(sql string) string {
	if csql, ok := p.translateCache[sql]; ok {
		return csql
	}
	curr := 1
	out := make([]byte, 0, len(sql))
	for i := 0; i < len(sql); i++ {
		if sql[i] == '?' {
			str := "$" + strconv.Itoa(curr)
			for _, char := range str {
				out = append(out, byte(char))
			}
			curr += 1
		} else {
			out = append(out, sql[i])
		}
	}
	csql := string(out)
	p.translateCache[sql] = csql
	return csql
}

func NewPostgreSQL(deets *ConnectionDetails) Dialect {
	deets.Parse("5432")
	cd := &PostgreSQL{
		ConnectionDetails: deets,
		translateCache:    map[string]string{},
	}
	return cd
}
<commit_msg>Synchronize access to Postgres translationCache<commit_after>package pop

import (
	"fmt"
	"os/exec"
	"strconv"
	"sync"

	_ "github.com/lib/pq"
	"github.com/markbates/going/clam"
	. "github.com/markbates/pop/columns"
)

type PostgreSQL struct {
	translateCache    map[string]string
	mu                sync.Mutex
	ConnectionDetails *ConnectionDetails
}

func (p *PostgreSQL) Details() *ConnectionDetails {
	return p.ConnectionDetails
}

func (p *PostgreSQL) Create(store Store, model *Model, cols Columns) error {
	cols.Remove("id")
	id := struct {
		ID int `db:"id"`
	}{}
	w := cols.Writeable()
	query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s) returning id", model.TableName(), w.String(), w.SymbolizedString())
	Log(query)
	stmt, err := store.PrepareNamed(query)
	if err != nil {
		return err
	}
	err = stmt.Get(&id, model.Value)
	if err == nil {
		model.setID(id.ID)
	}
	return err
}

func (p *PostgreSQL) Update(store Store, model *Model, cols Columns) error {
	return genericUpdate(store, model, cols)
}

func (p *PostgreSQL) Destroy(store Store, model *Model) error {
	return genericDestroy(store, model)
}

func (p *PostgreSQL) SelectOne(store Store, model *Model, query Query) error {
	return genericSelectOne(store, model, query)
}

func (p *PostgreSQL) SelectMany(store Store, models *Model, query Query) error {
	return genericSelectMany(store, models, query)
}

func (p *PostgreSQL) CreateDB() error {
	// createdb -h db -p 5432 -U postgres enterprise_development
	deets := p.ConnectionDetails
	cmd := exec.Command("createdb", "-e", "-h", deets.Host, "-p", deets.Port, "-U", deets.User, deets.Database)
	return clam.RunAndListen(cmd, func(s string) {
		fmt.Println(s)
	})
}

func (p *PostgreSQL) DropDB() error {
	deets := p.ConnectionDetails
	cmd := exec.Command("dropdb", "-e", "-h", deets.Host, "-p", deets.Port, "-U", deets.User, deets.Database)
	return clam.RunAndListen(cmd, func(s string) {
		fmt.Println(s)
	})
}

func (m *PostgreSQL) URL() string {
	c := m.ConnectionDetails
	if c.URL != "" {
		return c.URL
	}

	s := "postgres://%s:%s@%s:%s/%s?sslmode=disable"
	return fmt.Sprintf(s, c.User, c.Password, c.Host, c.Port, c.Database)
}

func (p *PostgreSQL) TranslateSQL(sql string) string {
	defer p.mu.Unlock()
	p.mu.Lock()

	if csql, ok := p.translateCache[sql]; ok {
		return csql
	}
	curr := 1
	out := make([]byte, 0, len(sql))
	for i := 0; i < len(sql); i++ {
		if sql[i] == '?' {
			str := "$" + strconv.Itoa(curr)
			for _, char := range str {
				out = append(out, byte(char))
			}
			curr += 1
		} else {
			out = append(out, sql[i])
		}
	}
	csql := string(out)
	p.translateCache[sql] = csql
	return csql
}

func NewPostgreSQL(deets *ConnectionDetails) Dialect {
	deets.Parse("5432")
	cd := &PostgreSQL{
		ConnectionDetails: deets,
		translateCache:    map[string]string{},
		mu:                sync.Mutex{},
	}
	return cd
}
<|endoftext|>


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

package results

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

	"github.com/mozilla/mig/modules"
)

// PersistResults abstracts over operations that allow the MIG API to
// save results produced while executing an action.
type PersistResults interface {
	PersistResults(float64, []modules.Result) error
}

// Upload is an HTTP request handler that serves PUT requests
// containing results produced while executing an action.
type Upload struct {
	persist PersistResult
}

// result is used to decouple our request data type from the rest of MIG's types.
// The toModuleResult method provides a convenient conversion to modules.Result.
type result struct {
	FoundAnything bool        `json:"foundAnything"`
	Success       bool        `json:"success"`
	Elements      interface{} `json:"elements"`
	Statistics    interface{} `json:"statistics"`
	Errors        []string    `json:"errors"`
}

// uploadRequest contains the body of a request to the Upload handler.
type uploadRequest struct {
	Action  float64  `json:"action"`
	Results []result `json:"results"`
}

// uploadResponse contains the body of a response to a request to upload results.
type uploadResponse struct {
	Error *string `json:"error"`
}

// NewUpload constructs a new Upload.
func NewUpload(persist PersistResult) Upload {
	return Upload{
		persist: persist,
	}
}

// validate ensures that a results upload request contains all of the data
// required to satisfy the request.
func (req uploadRequest) validate() error {
	if req.Action == "" {
		return fmt.Errorf("missing action field")
	}

	if len(req.Results) == 0 {
		return fmt.Errorf("empty array of results")
	}

	return nil
}

// toModuleResult converts instances of our result abstraction into a Result
// used by the rest of MIG.
func (res result) toModuleResult() modules.Result {
	return modules.Result{
		FoundAnything: res.FoundAnything,
		Success:       res.Success,
		Elements:      res.Elements,
		Statistics:    res.Statistics,
		Errors:        res.Errors,
	}
}

func (handler Upload) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	reqData := uploadRequest{}
	decoder := json.NewDecoder(request.Body)
	resEncoder := json.NewEncoder(response)

	response.Header().Set("Content-Type", "application/json")

	defer request.Body.Close()

	decodeErr := decoder.Decode(&reqData)
	if decodeErr != nil {
		errMsg := fmt.Sprintf("Failed to decode request body: %s", decodeErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	validateErr := reqData.validate()
	if validateErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid data in request: %s", validateErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	results := make([]module.Result, len(reqData.Results))
	for i, res := range reqData.Results {
		results[i] = reqData.Results[i].toModuleResult()
	}

	persistErr := handler.persist.PersistResult(reqData.Action, results)
	if persistErr != nil {
		errMsg := fmt.Sprintf("Failed to save results: %s", persistErr.Error())
		response.WriteHeader(http.StatusInternalServerError)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	resEncoder.Encode(&uploadResponse{nil})
}
<commit_msg>Fixed up some typos<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Zack Mullaly zmullaly@mozilla.com [:zack]

package results

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

	"github.com/mozilla/mig/modules"
)

// PersistResults abstracts over operations that allow the MIG API to
// save results produced while executing an action.
type PersistResults interface {
	PersistResults(float64, []modules.Result) error
}

// Upload is an HTTP request handler that serves PUT requests
// containing results produced while executing an action.
type Upload struct {
	persist PersistResults
}

// result is used to decouple our request data type from the rest of MIG's types.
// The toModuleResult method provides a convenient conversion to modules.Result.
type result struct {
	FoundAnything bool        `json:"foundAnything"`
	Success       bool        `json:"success"`
	Elements      interface{} `json:"elements"`
	Statistics    interface{} `json:"statistics"`
	Errors        []string    `json:"errors"`
}

// uploadRequest contains the body of a request to the Upload handler.
type uploadRequest struct {
	Action  float64  `json:"action"`
	Results []result `json:"results"`
}

// uploadResponse contains the body of a response to a request to upload results.
type uploadResponse struct {
	Error *string `json:"error"`
}

// NewUpload constructs a new Upload.
func NewUpload(persist PersistResults) Upload {
	return Upload{
		persist: persist,
	}
}

// validate ensures that a results upload request contains all of the data
// required to satisfy the request.
func (req uploadRequest) validate() error {
	if req.Action == 0.0 {
		return fmt.Errorf("missing action field")
	}

	if len(req.Results) == 0 {
		return fmt.Errorf("empty array of results")
	}

	return nil
}

// toModuleResult converts instances of our result abstraction into a Result
// used by the rest of MIG.
func (res result) toModuleResult() modules.Result {
	return modules.Result{
		FoundAnything: res.FoundAnything,
		Success:       res.Success,
		Elements:      res.Elements,
		Statistics:    res.Statistics,
		Errors:        res.Errors,
	}
}

func (handler Upload) ServeHTTP(response http.ResponseWriter, request *http.Request) {
	reqData := uploadRequest{}
	decoder := json.NewDecoder(request.Body)
	resEncoder := json.NewEncoder(response)

	response.Header().Set("Content-Type", "application/json")

	defer request.Body.Close()

	decodeErr := decoder.Decode(&reqData)
	if decodeErr != nil {
		errMsg := fmt.Sprintf("Failed to decode request body: %s", decodeErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	validateErr := reqData.validate()
	if validateErr != nil {
		errMsg := fmt.Sprintf("Missing or invalid data in request: %s", validateErr.Error())
		response.WriteHeader(http.StatusBadRequest)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	results := make([]modules.Result, len(reqData.Results))
	for i, res := range reqData.Results {
		results[i] = res.toModuleResult()
	}

	persistErr := handler.persist.PersistResults(reqData.Action, results)
	if persistErr != nil {
		errMsg := fmt.Sprintf("Failed to save results: %s", persistErr.Error())
		response.WriteHeader(http.StatusInternalServerError)
		resEncoder.Encode(&uploadResponse{&errMsg})
		return
	}

	resEncoder.Encode(&uploadResponse{nil})
}
<|endoftext|>


<commit_before>// Package tables implements (URL, HTTP method) -> SermoCRM action lookup
// tables. A table is an RPC service's set of endpoints.
package tables

// Table is a mapping of URLs to Endpoints.
type Table map[string][]Endpoint

// Endpoint is an RPC endpoint.
type Endpoint struct {
	// Method is the case-sensitive method.
	Method string

	// Unauthenticated is true if the endpoint does not require authentication.
	Unauthenticated bool

	// Action is the Endpoint's action.
	Action string
}

// Mapping maps (URL, HTTP method) -> SermoCRM actions.
type Mapping struct {
	t Table
}

// MakeMapping creates a new Mapping.
func MakeMapping(fns ...func() Table) Mapping {
	t := make(Table)
	for _, fn := range fns {
		for url, eps := range fn() {
			t[url] = append(t[url], eps...)
		}
	}
	return Mapping{t: t}
}

// Mapping finds an endpoint based on a URL and HTTP method pair.
func (m *Mapping) Lookup(url, method string) (Endpoint, error) {
	const errNotFound = err("url not found")

	eps, ok := m.t[url]
	if !ok {
		return Endpoint{}, errNotFound
	}
	for _, e := range eps {
		if e.Method == method {
			return e, nil
		}
	}
	return Endpoint{}, errNotFound
}

type err string

func (e err) Error() string { return string(e) }
<commit_msg>tables: Lookup should be value not pointer<commit_after>// Package tables implements (URL, HTTP method) -> SermoCRM action lookup
// tables. A table is an RPC service's set of endpoints.
package tables

// Table is a mapping of URLs to Endpoints.
type Table map[string][]Endpoint

// Endpoint is an RPC endpoint.
type Endpoint struct {
	// Method is the case-sensitive method.
	Method string

	// Unauthenticated is true if the endpoint does not require authentication.
	Unauthenticated bool

	// Action is the Endpoint's action.
	Action string
}

// Mapping maps (URL, HTTP method) -> SermoCRM actions.
type Mapping struct {
	t Table
}

// MakeMapping creates a new Mapping.
func MakeMapping(fns ...func() Table) Mapping {
	t := make(Table)
	for _, fn := range fns {
		for url, eps := range fn() {
			t[url] = append(t[url], eps...)
		}
	}
	return Mapping{t: t}
}

// Mapping finds an endpoint based on a URL and HTTP method pair.
func (m Mapping) Lookup(url, method string) (Endpoint, error) {
	const errNotFound = err("url not found")

	eps, ok := m.t[url]
	if !ok {
		return Endpoint{}, errNotFound
	}
	for _, e := range eps {
		if e.Method == method {
			return e, nil
		}
	}
	return Endpoint{}, errNotFound
}

type err string

func (e err) Error() string { return string(e) }
<|endoftext|>


<commit_before>// Package pomodoro contains functionality for timing work tasks and calling a user-supplied callback on end or cancel.
// This is currently Discord-specific, due to the "NotifyInfo" struct being strongly typed, rather than an interface{}.
// I prefer this for now, until other concrete use cases have been decided upon.
package pomodoro

// Copyright 2017 Sean A. Pfeifer

import (
	"sync"
	"time"
)

// Pomodoro represents a single Pomodoro instance, which can be started and stopped.
// There were a few options I considered when designing this:
//
// • A sync.Mutex on each Pomodoro, which is locked and unlocked internally to maintain state.
//
// • Use channels to notify the Pomodoro of events, which handles the state management on its own goroutine
//
// I chose the channel option.  This was to avoid the risks of issues related to locking (user error), as well as
// generally to make it more idiomatic Go.
type Pomodoro struct {
	workDuration time.Duration // The duration for a regular Pomodoro work cycle
	onWorkEnd    PomodoroCallback
	notifyInfo   NotifyInfo

	cancelChan chan bool // A channel to interrupt our wait if this Pomodoro is cancelled first
	cancel     sync.Once // To ensure we only close the cancelChan once
}

// PomodoroCallback is the type of function that will be called upon Pomodoro task completion.  These may be called in a separate
// goroutine, and thus should be made goroutine-safe.
//
// It receives the NotifyInfo and a boolean to tell the receiver whether the task completed (true), or was cancelled (false).
type PomodoroCallback func(info NotifyInfo, completed bool)

// NotifyInfo contains the necessary information to notify the creating user upon ending the Pomodoro.
type NotifyInfo struct {
	Title     string // The title of the work task
	UserID    string // The UserID to notify
	GuildID   string // The Guild (Discord server) that the user created the Pomodoro on
	ChannelID string // The Channel to notify with the state of the Pomodoro
}

// NewPomodoro creates a new Pomodoro and starts it, similar to time.NewTimer. "Start" functionality
// is intentionally omitted to prevent double-starting.
//
// onWorkEnd is called after the Pomodoro has been completed or cancelled.
func NewPomodoro(workDuration time.Duration, onWorkEnd PomodoroCallback, notify NotifyInfo) *Pomodoro {
	pom := &Pomodoro{
		workDuration,
		onWorkEnd,
		notify,
		make(chan bool),
		sync.Once{},
	}

	go pom.performPom()

	return pom
}

// Cancel is used to cancel a current work cycle. This uses "sync.Once" so we prevent a panic if, for whatever
// reason, the caller is able to call Cancel more than once.
//
// This method is goroutine-safe, and will cancel a Pomodoro only once (multiple calls are OK).
func (pom *Pomodoro) Cancel() {
	pom.cancel.Do(func() {
		close(pom.cancelChan)
	})
}

func (pom *Pomodoro) performPom() {
	workTimer := time.NewTimer(pom.workDuration)

	select {
	case <-workTimer.C:
		go pom.onWorkEnd(pom.notifyInfo, true)
	case <-pom.cancelChan:
		workTimer.Stop()
		go pom.onWorkEnd(pom.notifyInfo, false)
	}
}

// ChannelPomMap is a map-like structure that has goroutine-safe operations to create Pomodoros on individual channels.
type ChannelPomMap struct {
	sync.Mutex
	channelToPom map[string]*Pomodoro
}

// NewChannelPomMap creates a ChannelPomMap and prepares it to be used.
func NewChannelPomMap() ChannelPomMap {
	return ChannelPomMap{channelToPom: make(map[string]*Pomodoro)}
}

// CreateIfEmpty will create and start a Pomodoro on the given channel if one does not already exist.
// The Pomodoro will be removed from the map when its work is complete, or it is cancelled.
//
// This method is goroutine-safe.
func (m *ChannelPomMap) CreateIfEmpty(duration time.Duration, onWorkEnd PomodoroCallback, notify NotifyInfo) bool {
	m.Lock()
	defer m.Unlock()

	wasCreated := false
	if _, exists := m.channelToPom[notify.ChannelID]; !exists {
		// Ensure we remove the Pomodoro from the map when it completes
		doneInMap := func(notif NotifyInfo, completed bool) {
			// Note that this call is done so we use the mutex. The cancellation will never trigger "onWorkEnd", since the "performPom"
			// goroutine will already be complete by this point.
			m.RemoveIfExists(notif.ChannelID)
			onWorkEnd(notif, completed)
		}

		m.channelToPom[notify.ChannelID] = NewPomodoro(duration, doneInMap, notify)
		wasCreated = true
	}

	return wasCreated
}

// RemoveIfExists will stop and remove a Pomodoro from the given channel if one exists.  Note that this will perform cancellation of
// the Pomodoro if it is running and call the onWorkEnded callback.
//
// It returns a boolean representing whether the Pomodoro was removed.
//
// This method is goroutine-safe.
func (m *ChannelPomMap) RemoveIfExists(channel string) bool {
	m.Lock()
	defer m.Unlock()

	wasRemoved := false
	if p, exists := m.channelToPom[channel]; exists {
		delete(m.channelToPom, channel)
		p.Cancel()
		wasRemoved = true
	}

	return wasRemoved
}
<commit_msg>Renamed "PomodoroCallback" to "TaskCallback" to prevent stuttering. Added comment clarifications.<commit_after>// Package pomodoro contains functionality for timing work tasks and calling a user-supplied callback on end or cancel.
// This is currently Discord-specific, due to the "NotifyInfo" struct being strongly typed, rather than an interface{}.
// I prefer this for now, until other concrete use cases have been decided upon.
package pomodoro

// Copyright 2017 Sean A. Pfeifer

import (
	"sync"
	"time"
)

// Pomodoro represents a single Pomodoro instance, which can be started and stopped.
// There were a few options I considered when designing this:
//
// • A sync.Mutex on each Pomodoro, which is locked and unlocked internally to maintain state.
//
// • Use channels to notify the Pomodoro of events, which handles the state management on its own goroutine
//
// I chose the channel option.  This was to avoid the risks of issues related to locking if the code is
// expanded upon(user error), as well as generally to make it more idiomatic Go.
type Pomodoro struct {
	workDuration time.Duration // The duration for a regular Pomodoro work cycle
	onWorkEnd    TaskCallback
	notifyInfo   NotifyInfo

	cancelChan chan bool // A channel to interrupt our wait if this Pomodoro is cancelled first
	cancel     sync.Once // To ensure we only close the cancelChan once
}

// TaskCallback is the type of function that will be called upon Pomodoro task completion.  These may be called in a separate
// goroutine, and thus should be made goroutine-safe.
//
// It receives the NotifyInfo and a boolean to tell the receiver whether the task completed (true), or was cancelled (false).
type TaskCallback func(info NotifyInfo, completed bool)

// NotifyInfo contains the necessary information to notify the creating user upon ending the Pomodoro.
type NotifyInfo struct {
	Title     string // The title of the work task
	UserID    string // The UserID to notify
	GuildID   string // The Guild (Discord server) that the user created the Pomodoro on
	ChannelID string // The Channel to notify with the state of the Pomodoro
}

// NewPomodoro creates a new Pomodoro and starts it, similar to time.NewTimer. "Start" functionality
// is intentionally omitted to prevent double-starting.
//
// onWorkEnd is called after the Pomodoro has been completed or cancelled.
func NewPomodoro(workDuration time.Duration, onWorkEnd TaskCallback, notify NotifyInfo) *Pomodoro {
	pom := &Pomodoro{
		workDuration,
		onWorkEnd,
		notify,
		make(chan bool),
		sync.Once{},
	}

	go pom.performPom()

	return pom
}

// Cancel is used to cancel a current work cycle. This uses "sync.Once" so we prevent a panic if, for whatever
// reason, the caller is able to call Cancel more than once.
//
// This method is goroutine-safe, and will cancel a Pomodoro only once (multiple calls are OK).
func (pom *Pomodoro) Cancel() {
	pom.cancel.Do(func() {
		close(pom.cancelChan)
	})
}

func (pom *Pomodoro) performPom() {
	workTimer := time.NewTimer(pom.workDuration)

	select {
	case <-workTimer.C:
		go pom.onWorkEnd(pom.notifyInfo, true)
	case <-pom.cancelChan:
		workTimer.Stop()
		go pom.onWorkEnd(pom.notifyInfo, false)
	}
}

// ChannelPomMap is a map-like structure that has goroutine-safe operations to create Pomodoros on individual channels.
type ChannelPomMap struct {
	sync.Mutex
	channelToPom map[string]*Pomodoro
}

// NewChannelPomMap creates a ChannelPomMap and prepares it to be used.
func NewChannelPomMap() ChannelPomMap {
	return ChannelPomMap{channelToPom: make(map[string]*Pomodoro)}
}

// CreateIfEmpty will create and start a Pomodoro on the given channel if one does not already exist.
// The Pomodoro will be removed from the map when its work is complete, or it is cancelled.
//
// This method is goroutine-safe.
func (m *ChannelPomMap) CreateIfEmpty(duration time.Duration, onWorkEnd TaskCallback, notify NotifyInfo) bool {
	m.Lock()
	defer m.Unlock()

	wasCreated := false
	if _, exists := m.channelToPom[notify.ChannelID]; !exists {
		// Ensure we remove the Pomodoro from the map when it completes
		doneInMap := func(notif NotifyInfo, completed bool) {
			// Note that this call is done so we use the mutex. The cancellation will never trigger "onWorkEnd", since the "performPom"
			// goroutine will already be complete by this point.
			m.RemoveIfExists(notif.ChannelID)
			onWorkEnd(notif, completed)
		}

		m.channelToPom[notify.ChannelID] = NewPomodoro(duration, doneInMap, notify)
		wasCreated = true
	}

	return wasCreated
}

// RemoveIfExists will stop and remove a Pomodoro from the given channel if one exists.  Note that this will perform cancellation of
// the Pomodoro if it is running and call the onWorkEnded callback.
//
// It returns a boolean representing whether the Pomodoro was removed.
//
// This method is goroutine-safe.
func (m *ChannelPomMap) RemoveIfExists(channel string) bool {
	m.Lock()
	defer m.Unlock()

	wasRemoved := false
	if p, exists := m.channelToPom[channel]; exists {
		delete(m.channelToPom, channel)
		p.Cancel()
		wasRemoved = true
	}

	return wasRemoved
}
<|endoftext|>


<commit_before>package tally

import (
	"fmt"
	"github.com/qmsk/e2/client"
	"github.com/qmsk/e2/discovery"
	"io"
	"regexp"
	"time"
)

var INPUT_CONTACT_REGEXP = regexp.MustCompile("tally=(\\d+)")

func newSource(tally *Tally, discoveryPacket discovery.Packet, clientOptions client.Options) (Source, error) {
	source := Source{
		options:		 tally.options,
		created:		 time.Now(),
		discoveryPacket: discoveryPacket,
		clientOptions:   clientOptions,
	}

	// do not connect to slave VPs
	if discoveryPacket.MasterMac != discoveryPacket.MacAddress {
		return source, nil
	}

	// give updates every 10s when idle
	clientOptions.ReadKeepalive = true

	if xmlClient, err := clientOptions.XMLClient(); err != nil {
		return source, err
	} else {
		source.xmlClient = xmlClient
	}

	go source.run(tally.sourceChan)

	return source, nil
}

// E2 XML source
//
// A source can either be in a running state with err == nil, or in a failed state with err != nil
type Source struct {
	options			Options
	created			time.Time
	updated			time.Time

	discoveryPacket	discovery.Packet
	clientOptions	client.Options
	xmlClient       *client.XMLClient

	system client.System
	closed bool
	err    error
}

func (source Source) String() string {
	return source.clientOptions.String()
}

func (source Source) run(updateChan chan Source) {
	for {
		if system, err := source.xmlClient.Read(); err == nil {
			source.system = system
		} else if err == io.EOF {
			source.closed = true
		} else {
			source.err = err
		}

		updateChan <- source

		if source.err != nil {
			break
		}
	}
}

func (source Source) updateState(state *State) error {
	tallySource := source.String()

	if source.err != nil {
		return source.err
	}

	if source.xmlClient == nil {
		// not connected
		return nil
	}

	system := source.system

	for sourceID, systemSource := range system.SrcMgr.SourceCol.List() {
		// lookup Input from inputCfg with tally=
		if systemSource.InputCfgIndex < 0 {
			continue
		}
		inputCfg := system.SrcMgr.InputCfgCol[systemSource.InputCfgIndex]
		inputName := inputCfg.Name

		// resolve ID
		var tallyID ID

		if match := INPUT_CONTACT_REGEXP.FindStringSubmatch(inputCfg.ConfigContact); match == nil {
			continue
		} else if _, err := fmt.Sscanf(match[1], "%d", &tallyID); err != nil {
			return fmt.Errorf("Invalid Input Contact=%v: %v\n", inputCfg.ConfigContact, err)
		}

		input := state.addInput(tallySource, inputName, tallyID, inputCfg.InputCfgVideoStatus.String())

		// input state
		if inputCfg.InputCfgVideoStatus == client.InputVideoStatusBad {
			state.addTallyError(tallyID, input, fmt.Errorf("Source %v Input %v video status: %v", tallySource, inputName, inputCfg.InputCfgVideoStatus))
		}

		// lookup active Links
		for _, screen := range system.DestMgr.ScreenDestCol {
			// ignore?
			if source.options.ignoreDestRegexp != nil && source.options.ignoreDestRegexp.MatchString(screen.Name) {
				continue
			}

			var status Status

			if screen.IsActive > 0 {
				status.Active = true
			}

			for _, layer := range screen.LayerCollection {
				if layer.LastSrcIdx == sourceID {
					if layer.PvwMode > 0 {
						status.Preview = true
					}
					if layer.PgmMode > 0 {
						status.Program = true
					}
				}
			}

			output := state.addOutput(tallySource, screen.Name)

			if status.Preview || status.Program {
				state.addLink(Link{
					Tally:  tallyID,
					Input:  input,
					Output: output,
					Status: status,
				})
			}
		}

		for _, aux := range system.DestMgr.AuxDestCol {
			// ignore?
			if source.options.ignoreDestRegexp != nil && source.options.ignoreDestRegexp.MatchString(aux.Name) {
				continue
			}

			// TODO: active

			output := state.addOutput(tallySource, aux.Name)

			if aux.PvwLastSrcIndex == sourceID || aux.PgmLastSrcIndex == sourceID {
				state.addLink(Link{
					Tally:  tallyID,
					Input:  input,
					Output: output,
					Status: Status{
						Preview: (aux.PvwLastSrcIndex == sourceID),
						Program: (aux.PgmLastSrcIndex == sourceID),
					},
				})
			}
		}
	}

	return nil
}

// close, causing run() to exit
func (source Source) close() {
	source.xmlClient.Close()
}
<commit_msg>tally: fix aux output active status<commit_after>package tally

import (
	"fmt"
	"github.com/qmsk/e2/client"
	"github.com/qmsk/e2/discovery"
	"io"
	"regexp"
	"time"
)

var INPUT_CONTACT_REGEXP = regexp.MustCompile("tally=(\\d+)")

func newSource(tally *Tally, discoveryPacket discovery.Packet, clientOptions client.Options) (Source, error) {
	source := Source{
		options:		 tally.options,
		created:		 time.Now(),
		discoveryPacket: discoveryPacket,
		clientOptions:   clientOptions,
	}

	// do not connect to slave VPs
	if discoveryPacket.MasterMac != discoveryPacket.MacAddress {
		return source, nil
	}

	// give updates every 10s when idle
	clientOptions.ReadKeepalive = true

	if xmlClient, err := clientOptions.XMLClient(); err != nil {
		return source, err
	} else {
		source.xmlClient = xmlClient
	}

	go source.run(tally.sourceChan)

	return source, nil
}

// E2 XML source
//
// A source can either be in a running state with err == nil, or in a failed state with err != nil
type Source struct {
	options			Options
	created			time.Time
	updated			time.Time

	discoveryPacket	discovery.Packet
	clientOptions	client.Options
	xmlClient       *client.XMLClient

	system client.System
	closed bool
	err    error
}

func (source Source) String() string {
	return source.clientOptions.String()
}

func (source Source) run(updateChan chan Source) {
	for {
		if system, err := source.xmlClient.Read(); err == nil {
			source.system = system
		} else if err == io.EOF {
			source.closed = true
		} else {
			source.err = err
		}

		updateChan <- source

		if source.err != nil {
			break
		}
	}
}

func (source Source) updateState(state *State) error {
	tallySource := source.String()

	if source.err != nil {
		return source.err
	}

	if source.xmlClient == nil {
		// not connected
		return nil
	}

	system := source.system

	for sourceID, systemSource := range system.SrcMgr.SourceCol.List() {
		// lookup Input from inputCfg with tally=
		if systemSource.InputCfgIndex < 0 {
			continue
		}
		inputCfg := system.SrcMgr.InputCfgCol[systemSource.InputCfgIndex]
		inputName := inputCfg.Name

		// resolve ID
		var tallyID ID

		if match := INPUT_CONTACT_REGEXP.FindStringSubmatch(inputCfg.ConfigContact); match == nil {
			continue
		} else if _, err := fmt.Sscanf(match[1], "%d", &tallyID); err != nil {
			return fmt.Errorf("Invalid Input Contact=%v: %v\n", inputCfg.ConfigContact, err)
		}

		input := state.addInput(tallySource, inputName, tallyID, inputCfg.InputCfgVideoStatus.String())

		// input state
		if inputCfg.InputCfgVideoStatus == client.InputVideoStatusBad {
			state.addTallyError(tallyID, input, fmt.Errorf("Source %v Input %v video status: %v", tallySource, inputName, inputCfg.InputCfgVideoStatus))
		}

		// lookup active Links
		for _, screen := range system.DestMgr.ScreenDestCol {
			// ignore?
			if source.options.ignoreDestRegexp != nil && source.options.ignoreDestRegexp.MatchString(screen.Name) {
				continue
			}

			var status Status

			if screen.IsActive > 0 {
				status.Active = true
			}

			for _, layer := range screen.LayerCollection {
				if layer.LastSrcIdx == sourceID {
					if layer.PvwMode > 0 {
						status.Preview = true
					}
					if layer.PgmMode > 0 {
						status.Program = true
					}
				}
			}

			output := state.addOutput(tallySource, screen.Name)

			if status.Preview || status.Program {
				state.addLink(Link{
					Tally:  tallyID,
					Input:  input,
					Output: output,
					Status: status,
				})
			}
		}

		for _, aux := range system.DestMgr.AuxDestCol {
			// ignore?
			if source.options.ignoreDestRegexp != nil && source.options.ignoreDestRegexp.MatchString(aux.Name) {
				continue
			}

			output := state.addOutput(tallySource, aux.Name)

			if aux.PvwLastSrcIndex == sourceID || aux.PgmLastSrcIndex == sourceID {
				state.addLink(Link{
					Tally:  tallyID,
					Input:  input,
					Output: output,
					Status: Status{
						Preview: (aux.PvwLastSrcIndex == sourceID),
						Program: (aux.PgmLastSrcIndex == sourceID),
						Active: (aux.IsActive > 0),
					},
				})
			}
		}
	}

	return nil
}

// close, causing run() to exit
func (source Source) close() {
	source.xmlClient.Close()
}
<|endoftext|>


<commit_before>package tap_test

import (
	"testing"

	"github.com/bobuhiro11/gokvm/tap"
)

func TestNew(t *testing.T) {
	t.Parallel()

	tap, err := tap.New("test_tap")
	if err != nil {
		t.Fatal(err)
	}

	err = tap.Close()
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>tap: add tests for read & write funcs<commit_after>package tap_test

import (
	"errors"
	"os/exec"
	"syscall"
	"testing"

	"github.com/bobuhiro11/gokvm/tap"
)

func TestNew(t *testing.T) {
	t.Parallel()

	tap, err := tap.New("test_tap")
	if err != nil {
		t.Fatal(err)
	}

	err = tap.Close()
	if err != nil {
		t.Fatal(err)
	}
}

func TestWrite(t *testing.T) {
	t.Parallel()

	tap, err := tap.New("test_write")
	if err != nil {
		t.Fatal(err)
	}

	if err := exec.Command("ip", "link", "set", "test_write", "up").Run(); err != nil {
		t.Fatal(err)
	}

	if _, err := tap.Write(make([]byte, 20)); err != nil {
		t.Fatal(err)
	}

	_ = tap.Close()
}

func TestRead(t *testing.T) {
	t.Parallel()

	tap, err := tap.New("test_read")
	if err != nil {
		t.Fatal(err)
	}

	if err := exec.Command("ip", "link", "set", "test_read", "up").Run(); err != nil {
		t.Fatal(err)
	}

	buf := make([]byte, 20)
	if _, err := tap.Read(buf); !errors.Is(err, syscall.EAGAIN) {
		t.Fatal(err)
	}

	_ = tap.Close()
}
<|endoftext|>


<commit_before>package search

import (
    "path/filepath"
    "net/http"
    "os"
    "regexp"
    "encoding/json"
    "log"
    "io"
    "strings"

    "conf"
    "util"
)

var (
    docPath = conf.DocxConf.GetJson("path").(string)
    mdReg   = ".+.md$"
    key = ""
)

type searchTitle struct {
    Path string `json:"path"`
    Title string `json:"title"`
}

// type searchTitle map[string]string

var pathCtt = []string{}

type searchCtt []searchTitle

func SearchRoutes(w http.ResponseWriter, r *http.Request) {
    key = r.FormValue("name")

    if key != "" {
        search(w, r)
    }
}

// 搜索
func search(w http.ResponseWriter, r *http.Request) {
    if len(pathCtt) == 0 {
        filepath.Walk(docPath, walkFn)
    }

    filterRs := collectRs()
    returnJSON(filterRs, w, r)
}

func walkFn (walkPath string, info os.FileInfo, err error) error{
    isMd, err := regexp.MatchString(mdReg, walkPath)
    if !info.IsDir() {
        if err == nil && isMd {
            pathCtt = append(pathCtt, walkPath)
        }
    }
    return nil
}

// 返回搜索结果
func returnJSON(js []searchTitle, w http.ResponseWriter, r *http.Request) {
    jsonRs, err := json.Marshal(js)
	if err != nil {
		log.Fatal(err)
	}
    io.WriteString(w, string(jsonRs))
}

func collectRs() []searchTitle{
    // 替换标题
    keyRe := regexp.MustCompile(key)
    var stt searchCtt
    for _, v := range pathCtt {
        content := util.GetConent(v)
        ext := filepath.Ext(v)
        title := util.GetTitle(ext, content)

        var st = searchTitle{}
        ok, _ := regexp.MatchString(key, title)
        if  ok {
            replacedTitle := keyRe.ReplaceAllString(title, "$0")
            st.Path = strings.Replace(v, docPath, "", -1)
            st.Title = replacedTitle
            stt = append(stt, st)
        }
    }
    return stt
}<commit_msg>update title search<commit_after>package search

import (
    "path/filepath"
    "net/http"
    "os"
    "regexp"
    "encoding/json"
    "log"
    "io"
    "strings"

    "conf"
    "util"
)

var (
    docPath = conf.DocxConf.GetJson("path").(string)
    mdReg   = ".+.md$"
    key = ""
)

type searchTitle struct {
    Path string `json:"path"`
    Title string `json:"title"`
    Content string `json:"content"`
}

var pathCtt = []string{}

type searchCtt []searchTitle

func SearchRoutes(w http.ResponseWriter, r *http.Request) {
    key = r.FormValue("name")
    setype := r.FormValue("type")
    if key != "" {
        if len(pathCtt) == 0 {
            filepath.Walk(docPath, walkFn)
        }
        searchFn(w, r, setype)
    }
}

// 标题搜索
func searchFn(w http.ResponseWriter, r *http.Request, setype string) {
    filterRs := collectRs(setype)
    returnJSON(filterRs, w, r)
}

func walkFn (walkPath string, info os.FileInfo, err error) error{
    isMd, err := regexp.MatchString(mdReg, walkPath)
    if !info.IsDir() {
        if err == nil && isMd {
            pathCtt = append(pathCtt, walkPath)
        }
    }
    return nil
}

// 返回搜索结果
func returnJSON(js []searchTitle, w http.ResponseWriter, r *http.Request) {
    jsonRs, err := json.Marshal(js)
	if err != nil {
		log.Fatal(err)
	}
    // TODO 返回对象
    io.WriteString(w, string(jsonRs))
}

func collectRs(setype string) []searchTitle{
    // 替换标题
    keyRe := regexp.MustCompile(key)
    var stt searchCtt
    for _, v := range pathCtt {
        content := util.GetConent(v)
        ext := filepath.Ext(v)
        title := util.GetTitle(ext, content)

        var st = searchTitle{}
        ok, _ := regexp.MatchString(key, title)
        if  ok {
            replacedTitle := keyRe.ReplaceAllString(title, "$0")
            st.Path = strings.Replace(v, docPath, "", -1)
            st.Title = replacedTitle
            if setype == "" {
                replacedCtt := searchContentFn(content, title)
                st.Content = replacedCtt
            }

            stt = append(stt, st)
        }
    }
    return stt
}

// 内容搜索
func searchContentFn(content []byte, title string) string{
    keyRe := regexp.MustCompile(key)
    // TODO
    replacedContent := keyRe.ReplaceAllString(string(content), "$0")
    return replacedContent
}<|endoftext|>


<commit_before>package reporter

import (
	"fmt"
	"log"
	"net"
	"strings"
	"time"

	"github.com/samuel/go-metrics/metrics"
)

type graphiteReporter struct {
	addr             string
	percentiles      []float64
	percentileNames  []string
	previousCounters map[string]int64 // TODO: These should expire if counters aren't seen again
}

func NewGraphiteReporter(registry *metrics.Registry, interval time.Duration, addr string, percentiles map[string]float64) *PeriodicReporter {
	per := metrics.DefaultPercentiles
	perNames := metrics.DefaultPercentileNames

	if percentiles != nil {
		per = make([]float64, 0)
		perNames = make([]string, 0)
		for name, p := range percentiles {
			per = append(per, p)
			perNames = append(perNames, name)
		}
	}

	gr := &graphiteReporter{
		addr:             addr,
		percentiles:      per,
		percentileNames:  perNames,
		previousCounters: make(map[string]int64),
	}
	return NewPeriodicReporter(registry, interval, false, gr)
}

func (r *graphiteReporter) Report(registry *metrics.Registry) {
	conn, err := net.Dial("tcp", r.addr)
	if err != nil {
		log.Printf("Failed to connect to graphite/carbon: %+v", err)
		return
	}
	defer conn.Close()

	ts := time.Now().UTC().Unix()

	err = registry.Do(func(name string, metric interface{}) error {
		name = strings.Replace(name, "/", ".", -1)
		switch m := metric.(type) {
		case metrics.CounterValue:
			count := int64(m)
			prev := r.previousCounters[name]
			r.previousCounters[name] = count
			if _, err := fmt.Fprintf(conn, "%s %d %d\n", name, count-prev, ts); err != nil {
				return err
			}
		case metrics.GaugeValue:
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", name, m, ts); err != nil {
				return err
			}
		case metrics.Counter:
			count := m.Count()
			prev := r.previousCounters[name]
			r.previousCounters[name] = count
			if _, err := fmt.Fprintf(conn, "%s %d %d\n", name, count-prev, ts); err != nil {
				return err
			}
		case *metrics.EWMA:
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", name, m.Rate(), ts); err != nil {
				return err
			}
		case *metrics.Meter:
			if _, err := fmt.Fprintf(conn, "%s.1m %f %d\n", name, m.OneMinuteRate(), ts); err != nil {
				return err
			}
			if _, err := fmt.Fprintf(conn, "%s.5m %f %d\n", name, m.FiveMinuteRate(), ts); err != nil {
				return err
			}
			if _, err := fmt.Fprintf(conn, "%s.15m %f %d\n", name, m.FifteenMinuteRate(), ts); err != nil {
				return err
			}
		case metrics.Histogram:
			count := m.Count()
			if count > 0 {
				if _, err := fmt.Fprintf(conn, "%s.mean %f %d\n", name, m.Mean(), ts); err != nil {
					return err
				}
				percentiles := m.Percentiles(r.percentiles)
				for i, perc := range percentiles {
					if _, err := fmt.Fprintf(conn, "%s.%s %d %d\n", name, r.percentileNames[i], perc, ts); err != nil {
						return err
					}
				}
			}
		default:
			log.Printf("Unrecognized metric type for %s: %+v", name, m)
		}
		return nil
	})
	if err != nil {
		log.Printf("ERR graphite: %+v", err)
	}
}
<commit_msg>Add optional source to graphite<commit_after>package reporter

import (
	"fmt"
	"log"
	"net"
	"strings"
	"time"

	"github.com/samuel/go-metrics/metrics"
)

type graphiteReporter struct {
	addr             string
	source           string
	percentiles      []float64
	percentileNames  []string
	previousCounters map[string]int64 // TODO: These should expire if counters aren't seen again
}

func NewGraphiteReporter(registry *metrics.Registry, interval time.Duration, addr, source string, percentiles map[string]float64) *PeriodicReporter {
	per := metrics.DefaultPercentiles
	perNames := metrics.DefaultPercentileNames

	if percentiles != nil {
		per = make([]float64, 0)
		perNames = make([]string, 0)
		for name, p := range percentiles {
			per = append(per, p)
			perNames = append(perNames, name)
		}
	}

	gr := &graphiteReporter{
		addr:             addr,
		source:           source,
		percentiles:      per,
		percentileNames:  perNames,
		previousCounters: make(map[string]int64),
	}
	return NewPeriodicReporter(registry, interval, false, gr)
}

func (r *graphiteReporter) sourcedName(name string) string {
	if r.source != "" {
		return name + "." + r.source
	}
	return name
}

func (r *graphiteReporter) Report(registry *metrics.Registry) {
	conn, err := net.Dial("tcp", r.addr)
	if err != nil {
		log.Printf("Failed to connect to graphite/carbon: %+v", err)
		return
	}
	defer conn.Close()

	ts := time.Now().UTC().Unix()

	err = registry.Do(func(name string, metric interface{}) error {
		name = strings.Replace(name, "/", ".", -1)
		switch m := metric.(type) {
		case metrics.CounterValue:
			count := int64(m)
			prev := r.previousCounters[name]
			r.previousCounters[name] = count
			if _, err := fmt.Fprintf(conn, "%s %d %d\n", r.sourcedName(name), count-prev, ts); err != nil {
				return err
			}
		case metrics.GaugeValue:
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", r.sourcedName(name), m, ts); err != nil {
				return err
			}
		case metrics.Counter:
			count := m.Count()
			prev := r.previousCounters[name]
			r.previousCounters[name] = count
			if _, err := fmt.Fprintf(conn, "%s %d %d\n", r.sourcedName(name), count-prev, ts); err != nil {
				return err
			}
		case *metrics.EWMA:
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", r.sourcedName(name), m.Rate(), ts); err != nil {
				return err
			}
		case *metrics.Meter:
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", r.sourcedName(name+".1m"), m.OneMinuteRate(), ts); err != nil {
				return err
			}
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", r.sourcedName(name+".5m"), m.FiveMinuteRate(), ts); err != nil {
				return err
			}
			if _, err := fmt.Fprintf(conn, "%s %f %d\n", r.sourcedName(name+".15m"), m.FifteenMinuteRate(), ts); err != nil {
				return err
			}
		case metrics.Histogram:
			count := m.Count()
			if count > 0 {
				if _, err := fmt.Fprintf(conn, "%s.mean %f %d\n", r.sourcedName(name), m.Mean(), ts); err != nil {
					return err
				}
				percentiles := m.Percentiles(r.percentiles)
				for i, perc := range percentiles {
					if _, err := fmt.Fprintf(conn, "%s %d %d\n", r.sourcedName(name+"."+r.percentileNames[i]), perc, ts); err != nil {
						return err
					}
				}
			}
		default:
			log.Printf("Unrecognized metric type for %s: %+v", name, m)
		}
		return nil
	})
	if err != nil {
		log.Printf("ERR graphite: %+v", err)
	}
}
<|endoftext|>


<commit_before>package fs

import (
	"fmt"
	"testing"
	"time"

	"github.com/gravitational/teleport/lib/backend"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"

	"github.com/mailgun/timetools"
	"gopkg.in/check.v1"
)

type Suite struct {
	dirName string
	bk      backend.Backend
	clock   timetools.FreezedTime
}

var _ = check.Suite(&Suite{})

// bootstrap check.v1:
func TestFSBackend(t *testing.T) { check.TestingT(t) }

func (s *Suite) SetUpSuite(c *check.C) {
	dirName := c.MkDir()
	bk, err := FromJSON(fmt.Sprintf(`{ "path": "%s" }`, dirName))

	c.Assert(err, check.IsNil)
	c.Assert(bk.RootDir, check.Equals, dirName)
	c.Assert(utils.IsDir(bk.RootDir), check.Equals, true)

	bk.Clock = &s.clock
	s.bk = bk
}

func (s *Suite) TestCreateAndRead(c *check.C) {
	bucket := []string{"one", "two"}

	// must succeed:
	err := s.bk.CreateVal(bucket, "key", []byte("original"), backend.Forever)
	c.Assert(err, check.IsNil)

	// must get 'already exists' error
	err = s.bk.CreateVal(bucket, "key", []byte("failed-write"), backend.Forever)
	c.Assert(trace.IsAlreadyExists(err), check.Equals, true)

	// read back the original:
	val, err := s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(val), check.Equals, "original")

	// upsert:
	err = s.bk.UpsertVal(bucket, "key", []byte("new-value"), backend.Forever)
	c.Assert(err, check.IsNil)

	// read back the new value:
	val, err = s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(val), check.Equals, "new-value")

	// read back non-existing (bad path):
	val, err = s.bk.GetVal([]string{"bad", "path"}, "key")
	c.Assert(err, check.NotNil)
	c.Assert(val, check.IsNil)
	c.Assert(trace.IsNotFound(err), check.Equals, true)

	// read back non-existing (bad key):
	val, err = s.bk.GetVal(bucket, "bad-key")
	c.Assert(err, check.NotNil)
	c.Assert(val, check.IsNil)
	c.Assert(trace.IsNotFound(err), check.Equals, true)
}

func (s *Suite) TestListDelete(c *check.C) {
	root := []string{"root"}
	kid := []string{"root", "kid"}

	// list from non-existing bucket (must return an empty array)
	kids, err := s.bk.GetKeys([]string{"bad", "bucket"})
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 0)

	// create two entries in root:
	s.bk.CreateVal(root, "one", []byte("1"), backend.Forever)
	s.bk.CreateVal(root, "two", []byte("2"), time.Second)

	// create one entry in the kid:
	s.bk.CreateVal(kid, "three", []byte("3"), backend.Forever)

	// list the root (should get 2 back):
	kids, err = s.bk.GetKeys(root)
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 2)
	c.Assert(kids[0], check.Equals, "one")
	c.Assert(kids[1], check.Equals, "two")

	// list the kid (should get 1)
	kids, err = s.bk.GetKeys(kid)
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 1)
	c.Assert(kids[0], check.Equals, "three")

	// delete one of the kids:
	err = s.bk.DeleteKey(kid, "three")
	c.Assert(err, check.IsNil)
	kids, err = s.bk.GetKeys(kid)
	c.Assert(kids, check.HasLen, 0)

	// try to delete non-existing key:
	err = s.bk.DeleteKey(kid, "three")
	c.Assert(trace.IsNotFound(err), check.Equals, true)

	// try to delete the root bucket:
	err = s.bk.DeleteBucket(root, "kid")
	c.Assert(err, check.IsNil)
}

func (s *Suite) TestTTL(c *check.C) {
	bucket := []string{"root"}
	value := []byte("value")

	s.bk.CreateVal(bucket, "key", value, time.Second)
	v, err := s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(v), check.Equals, string(value))

	// after sleeping for 2 seconds the value must be gone:
	s.clock.Sleep(time.Second * 2)
	v, err = s.bk.GetVal(bucket, "key")
	c.Assert(trace.IsNotFound(err), check.Equals, true)
	c.Assert(err.Error(), check.Equals, `key 'key' is not found`)
	c.Assert(v, check.IsNil)
}

func (s *Suite) TestLock(c *check.C) {
	protectedFlag := true
	defer s.bk.ReleaseLock("lock")

	err := s.bk.AcquireLock("lock", time.Second)
	c.Assert(err, check.IsNil)

	go func() {
		defer s.bk.ReleaseLock("lock")
		s.bk.AcquireLock("lock", time.Second)
		protectedFlag = false
	}()

	s.clock.Sleep(time.Millisecond)
	c.Assert(protectedFlag, check.Equals, true)
}

func (s *Suite) TestLockTTL(c *check.C) {
	protectedFlag := true
	ln := "ttl-test"

	err := s.bk.AcquireLock(ln, time.Second)
	c.Assert(err, check.IsNil)
	defer s.bk.ReleaseLock(ln)

	go func() {
		s.bk.AcquireLock(ln, time.Minute)
		protectedFlag = false
		s.bk.ReleaseLock(ln)
	}()

	time.Sleep(time.Millisecond) // give the goroutine some time to start

	// wait for 5 seconds. this should be enough for the 1st lock
	// to expire and the goroutine should be able to flip the flag
	s.clock.Sleep(time.Second * 5)
	c.Assert(protectedFlag, check.Equals, false)
}
<commit_msg>Fixed data race in tests<commit_after>package fs

import (
	"fmt"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/gravitational/teleport/lib/backend"
	"github.com/gravitational/teleport/lib/utils"
	"github.com/gravitational/trace"

	"gopkg.in/check.v1"
)

type FrozenTime struct {
	sync.Mutex
	CurrentTime time.Time
}

func (t *FrozenTime) UtcNow() time.Time {
	t.Lock()
	defer t.Unlock()
	return t.CurrentTime
}

func (t *FrozenTime) Sleep(d time.Duration) {
	t.Lock()
	defer t.Unlock()
	t.CurrentTime = t.CurrentTime.Add(d)
}

func (t *FrozenTime) After(d time.Duration) <-chan time.Time {
	t.Sleep(d)
	c := make(chan time.Time, 1)
	c <- t.CurrentTime
	return c
}

type Suite struct {
	dirName string
	bk      backend.Backend
	clock   FrozenTime
}

var _ = check.Suite(&Suite{})

// bootstrap check.v1:
func TestFSBackend(t *testing.T) { check.TestingT(t) }

func (s *Suite) SetUpSuite(c *check.C) {
	dirName := c.MkDir()
	bk, err := FromJSON(fmt.Sprintf(`{ "path": "%s" }`, dirName))

	c.Assert(err, check.IsNil)
	c.Assert(bk.RootDir, check.Equals, dirName)
	c.Assert(utils.IsDir(bk.RootDir), check.Equals, true)

	bk.Clock = &s.clock
	s.bk = bk
}

func (s *Suite) TestCreateAndRead(c *check.C) {
	bucket := []string{"one", "two"}

	// must succeed:
	err := s.bk.CreateVal(bucket, "key", []byte("original"), backend.Forever)
	c.Assert(err, check.IsNil)

	// must get 'already exists' error
	err = s.bk.CreateVal(bucket, "key", []byte("failed-write"), backend.Forever)
	c.Assert(trace.IsAlreadyExists(err), check.Equals, true)

	// read back the original:
	val, err := s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(val), check.Equals, "original")

	// upsert:
	err = s.bk.UpsertVal(bucket, "key", []byte("new-value"), backend.Forever)
	c.Assert(err, check.IsNil)

	// read back the new value:
	val, err = s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(val), check.Equals, "new-value")

	// read back non-existing (bad path):
	val, err = s.bk.GetVal([]string{"bad", "path"}, "key")
	c.Assert(err, check.NotNil)
	c.Assert(val, check.IsNil)
	c.Assert(trace.IsNotFound(err), check.Equals, true)

	// read back non-existing (bad key):
	val, err = s.bk.GetVal(bucket, "bad-key")
	c.Assert(err, check.NotNil)
	c.Assert(val, check.IsNil)
	c.Assert(trace.IsNotFound(err), check.Equals, true)
}

func (s *Suite) TestListDelete(c *check.C) {
	root := []string{"root"}
	kid := []string{"root", "kid"}

	// list from non-existing bucket (must return an empty array)
	kids, err := s.bk.GetKeys([]string{"bad", "bucket"})
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 0)

	// create two entries in root:
	s.bk.CreateVal(root, "one", []byte("1"), backend.Forever)
	s.bk.CreateVal(root, "two", []byte("2"), time.Second)

	// create one entry in the kid:
	s.bk.CreateVal(kid, "three", []byte("3"), backend.Forever)

	// list the root (should get 2 back):
	kids, err = s.bk.GetKeys(root)
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 2)
	c.Assert(kids[0], check.Equals, "one")
	c.Assert(kids[1], check.Equals, "two")

	// list the kid (should get 1)
	kids, err = s.bk.GetKeys(kid)
	c.Assert(err, check.IsNil)
	c.Assert(kids, check.HasLen, 1)
	c.Assert(kids[0], check.Equals, "three")

	// delete one of the kids:
	err = s.bk.DeleteKey(kid, "three")
	c.Assert(err, check.IsNil)
	kids, err = s.bk.GetKeys(kid)
	c.Assert(kids, check.HasLen, 0)

	// try to delete non-existing key:
	err = s.bk.DeleteKey(kid, "three")
	c.Assert(trace.IsNotFound(err), check.Equals, true)

	// try to delete the root bucket:
	err = s.bk.DeleteBucket(root, "kid")
	c.Assert(err, check.IsNil)
}

func (s *Suite) TestTTL(c *check.C) {
	bucket := []string{"root"}
	value := []byte("value")

	s.bk.CreateVal(bucket, "key", value, time.Second)
	v, err := s.bk.GetVal(bucket, "key")
	c.Assert(err, check.IsNil)
	c.Assert(string(v), check.Equals, string(value))

	// after sleeping for 2 seconds the value must be gone:
	s.clock.Sleep(time.Second * 2)
	v, err = s.bk.GetVal(bucket, "key")
	c.Assert(trace.IsNotFound(err), check.Equals, true)
	c.Assert(err.Error(), check.Equals, `key 'key' is not found`)
	c.Assert(v, check.IsNil)
}

func (s *Suite) TestLock(c *check.C) {
	var protectedFlag int64 = 1
	defer s.bk.ReleaseLock("lock")

	err := s.bk.AcquireLock("lock", time.Second)
	c.Assert(err, check.IsNil)

	go func() {
		defer s.bk.ReleaseLock("lock")
		s.bk.AcquireLock("lock", time.Second)
		atomic.AddInt64(&protectedFlag, 1)
	}()

	s.clock.Sleep(time.Millisecond)
	c.Assert(atomic.LoadInt64(&protectedFlag), check.Equals, int64(1))
}

func (s *Suite) TestLockTTL(c *check.C) {
	var protectedFlag int64 = 1
	ln := "ttl-test"

	err := s.bk.AcquireLock(ln, time.Second)
	c.Assert(err, check.IsNil)
	defer s.bk.ReleaseLock(ln)

	go func() {
		s.bk.AcquireLock(ln, time.Minute)
		defer s.bk.ReleaseLock(ln)
		atomic.AddInt64(&protectedFlag, 1)
	}()

	time.Sleep(time.Millisecond * 3) // give the goroutine some time to start

	// wait for 5 seconds. this should be enough for the 1st lock
	// to expire and the goroutine should be able to flip the flag
	s.clock.Sleep(time.Second * 5)
	c.Assert(atomic.LoadInt64(&protectedFlag), check.Equals, int64(2))
}
<|endoftext|>


<commit_before>// Package terminal provides a tty emulation and session handlings that is
// supported via Screen
package terminal

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"koding/tools/pty"
	"os/exec"
	"os/user"
	"syscall"
	"time"
	"unicode/utf8"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

const randomStringLength = 24 // 144 bit base64 encoded

// Server is the type of object that is sent to the connected client.
// Represents a running shell process on the server.
type Server struct {
	Session          string
	remote           Remote
	pty              *pty.PTY
	currentSecond    int64
	messageCounter   int
	byteCounter      int
	lineFeeedCounter int
}

type Remote struct {
	Output       dnode.Function
	SessionEnded dnode.Function
}

func KillSession(r *kite.Request) (interface{}, error) {
	var params struct {
		Session string
	}

	if r.Args.One().Unmarshal(&params) != nil {
		return nil, errors.New("{ session: [string] }")
	}

	if params.Session == "" {
		return nil, errors.New("session is empty")
	}

	if err := killSession(params.Session); err != nil {
		return nil, err
	}

	return true, nil
}

func GetSessions(r *kite.Request) (interface{}, error) {
	user, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("Could not get home dir: %s", err)
	}

	sessions := screenSessions(user.Username)
	if len(sessions) == 0 {
		return nil, errors.New("no sessions available")
	}

	return sessions, nil
}

func Connect(r *kite.Request) (interface{}, error) {
	var params struct {
		Remote       Remote
		Session      string
		SizeX, SizeY int
		Mode         string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.SizeX <= 0 || params.SizeY <= 0 {
		return nil, errors.New("{ remote: [object], session: [string], sizeX: [integer], sizeY: [integer], noScreen: [boolean] }")
	}

	user, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("Could not get home dir: %s", err)
	}

	command, err := newCommand(params.Mode, params.Session, user.Username)
	if err != nil {
		return nil, err
	}

	// We will return this object to the client.
	server := &Server{
		Session: command.Session,
		remote:  params.Remote,
		pty:     pty.New("/dev/pts"),
	}
	server.setSize(float64(params.SizeX), float64(params.SizeY))

	cmd := exec.Command(command.Name, command.Args...)
	cmd.Env = []string{"TERM=xterm-256color", "HOME=" + user.HomeDir}
	cmd.Stdin = server.pty.Slave
	cmd.Stdout = server.pty.Slave
	// cmd.Stderr = server.pty.Slave

	// Open in background, this is needed otherwise the process will be killed
	// if you hit close on the client side.
	cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
	err = cmd.Start()
	if err != nil {
		fmt.Println("could not start", err)
	}

	// Wait until the shell process is closed and notify the client.
	go func() {
		err := cmd.Wait()
		if err != nil {
			fmt.Println("cmd.wait err", err)
		}

		server.pty.Slave.Close()
		server.pty.Master.Close()
		server.remote.SessionEnded.Call()
	}()

	// Read the STDOUT from shell process and send to the connected client.
	go func() {
		buf := make([]byte, (1<<12)-utf8.UTFMax, 1<<12)
		for {
			n, err := server.pty.Master.Read(buf)
			for n < cap(buf)-1 {
				r, _ := utf8.DecodeLastRune(buf[:n])
				if r != utf8.RuneError {
					break
				}
				server.pty.Master.Read(buf[n : n+1])
				n++
			}

			// Rate limiting...
			s := time.Now().Unix()
			if server.currentSecond != s {
				server.currentSecond = s
				server.messageCounter = 0
				server.byteCounter = 0
				server.lineFeeedCounter = 0
			}
			server.messageCounter += 1
			server.byteCounter += n
			server.lineFeeedCounter += bytes.Count(buf[:n], []byte{'\n'})
			if server.messageCounter > 100 || server.byteCounter > 1<<18 || server.lineFeeedCounter > 300 {
				time.Sleep(time.Second)
			}

			server.remote.Output.Call(string(filterInvalidUTF8(buf[:n])))
			if err != nil {
				break
			}
		}
	}()

	return server, nil
}

// Input is called when some text is written to the terminal.
func (s *Server) Input(d *dnode.Partial) {
	data := d.MustSliceOfLength(1)[0].MustString()

	// There is no need to protect the Write() with a mutex because
	// Kite Library guarantees that only one message is processed at a time.
	s.pty.Master.Write([]byte(data))
}

// ControlSequence is called when a non-printable key is pressed on the terminal.
func (s *Server) ControlSequence(d *dnode.Partial) {
	data := d.MustSliceOfLength(1)[0].MustString()
	s.pty.MasterEncoded.Write([]byte(data))
}

func (s *Server) SetSize(d *dnode.Partial) {
	args := d.MustSliceOfLength(2)
	x := args[0].MustFloat64()
	y := args[1].MustFloat64()
	s.setSize(x, y)
}

func (s *Server) setSize(x, y float64) {
	s.pty.SetSize(uint16(x), uint16(y))
}

func (s *Server) Close(d *dnode.Partial) {
	s.pty.Signal(syscall.SIGHUP)
}

func (s *Server) Terminate(d *dnode.Partial) {
	s.Close(nil)
}

func filterInvalidUTF8(buf []byte) []byte {
	i := 0
	j := 0
	for {
		r, l := utf8.DecodeRune(buf[i:])
		if l == 0 {
			break
		}
		if r < 0xD800 {
			if i != j {
				copy(buf[j:], buf[i:i+l])
			}
			j += l
		}
		i += l
	}
	return buf[:j]
}

func randomString() string {
	r := make([]byte, randomStringLength*6/8)
	rand.Read(r)
	return base64.URLEncoding.EncodeToString(r)
}
<commit_msg>kite-handler/terminal: make session lowercase<commit_after>// Package terminal provides a tty emulation and session handlings that is
// supported via Screen
package terminal

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"errors"
	"fmt"
	"koding/tools/pty"
	"os/exec"
	"os/user"
	"syscall"
	"time"
	"unicode/utf8"

	"github.com/koding/kite"
	"github.com/koding/kite/dnode"
)

const randomStringLength = 24 // 144 bit base64 encoded

// Server is the type of object that is sent to the connected client.
// Represents a running shell process on the server.
type Server struct {
	Session          string `json:"session"`
	remote           Remote
	pty              *pty.PTY
	currentSecond    int64
	messageCounter   int
	byteCounter      int
	lineFeeedCounter int
}

type Remote struct {
	Output       dnode.Function
	SessionEnded dnode.Function
}

func KillSession(r *kite.Request) (interface{}, error) {
	var params struct {
		Session string
	}

	if r.Args.One().Unmarshal(&params) != nil {
		return nil, errors.New("{ session: [string] }")
	}

	if params.Session == "" {
		return nil, errors.New("session is empty")
	}

	if err := killSession(params.Session); err != nil {
		return nil, err
	}

	return true, nil
}

func GetSessions(r *kite.Request) (interface{}, error) {
	user, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("Could not get home dir: %s", err)
	}

	sessions := screenSessions(user.Username)
	if len(sessions) == 0 {
		return nil, errors.New("no sessions available")
	}

	return sessions, nil
}

func Connect(r *kite.Request) (interface{}, error) {
	var params struct {
		Remote       Remote
		Session      string
		SizeX, SizeY int
		Mode         string
	}

	if r.Args.One().Unmarshal(&params) != nil || params.SizeX <= 0 || params.SizeY <= 0 {
		return nil, errors.New("{ remote: [object], session: [string], sizeX: [integer], sizeY: [integer], noScreen: [boolean] }")
	}

	user, err := user.Current()
	if err != nil {
		return nil, fmt.Errorf("Could not get home dir: %s", err)
	}

	command, err := newCommand(params.Mode, params.Session, user.Username)
	if err != nil {
		return nil, err
	}

	// We will return this object to the client.
	server := &Server{
		Session: command.Session,
		remote:  params.Remote,
		pty:     pty.New("/dev/pts"),
	}
	server.setSize(float64(params.SizeX), float64(params.SizeY))

	cmd := exec.Command(command.Name, command.Args...)
	cmd.Env = []string{"TERM=xterm-256color", "HOME=" + user.HomeDir}
	cmd.Stdin = server.pty.Slave
	cmd.Stdout = server.pty.Slave
	// cmd.Stderr = server.pty.Slave

	// Open in background, this is needed otherwise the process will be killed
	// if you hit close on the client side.
	cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true}
	err = cmd.Start()
	if err != nil {
		fmt.Println("could not start", err)
	}

	// Wait until the shell process is closed and notify the client.
	go func() {
		err := cmd.Wait()
		if err != nil {
			fmt.Println("cmd.wait err", err)
		}

		server.pty.Slave.Close()
		server.pty.Master.Close()
		server.remote.SessionEnded.Call()
	}()

	// Read the STDOUT from shell process and send to the connected client.
	go func() {
		buf := make([]byte, (1<<12)-utf8.UTFMax, 1<<12)
		for {
			n, err := server.pty.Master.Read(buf)
			for n < cap(buf)-1 {
				r, _ := utf8.DecodeLastRune(buf[:n])
				if r != utf8.RuneError {
					break
				}
				server.pty.Master.Read(buf[n : n+1])
				n++
			}

			// Rate limiting...
			s := time.Now().Unix()
			if server.currentSecond != s {
				server.currentSecond = s
				server.messageCounter = 0
				server.byteCounter = 0
				server.lineFeeedCounter = 0
			}
			server.messageCounter += 1
			server.byteCounter += n
			server.lineFeeedCounter += bytes.Count(buf[:n], []byte{'\n'})
			if server.messageCounter > 100 || server.byteCounter > 1<<18 || server.lineFeeedCounter > 300 {
				time.Sleep(time.Second)
			}

			server.remote.Output.Call(string(filterInvalidUTF8(buf[:n])))
			if err != nil {
				break
			}
		}
	}()

	return server, nil
}

// Input is called when some text is written to the terminal.
func (s *Server) Input(d *dnode.Partial) {
	data := d.MustSliceOfLength(1)[0].MustString()

	// There is no need to protect the Write() with a mutex because
	// Kite Library guarantees that only one message is processed at a time.
	s.pty.Master.Write([]byte(data))
}

// ControlSequence is called when a non-printable key is pressed on the terminal.
func (s *Server) ControlSequence(d *dnode.Partial) {
	data := d.MustSliceOfLength(1)[0].MustString()
	s.pty.MasterEncoded.Write([]byte(data))
}

func (s *Server) SetSize(d *dnode.Partial) {
	args := d.MustSliceOfLength(2)
	x := args[0].MustFloat64()
	y := args[1].MustFloat64()
	s.setSize(x, y)
}

func (s *Server) setSize(x, y float64) {
	s.pty.SetSize(uint16(x), uint16(y))
}

func (s *Server) Close(d *dnode.Partial) {
	s.pty.Signal(syscall.SIGHUP)
}

func (s *Server) Terminate(d *dnode.Partial) {
	s.Close(nil)
}

func filterInvalidUTF8(buf []byte) []byte {
	i := 0
	j := 0
	for {
		r, l := utf8.DecodeRune(buf[i:])
		if l == 0 {
			break
		}
		if r < 0xD800 {
			if i != j {
				copy(buf[j:], buf[i:i+l])
			}
			j += l
		}
		i += l
	}
	return buf[:j]
}

func randomString() string {
	r := make([]byte, randomStringLength*6/8)
	rand.Read(r)
	return base64.URLEncoding.EncodeToString(r)
}
<|endoftext|>


<commit_before>package terraform

import (
	"github.com/hashicorp/go-version"
)

// The main version number that is being run at the moment.
const Version = "0.7.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "dev"

// SemVersion is an instance of version.Version. This has the secondary
// benefit of verifying during tests and init time that our version is a
// proper semantic version, which should always be the case.
var SemVersion = version.Must(version.NewVersion(Version))
<commit_msg>v0.7.0-rc1<commit_after>package terraform

import (
	"github.com/hashicorp/go-version"
)

// The main version number that is being run at the moment.
const Version = "0.7.0"

// A pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc.
const VersionPrerelease = "rc1"

// SemVersion is an instance of version.Version. This has the secondary
// benefit of verifying during tests and init time that our version is a
// proper semantic version, which should always be the case.
var SemVersion = version.Must(version.NewVersion(Version))
<|endoftext|>