<commit_before>package socket

import (
	"bytes"
	"fmt"
	"net"
	"strconv"
	"sync"
	"time"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/hashicorp/vault/audit"
	"github.com/hashicorp/vault/helper/duration"
	"github.com/hashicorp/vault/logical"
)

func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
	if conf.Salt == nil {
		return nil, fmt.Errorf("nil salt passed in")
	}

	address, ok := conf.Config["address"]
	if !ok {
		return nil, fmt.Errorf("address is required")
	}

	socketType, ok := conf.Config["socket_type"]
	if !ok {
		socketType = "tcp"
	}

	writeDeadline, ok := conf.Config["write_timeout"]
	if !ok {
		writeDeadline = "2s"
	}
	writeDuration, err := duration.ParseDurationSecond(writeDeadline)
	if err != nil {
		return nil, err
	}

	format, ok := conf.Config["format"]
	if !ok {
		format = "json"
	}
	switch format {
	case "json", "jsonx":
	default:
		return nil, fmt.Errorf("unknown format type %s", format)
	}

	// Check if hashing of accessor is disabled
	hmacAccessor := true
	if hmacAccessorRaw, ok := conf.Config["hmac_accessor"]; ok {
		value, err := strconv.ParseBool(hmacAccessorRaw)
		if err != nil {
			return nil, err
		}
		hmacAccessor = value
	}

	// Check if raw logging is enabled
	logRaw := false
	if raw, ok := conf.Config["log_raw"]; ok {
		b, err := strconv.ParseBool(raw)
		if err != nil {
			return nil, err
		}
		logRaw = b
	}

	conn, err := net.Dial(socketType, address)
	if err != nil {
		return nil, err
	}

	b := &Backend{
		connection: conn,
		formatConfig: audit.FormatterConfig{
			Raw:          logRaw,
			Salt:         conf.Salt,
			HMACAccessor: hmacAccessor,
		},
		writeDuration: writeDuration,
		address:       address,
		socketType:    socketType,
	}

	switch format {
	case "json":
		b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
	case "jsonx":
		b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
	}

	return b, nil
}

// Backend is the audit backend for the socket audit transport.
type Backend struct {
	connection net.Conn

	formatter    audit.AuditFormatter
	formatConfig audit.FormatterConfig

	writeDuration time.Duration
	address       string
	socketType    string

	sync.Mutex
}

func (b *Backend) GetHash(data string) string {
	return audit.HashString(b.formatConfig.Salt, data)
}

func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr error) error {
	var buf bytes.Buffer
	if err := b.formatter.FormatRequest(&buf, b.formatConfig, auth, req, outerErr); err != nil {
		return err
	}

	b.Lock()
	defer b.Unlock()

	b.connection.SetDeadline(time.Now().Add(b.writeDuration))
	_, err := b.connection.Write(buf.Bytes())

	if err != nil {
		rErr := b.reconnect()
		if rErr != nil {
			err = multierror.Append(err, rErr)
		}
	}

	return err
}

func (b *Backend) LogResponse(auth *logical.Auth, req *logical.Request,
	resp *logical.Response, outerErr error) error {
	var buf bytes.Buffer
	if err := b.formatter.FormatResponse(&buf, b.formatConfig, auth, req, resp, outerErr); err != nil {
		return err
	}

	b.Lock()
	defer b.Unlock()

	b.connection.SetDeadline(time.Now().Add(b.writeDuration))
	_, err := b.connection.Write(buf.Bytes())

	if err != nil {
		rErr := b.reconnect()
		if rErr != nil {
			err = multierror.Append(err, rErr)
		}
	}

	return err
}

func (b *Backend) reconnect() error {
	conn, err := net.Dial(b.socketType, b.address)
	if err != nil {
		return err
	}

	b.connection.Close()
	b.connection = conn

	return nil
}

func (b *Backend) Reload() error {
	b.Lock()
	defer b.Unlock()

	err := b.reconnect()

	return err
}
<commit_msg>Added a single retry after a reconnection<commit_after>package socket

import (
	"bytes"
	"fmt"
	"net"
	"strconv"
	"sync"
	"time"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/hashicorp/vault/audit"
	"github.com/hashicorp/vault/helper/duration"
	"github.com/hashicorp/vault/logical"
)

func Factory(conf *audit.BackendConfig) (audit.Backend, error) {
	if conf.Salt == nil {
		return nil, fmt.Errorf("nil salt passed in")
	}

	address, ok := conf.Config["address"]
	if !ok {
		return nil, fmt.Errorf("address is required")
	}

	socketType, ok := conf.Config["socket_type"]
	if !ok {
		socketType = "tcp"
	}

	writeDeadline, ok := conf.Config["write_timeout"]
	if !ok {
		writeDeadline = "2s"
	}
	writeDuration, err := duration.ParseDurationSecond(writeDeadline)
	if err != nil {
		return nil, err
	}

	format, ok := conf.Config["format"]
	if !ok {
		format = "json"
	}
	switch format {
	case "json", "jsonx":
	default:
		return nil, fmt.Errorf("unknown format type %s", format)
	}

	// Check if hashing of accessor is disabled
	hmacAccessor := true
	if hmacAccessorRaw, ok := conf.Config["hmac_accessor"]; ok {
		value, err := strconv.ParseBool(hmacAccessorRaw)
		if err != nil {
			return nil, err
		}
		hmacAccessor = value
	}

	// Check if raw logging is enabled
	logRaw := false
	if raw, ok := conf.Config["log_raw"]; ok {
		b, err := strconv.ParseBool(raw)
		if err != nil {
			return nil, err
		}
		logRaw = b
	}

	conn, err := net.Dial(socketType, address)
	if err != nil {
		return nil, err
	}

	b := &Backend{
		connection: conn,
		formatConfig: audit.FormatterConfig{
			Raw:          logRaw,
			Salt:         conf.Salt,
			HMACAccessor: hmacAccessor,
		},
		writeDuration: writeDuration,
		address:       address,
		socketType:    socketType,
	}

	switch format {
	case "json":
		b.formatter.AuditFormatWriter = &audit.JSONFormatWriter{}
	case "jsonx":
		b.formatter.AuditFormatWriter = &audit.JSONxFormatWriter{}
	}

	return b, nil
}

// Backend is the audit backend for the socket audit transport.
type Backend struct {
	connection net.Conn

	formatter    audit.AuditFormatter
	formatConfig audit.FormatterConfig

	writeDuration time.Duration
	address       string
	socketType    string

	sync.Mutex
}

func (b *Backend) GetHash(data string) string {
	return audit.HashString(b.formatConfig.Salt, data)
}

func (b *Backend) LogRequest(auth *logical.Auth, req *logical.Request, outerErr error) error {
	var buf bytes.Buffer
	if err := b.formatter.FormatRequest(&buf, b.formatConfig, auth, req, outerErr); err != nil {
		return err
	}

	b.Lock()
	defer b.Unlock()

	err := b.write(buf.Bytes())
	if err != nil {
		rErr := b.reconnect()
		if rErr != nil {
			err = multierror.Append(err, rErr)
		} else {
			// Try once more after reconnecting
			err = b.write(buf.Bytes())
		}
	}

	return err
}

func (b *Backend) LogResponse(auth *logical.Auth, req *logical.Request,
	resp *logical.Response, outerErr error) error {
	var buf bytes.Buffer
	if err := b.formatter.FormatResponse(&buf, b.formatConfig, auth, req, resp, outerErr); err != nil {
		return err
	}

	b.Lock()
	defer b.Unlock()

	err := b.write(buf.Bytes())
	if err != nil {
		rErr := b.reconnect()
		if rErr != nil {
			err = multierror.Append(err, rErr)
		} else {
			// Try once more after reconnecting
			err = b.write(buf.Bytes())
		}
	}

	return err
}

func (b *Backend) write(buf []byte) error {
	err := b.connection.SetWriteDeadline(time.Now().Add(b.writeDuration))
	if err != nil {
		return err
	}

	_, err = b.connection.Write(buf)
	if err != nil {
		return err
	}

	return err
}

func (b *Backend) reconnect() error {
	conn, err := net.Dial(b.socketType, b.address)
	if err != nil {
		return err
	}

	b.connection.Close()
	b.connection = conn

	return nil
}

func (b *Backend) Reload() error {
	b.Lock()
	defer b.Unlock()

	err := b.reconnect()

	return err
}
<|endoftext|>


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

package audio

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"time"
)

// TODO: The term 'buffer' is confusing. Name each buffer with good terms.

// oneBufferSize returns the size of one buffer in the player implementation.
func oneBufferSize(sampleRate int) int {
	return sampleRate * channelNum * bitDepthInBytes / 4
}

// maxBufferSize returns the maximum size of the buffer for the audio source.
// This buffer is used when unreading on pausing the player.
func maxBufferSize(sampleRate int) int {
	// The number of underlying buffers should be 2.
	return oneBufferSize(sampleRate) * 2
}

// readerDriver represents a driver using io.ReadClosers.
type readerDriver interface {
	NewPlayer(io.Reader) readerDriverPlayer
	io.Closer
}

type readerDriverPlayer interface {
	Pause()
	Play()
	IsPlaying() bool
	Reset()
	Volume() float64
	SetVolume(volume float64)
	UnplayedBufferSize() int64
	io.Closer
}

type readerPlayerFactory struct {
	driver     readerDriver
	sampleRate int
}

var readerDriverForTesting readerDriver

func newReaderPlayerFactory(sampleRate int) *readerPlayerFactory {
	f := &readerPlayerFactory{
		sampleRate: sampleRate,
	}
	if readerDriverForTesting != nil {
		f.driver = readerDriverForTesting
	}
	// TODO: Consider the hooks.
	return f
}

type readerPlayer struct {
	context *Context
	player  readerDriverPlayer
	stream  *timeStream
	factory *readerPlayerFactory
	m       sync.Mutex
}

func (f *readerPlayerFactory) newPlayerImpl(context *Context, src io.Reader) (playerImpl, error) {
	sampleRate := context.SampleRate()
	s, err := newTimeStream(src, sampleRate)
	if err != nil {
		return nil, err
	}

	p := &readerPlayer{
		context: context,
		stream:  s,
		factory: f,
	}
	runtime.SetFinalizer(p, (*readerPlayer).Close)
	return p, nil
}

func (p *readerPlayer) ensurePlayer() error {
	// Initialize the underlying player lazily to enable calling NewContext in an 'init' function.
	// Accessing the underlying player functions requires the environment to be already initialized,
	// but if Ebiten is used for a shared library, the timing when init functions are called
	// is unexpectable.
	// e.g. a variable for JVM on Android might not be set.
	if p.factory.driver == nil {
		d, err := newReaderDriverImpl(p.context)
		if err != nil {
			return err
		}
		p.factory.driver = d
	}
	if p.player == nil {
		p.player = p.factory.driver.NewPlayer(p.stream)
	}
	return nil
}

func (p *readerPlayer) Play() {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return
	}

	p.player.Play()
	p.context.addPlayer(p)
}

func (p *readerPlayer) Pause() {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return
	}

	n := p.player.UnplayedBufferSize()
	p.player.Pause()
	p.stream.Unread(int(n))
	p.context.removePlayer(p)
}

func (p *readerPlayer) IsPlaying() bool {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return false
	}

	return p.player.IsPlaying()
}

func (p *readerPlayer) Volume() float64 {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return 0
	}

	return p.player.Volume()
}

func (p *readerPlayer) SetVolume(volume float64) {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return
	}

	p.player.SetVolume(volume)
}

func (p *readerPlayer) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	runtime.SetFinalizer(p, nil)

	if p.player != nil {
		defer func() {
			p.player = nil
		}()
		p.player.Pause()
		return p.player.Close()
	}
	return nil
}

func (p *readerPlayer) Current() time.Duration {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return 0
	}

	sample := (p.stream.Current() - p.player.UnplayedBufferSize()) / bytesPerSample
	return time.Duration(sample) * time.Second / time.Duration(p.factory.sampleRate)
}

func (p *readerPlayer) Rewind() error {
	return p.Seek(0)
}

func (p *readerPlayer) Seek(offset time.Duration) error {
	p.m.Lock()
	defer p.m.Unlock()

	if p.player != nil {
		if p.player.IsPlaying() {
			defer func() {
				p.player.Play()
			}()
		}
		p.player.Reset()
	}
	return p.stream.Seek(offset)
}

func (p *readerPlayer) source() io.Reader {
	return p.stream.r
}

type timeStream struct {
	r          io.Reader
	sampleRate int
	pos        int64
	buf        []byte
	unread     int
}

func newTimeStream(r io.Reader, sampleRate int) (*timeStream, error) {
	s := &timeStream{
		r:          r,
		sampleRate: sampleRate,
	}
	if seeker, ok := s.r.(io.Seeker); ok {
		// Get the current position of the source.
		pos, err := seeker.Seek(0, io.SeekCurrent)
		if err != nil {
			return nil, err
		}
		s.pos = pos
	}
	return s, nil
}

func (s *timeStream) Unread(n int) {
	if s.unread+n > len(s.buf) {
		panic(fmt.Sprintf("audio: too much unreading: %d, the buffer size: %d, unreading position: %d", n, len(s.buf), s.unread))
	}
	s.unread += n
	s.pos -= int64(n)
}

func (s *timeStream) Read(buf []byte) (int, error) {
	if s.unread > 0 {
		n := copy(buf, s.buf[len(s.buf)-s.unread:])
		s.unread -= n
		s.pos += int64(n)
		return n, nil
	}

	n, err := s.r.Read(buf)
	s.pos += int64(n)
	s.buf = append(s.buf, buf[:n]...)
	if m := maxBufferSize(s.sampleRate); len(s.buf) > m {
		s.buf = s.buf[len(s.buf)-m:]
	}
	return n, err
}

func (s *timeStream) Seek(offset time.Duration) error {
	o := int64(offset) * bytesPerSample * int64(s.sampleRate) / int64(time.Second)

	// Align the byte position with the samples.
	o -= o % bytesPerSample
	o += s.pos % bytesPerSample

	seeker, ok := s.r.(io.Seeker)
	if !ok {
		panic("audio: the source must be io.Seeker when seeking but not")
	}
	pos, err := seeker.Seek(o, io.SeekStart)
	if err != nil {
		return err
	}

	s.pos = pos
	s.buf = s.buf[:0]
	s.unread = 0
	return nil
}

func (s *timeStream) Current() int64 {
	return s.pos
}
<commit_msg>audio: Bug fix: Duplicated pausing at reader players<commit_after>// Copyright 2021 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package audio

import (
	"fmt"
	"io"
	"runtime"
	"sync"
	"time"
)

// TODO: The term 'buffer' is confusing. Name each buffer with good terms.

// oneBufferSize returns the size of one buffer in the player implementation.
func oneBufferSize(sampleRate int) int {
	return sampleRate * channelNum * bitDepthInBytes / 4
}

// maxBufferSize returns the maximum size of the buffer for the audio source.
// This buffer is used when unreading on pausing the player.
func maxBufferSize(sampleRate int) int {
	// The number of underlying buffers should be 2.
	return oneBufferSize(sampleRate) * 2
}

// readerDriver represents a driver using io.ReadClosers.
type readerDriver interface {
	NewPlayer(io.Reader) readerDriverPlayer
	io.Closer
}

type readerDriverPlayer interface {
	Pause()
	Play()
	IsPlaying() bool
	Reset()
	Volume() float64
	SetVolume(volume float64)
	UnplayedBufferSize() int64
	io.Closer
}

type readerPlayerFactory struct {
	driver     readerDriver
	sampleRate int
}

var readerDriverForTesting readerDriver

func newReaderPlayerFactory(sampleRate int) *readerPlayerFactory {
	f := &readerPlayerFactory{
		sampleRate: sampleRate,
	}
	if readerDriverForTesting != nil {
		f.driver = readerDriverForTesting
	}
	// TODO: Consider the hooks.
	return f
}

type readerPlayer struct {
	context *Context
	player  readerDriverPlayer
	stream  *timeStream
	factory *readerPlayerFactory
	m       sync.Mutex
}

func (f *readerPlayerFactory) newPlayerImpl(context *Context, src io.Reader) (playerImpl, error) {
	sampleRate := context.SampleRate()
	s, err := newTimeStream(src, sampleRate)
	if err != nil {
		return nil, err
	}

	p := &readerPlayer{
		context: context,
		stream:  s,
		factory: f,
	}
	runtime.SetFinalizer(p, (*readerPlayer).Close)
	return p, nil
}

func (p *readerPlayer) ensurePlayer() error {
	// Initialize the underlying player lazily to enable calling NewContext in an 'init' function.
	// Accessing the underlying player functions requires the environment to be already initialized,
	// but if Ebiten is used for a shared library, the timing when init functions are called
	// is unexpectable.
	// e.g. a variable for JVM on Android might not be set.
	if p.factory.driver == nil {
		d, err := newReaderDriverImpl(p.context)
		if err != nil {
			return err
		}
		p.factory.driver = d
	}
	if p.player == nil {
		p.player = p.factory.driver.NewPlayer(p.stream)
	}
	return nil
}

func (p *readerPlayer) Play() {
	p.m.Lock()
	defer p.m.Unlock()

	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return
	}
	if p.player.IsPlaying() {
		return
	}
	p.player.Play()
	p.context.addPlayer(p)
}

func (p *readerPlayer) Pause() {
	p.m.Lock()
	defer p.m.Unlock()

	if p.player == nil {
		return
	}
	if !p.player.IsPlaying() {
		return
	}

	n := p.player.UnplayedBufferSize()
	p.player.Pause()
	p.stream.Unread(int(n))
	p.context.removePlayer(p)
}

func (p *readerPlayer) IsPlaying() bool {
	p.m.Lock()
	defer p.m.Unlock()

	if p.player == nil {
		return false
	}
	return p.player.IsPlaying()
}

func (p *readerPlayer) Volume() float64 {
	p.m.Lock()
	defer p.m.Unlock()

	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return 0
	}
	return p.player.Volume()
}

func (p *readerPlayer) SetVolume(volume float64) {
	p.m.Lock()
	defer p.m.Unlock()

	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return
	}
	p.player.SetVolume(volume)
}

func (p *readerPlayer) Close() error {
	p.m.Lock()
	defer p.m.Unlock()
	runtime.SetFinalizer(p, nil)

	if p.player != nil {
		defer func() {
			p.player = nil
		}()
		p.player.Pause()
		return p.player.Close()
	}
	return nil
}

func (p *readerPlayer) Current() time.Duration {
	p.m.Lock()
	defer p.m.Unlock()
	if err := p.ensurePlayer(); err != nil {
		p.context.setError(err)
		return 0
	}

	sample := (p.stream.Current() - p.player.UnplayedBufferSize()) / bytesPerSample
	return time.Duration(sample) * time.Second / time.Duration(p.factory.sampleRate)
}

func (p *readerPlayer) Rewind() error {
	return p.Seek(0)
}

func (p *readerPlayer) Seek(offset time.Duration) error {
	p.m.Lock()
	defer p.m.Unlock()

	if p.player != nil {
		if p.player.IsPlaying() {
			defer func() {
				p.player.Play()
			}()
		}
		p.player.Reset()
	}
	return p.stream.Seek(offset)
}

func (p *readerPlayer) source() io.Reader {
	return p.stream.r
}

type timeStream struct {
	r          io.Reader
	sampleRate int
	pos        int64
	buf        []byte
	unread     int
}

func newTimeStream(r io.Reader, sampleRate int) (*timeStream, error) {
	s := &timeStream{
		r:          r,
		sampleRate: sampleRate,
	}
	if seeker, ok := s.r.(io.Seeker); ok {
		// Get the current position of the source.
		pos, err := seeker.Seek(0, io.SeekCurrent)
		if err != nil {
			return nil, err
		}
		s.pos = pos
	}
	return s, nil
}

func (s *timeStream) Unread(n int) {
	if s.unread+n > len(s.buf) {
		panic(fmt.Sprintf("audio: too much unreading: %d, the buffer size: %d, unreading position: %d", n, len(s.buf), s.unread))
	}
	s.unread += n
	s.pos -= int64(n)
}

func (s *timeStream) Read(buf []byte) (int, error) {
	if s.unread > 0 {
		n := copy(buf, s.buf[len(s.buf)-s.unread:])
		s.unread -= n
		s.pos += int64(n)
		return n, nil
	}

	n, err := s.r.Read(buf)
	s.pos += int64(n)
	s.buf = append(s.buf, buf[:n]...)
	if m := maxBufferSize(s.sampleRate); len(s.buf) > m {
		s.buf = s.buf[len(s.buf)-m:]
	}
	return n, err
}

func (s *timeStream) Seek(offset time.Duration) error {
	o := int64(offset) * bytesPerSample * int64(s.sampleRate) / int64(time.Second)

	// Align the byte position with the samples.
	o -= o % bytesPerSample
	o += s.pos % bytesPerSample

	seeker, ok := s.r.(io.Seeker)
	if !ok {
		panic("audio: the source must be io.Seeker when seeking but not")
	}
	pos, err := seeker.Seek(o, io.SeekStart)
	if err != nil {
		return err
	}

	s.pos = pos
	s.buf = s.buf[:0]
	s.unread = 0
	return nil
}

func (s *timeStream) Current() int64 {
	return s.pos
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"github.com/mdlayher/wavepipe/data"
)

var (
	// ErrCannotSeek is returned when the input stream is not seekable
	ErrCannotSeek = errors.New("httpStream: cannot seek input stream")
	// ErrInvalidRange is returned when the client attempts to retrieve an out-of-bounds part of the stream
	ErrInvalidRange = errors.New("httpStream: invalid range")
)

// HTTPStream provides a common method to transfer a file stream using a HTTP response writer
func HTTPStream(song *data.Song, mimeType string, contentLength int64, inputStream io.Reader, req *http.Request, res http.ResponseWriter) error {
	// Total bytes transferred
	var total int64

	// Output data stream, which uses the input stream by default
	stream := inputStream

	// Check for a Range header with bytes request, meaning the client is seeking through the stream
	// If client requests the entire stream (browsers, "bytes=0-"), skip range logic
	rawRange := req.Header.Get("Range")
	if rawRange != "" && rawRange != "bytes=0-" && strings.HasPrefix(rawRange, "bytes=") {
		// Check if input stream is seekable
		seekStream, ok := inputStream.(io.ReadSeeker)
		if !ok || contentLength < 0 {
			return ErrCannotSeek
		}

		// Attempt to parse byte range
		pair := strings.Split(rawRange[6:], "-")

		// Parse first element as the starting point
		startOffset, err := strconv.ParseInt(pair[0], 10, 64)
		if err != nil {
			return err
		}

		// Parse second element as the ending point, if available
		var endOffset int64
		if pair[1] != "" {
			tempEndOffset, err := strconv.ParseInt(pair[1], 10, 64)
			if err != nil {
				return err
			}

			endOffset = tempEndOffset
		}

		// Check for invalid boundaries for seeking
		if startOffset > contentLength || endOffset > contentLength {
			return ErrInvalidRange
		}

		// Seek the file stream to the starting offset
		if _, err := seekStream.Seek(startOffset, 0); err != nil {
			return err
		}

		// Recalculate content length to account for start offset
		contentLength = song.FileSize - startOffset

		// By default, use the length of the file (minus 1 byte) as the ending offset
		rangeEnd := contentLength - 1

		// If an ending offset was set, recalculate values, and limit the stream to return up to that point
		if endOffset > 0 {
			// Recalculate range ending offset using the specified end point
			rangeEnd = endOffset

			// Recalculate content length
			contentLength = endOffset - startOffset

			// Wrap the stream to return only contentLength bytes
			stream = io.LimitReader(seekStream.(io.Reader), contentLength)
		}

		// Respond with HTTP 206 Partial Content, and Content-Range header indicating the stream offset
		res.WriteHeader(206)
		res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startOffset, rangeEnd, contentLength))
	}

	// Track the stream's progress via log
	stopProgressChan := make(chan struct{})
	go func() {
		// Track start time
		startTime := time.Now()

		// Print progress every 5 seconds
		progress := time.NewTicker(5 * time.Second)

		// Calculate total file length
		totalSize := float64(contentLength) / 1024 / 1024
		for {
			select {
			// Print progress
			case <-progress.C:
				// Capture current progress
				currTotal := atomic.LoadInt64(&total)
				current := float64(currTotal) / 1024 / 1024

				// Capture current transfer rate
				rate := float64(float64((currTotal*8)/1024/1024) / float64(time.Now().Sub(startTime).Seconds()))

				// If size available, we can print percentage and file sizes
				if contentLength > 0 {
					// Capture current percentage
					percent := int64(float64(float64(currTotal)/float64(contentLength)) * 100)

					log.Printf("[%d] [%03d%%] %02.3f / %02.3f MB [%02.3f Mbps]", song.ID, percent, current, totalSize, rate)
					break
				}

				// Else, print the current transfer size and rate
				log.Printf("[%d] sent: %02.3f MB [%02.3f Mbps]", song.ID, current, rate)
			// Stop printing
			case <-stopProgressChan:
				return
			}
		}
	}()

	// Stop progress on return
	defer func() {
		close(stopProgressChan)
	}()

	// Buffer to store and transfer file bytes
	buf := make([]byte, 8192)

	// Indicate when stream is complete
	streamComplete := false

	// Set necessary output HTTP headers

	// Set Content-Length if set
	// NOTE: HTTP standards specify that this must be an exact length, so we cannot estimate it for
	// transcodes unless the entire file is transcoded and then sent
	if contentLength > 0 {
		res.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
	}

	// Override Content-Type if set
	contentType := mime.TypeByExtension(path.Ext(song.FileName))
	if mimeType != "" {
		contentType = mimeType
	}
	res.Header().Set("Content-Type", contentType)

	// Get song modify time in RFC1123 format, replace UTC with GMT
	lastMod := strings.Replace(time.Unix(song.LastModified, 0).UTC().Format(time.RFC1123), "UTC", "GMT", 1)

	// Set Last-Modified using filesystem modify time
	res.Header().Set("Last-Modified", lastMod)

	// Specify connection close on send
	res.Header().Set("Connection", "close")

	// Begin transferring the data stream
	for {
		// Read in a buffer from the file
		n, err := stream.Read(buf)
		if err != nil && err != io.EOF {
			return err
		} else if err == io.EOF {
			// Halt streaming after next write
			streamComplete = true
		}

		// Count bytes
		atomic.AddInt64(&total, int64(n))

		// Write bytes over HTTP
		if _, err := res.Write(buf[:n]); err != nil && err != io.EOF {
			return err
		}

		// If stream is complete, break loop
		if streamComplete {
			break
		}
	}

	// No errors
	return nil
}
<commit_msg>api/httpStream: remove unneeded deferred closure<commit_after>package api

import (
	"errors"
	"fmt"
	"io"
	"log"
	"mime"
	"net/http"
	"path"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"github.com/mdlayher/wavepipe/data"
)

var (
	// ErrCannotSeek is returned when the input stream is not seekable
	ErrCannotSeek = errors.New("httpStream: cannot seek input stream")
	// ErrInvalidRange is returned when the client attempts to retrieve an out-of-bounds part of the stream
	ErrInvalidRange = errors.New("httpStream: invalid range")
)

// HTTPStream provides a common method to transfer a file stream using a HTTP response writer
func HTTPStream(song *data.Song, mimeType string, contentLength int64, inputStream io.Reader, req *http.Request, res http.ResponseWriter) error {
	// Total bytes transferred
	var total int64

	// Output data stream, which uses the input stream by default
	stream := inputStream

	// Check for a Range header with bytes request, meaning the client is seeking through the stream
	// If client requests the entire stream (browsers, "bytes=0-"), skip range logic
	rawRange := req.Header.Get("Range")
	if rawRange != "" && rawRange != "bytes=0-" && strings.HasPrefix(rawRange, "bytes=") {
		// Check if input stream is seekable
		seekStream, ok := inputStream.(io.ReadSeeker)
		if !ok || contentLength < 0 {
			return ErrCannotSeek
		}

		// Attempt to parse byte range
		pair := strings.Split(rawRange[6:], "-")

		// Parse first element as the starting point
		startOffset, err := strconv.ParseInt(pair[0], 10, 64)
		if err != nil {
			return err
		}

		// Parse second element as the ending point, if available
		var endOffset int64
		if pair[1] != "" {
			tempEndOffset, err := strconv.ParseInt(pair[1], 10, 64)
			if err != nil {
				return err
			}

			endOffset = tempEndOffset
		}

		// Check for invalid boundaries for seeking
		if startOffset > contentLength || endOffset > contentLength {
			return ErrInvalidRange
		}

		// Seek the file stream to the starting offset
		if _, err := seekStream.Seek(startOffset, 0); err != nil {
			return err
		}

		// Recalculate content length to account for start offset
		contentLength = song.FileSize - startOffset

		// By default, use the length of the file (minus 1 byte) as the ending offset
		rangeEnd := contentLength - 1

		// If an ending offset was set, recalculate values, and limit the stream to return up to that point
		if endOffset > 0 {
			// Recalculate range ending offset using the specified end point
			rangeEnd = endOffset

			// Recalculate content length
			contentLength = endOffset - startOffset

			// Wrap the stream to return only contentLength bytes
			stream = io.LimitReader(seekStream.(io.Reader), contentLength)
		}

		// Respond with HTTP 206 Partial Content, and Content-Range header indicating the stream offset
		res.WriteHeader(206)
		res.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startOffset, rangeEnd, contentLength))
	}

	// Track the stream's progress via log
	stopProgressChan := make(chan struct{})
	go func() {
		// Track start time
		startTime := time.Now()

		// Print progress every 5 seconds
		progress := time.NewTicker(5 * time.Second)

		// Calculate total file length
		totalSize := float64(contentLength) / 1024 / 1024
		for {
			select {
			// Print progress
			case <-progress.C:
				// Capture current progress
				currTotal := atomic.LoadInt64(&total)
				current := float64(currTotal) / 1024 / 1024

				// Capture current transfer rate
				rate := float64(float64((currTotal*8)/1024/1024) / float64(time.Now().Sub(startTime).Seconds()))

				// If size available, we can print percentage and file sizes
				if contentLength > 0 {
					// Capture current percentage
					percent := int64(float64(float64(currTotal)/float64(contentLength)) * 100)

					log.Printf("[%d] [%03d%%] %02.3f / %02.3f MB [%02.3f Mbps]", song.ID, percent, current, totalSize, rate)
					break
				}

				// Else, print the current transfer size and rate
				log.Printf("[%d] sent: %02.3f MB [%02.3f Mbps]", song.ID, current, rate)
			// Stop printing
			case <-stopProgressChan:
				return
			}
		}
	}()

	// Stop progress on return
	defer close(stopProgressChan)

	// Buffer to store and transfer file bytes
	buf := make([]byte, 8192)

	// Indicate when stream is complete
	streamComplete := false

	// Set necessary output HTTP headers

	// Set Content-Length if set
	// NOTE: HTTP standards specify that this must be an exact length, so we cannot estimate it for
	// transcodes unless the entire file is transcoded and then sent
	if contentLength > 0 {
		res.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10))
	}

	// Override Content-Type if set
	contentType := mime.TypeByExtension(path.Ext(song.FileName))
	if mimeType != "" {
		contentType = mimeType
	}
	res.Header().Set("Content-Type", contentType)

	// Get song modify time in RFC1123 format, replace UTC with GMT
	lastMod := strings.Replace(time.Unix(song.LastModified, 0).UTC().Format(time.RFC1123), "UTC", "GMT", 1)

	// Set Last-Modified using filesystem modify time
	res.Header().Set("Last-Modified", lastMod)

	// Specify connection close on send
	res.Header().Set("Connection", "close")

	// Begin transferring the data stream
	for {
		// Read in a buffer from the file
		n, err := stream.Read(buf)
		if err != nil && err != io.EOF {
			return err
		} else if err == io.EOF {
			// Halt streaming after next write
			streamComplete = true
		}

		// Count bytes
		atomic.AddInt64(&total, int64(n))

		// Write bytes over HTTP
		if _, err := res.Write(buf[:n]); err != nil && err != io.EOF {
			return err
		}

		// If stream is complete, break loop
		if streamComplete {
			break
		}
	}

	// No errors
	return nil
}
<|endoftext|>


<commit_before>package api

import (
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"
)

type logRequest struct {
	channel          string
	user             string
	channelid        string
	userid           string
	time             logTime
	reverse          bool
	responseType     string
	redirectPath     string
	isUserRequest    bool
	isChannelRequest bool
}

// userRandomMessageRequest /channel/pajlada/user/gempir/random

type logTime struct {
	from   string
	to     string
	year   string
	month  string
	day    string
	random bool
}

var (
	pathRegex = regexp.MustCompile(`\/(channel|channelid)\/(\w+)(?:\/(user|userid)\/(\w+))?(?:(?:\/(\d{4})\/(\d{1,2})(?:\/(\d{1,2}))?)|(?:\/(range|random)))?`)
)

func (s *Server) newLogRequestFromURL(r *http.Request) (logRequest, error) {
	path := r.URL.EscapedPath()

	if !strings.HasPrefix(path, "/channel") && !strings.HasPrefix(path, "/channelid") {
		return logRequest{}, errors.New("route not found")
	}

	url := strings.TrimRight(path, "/")

	matches := pathRegex.FindAllStringSubmatch(url, -1)
	if len(matches) == 0 || len(matches[0]) < 5 {
		return logRequest{}, errors.New("route not found")
	}

	request := logRequest{
		time: logTime{},
	}

	params := []string{}
	for _, match := range matches[0] {
		if match != "" {
			params = append(params, match)
		}
	}

	request.isUserRequest = len(params) > 4 && (params[3] == "user" || params[3] == "userid")
	request.isChannelRequest = len(params) < 4 || (len(params) >= 4 && params[3] != "user" && params[3] != "userid")

	if params[1] == "channel" {
		request.channel = params[2]
	}
	if params[1] == "channelid" {
		request.channelid = params[2]
	}
	if request.isUserRequest && params[3] == "user" {
		request.user = params[4]
	}
	if request.isUserRequest && params[3] == "userid" {
		request.userid = params[4]
	}

	var err error
	request, err = s.fillIds(request)
	if err != nil {
		log.Error(err)
		return logRequest{}, nil
	}

	if request.isUserRequest && len(params) == 7 {
		request.time.year = params[5]
		request.time.month = params[6]
	} else if request.isChannelRequest && len(params) == 6 {
		request.time.year = params[3]
		request.time.month = params[4]
		request.time.day = params[5]
	} else if request.isUserRequest && len(params) == 6 && params[5] == "random" {
		request.time.random = true
	} else if (request.isUserRequest && len(params) == 6 && params[5] == "range") || (request.isChannelRequest && len(params) == 4 && params[3] == "range") {
		request.time.from = r.URL.Query().Get("from")
		request.time.to = r.URL.Query().Get("to")
	} else {
		if request.isChannelRequest {
			request.time.year = fmt.Sprintf("%d", time.Now().Year())
			request.time.month = fmt.Sprintf("%d", time.Now().Month())
		} else {
			year, month, err := s.fileLogger.GetLastLogYearAndMonthForUser(request.channelid, request.userid)
			if err == nil {
				request.time.year = fmt.Sprintf("%d", year)
				request.time.month = fmt.Sprintf("%d", month)
			} else {
				request.time.year = fmt.Sprintf("%d", time.Now().Year())
				request.time.month = fmt.Sprintf("%d", time.Now().Month())
			}
		}

		timePath := request.time.year + "/" + request.time.month

		if request.isChannelRequest {
			request.time.day = fmt.Sprintf("%d", time.Now().Day())
			timePath += "/" + request.time.day
		}

		query := r.URL.Query()
		query.Del("from")
		query.Del("to")

		encodedQuery := ""
		if query.Encode() != "" {
			encodedQuery = "?" + query.Encode()
		}

		return logRequest{redirectPath: fmt.Sprintf("%s/%s%s", params[0], timePath, encodedQuery)}, nil
	}

	if _, ok := r.URL.Query()["reverse"]; ok {
		request.reverse = true
	} else {
		request.reverse = false
	}

	if _, ok := r.URL.Query()["json"]; ok || r.URL.Query().Get("type") == "json" || r.Header.Get("Content-Type") == "application/json" {
		request.responseType = responseTypeJSON
	} else if _, ok := r.URL.Query()["raw"]; ok || r.URL.Query().Get("type") == "raw" {
		request.responseType = responseTypeRaw
	} else {
		request.responseType = responseTypeText
	}

	return request, nil
}

func (s *Server) fillIds(request logRequest) (logRequest, error) {
	usernames := []string{}
	if request.channelid == "" && request.channel != "" {
		usernames = append(usernames, request.channel)
	}
	if request.userid == "" && request.user != "" {
		usernames = append(usernames, request.user)
	}

	ids, err := s.helixClient.GetUsersByUsernames(usernames)
	if err != nil {
		return request, err
	}

	if request.channelid == "" {
		request.channelid = ids[strings.ToLower(request.channel)].ID
	}
	if request.userid == "" {
		request.userid = ids[strings.ToLower(request.user)].ID
	}

	return request, nil
}
<commit_msg>support opening daily logs of users<commit_after>package api

import (
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"strings"
	"time"

	log "github.com/sirupsen/logrus"
)

type logRequest struct {
	channel          string
	user             string
	channelid        string
	userid           string
	time             logTime
	reverse          bool
	responseType     string
	redirectPath     string
	isUserRequest    bool
	isChannelRequest bool
}

// userRandomMessageRequest /channel/pajlada/user/gempir/random

type logTime struct {
	from   string
	to     string
	year   string
	month  string
	day    string
	random bool
}

var (
	pathRegex = regexp.MustCompile(`\/(channel|channelid)\/(\w+)(?:\/(user|userid)\/(\w+))?(?:(?:\/(\d{4})\/(\d{1,2})(?:\/(\d{1,2}))?)|(?:\/(range|random)))?`)
)

func (s *Server) newLogRequestFromURL(r *http.Request) (logRequest, error) {
	path := r.URL.EscapedPath()

	if !strings.HasPrefix(path, "/channel") && !strings.HasPrefix(path, "/channelid") {
		return logRequest{}, errors.New("route not found")
	}

	url := strings.TrimRight(path, "/")

	matches := pathRegex.FindAllStringSubmatch(url, -1)
	if len(matches) == 0 || len(matches[0]) < 5 {
		return logRequest{}, errors.New("route not found")
	}

	request := logRequest{
		time: logTime{},
	}

	params := []string{}
	for _, match := range matches[0] {
		if match != "" {
			params = append(params, match)
		}
	}

	request.isUserRequest = len(params) > 4 && (params[3] == "user" || params[3] == "userid")
	request.isChannelRequest = len(params) < 4 || (len(params) >= 4 && params[3] != "user" && params[3] != "userid")

	if params[1] == "channel" {
		request.channel = params[2]
	}
	if params[1] == "channelid" {
		request.channelid = params[2]
	}
	if request.isUserRequest && params[3] == "user" {
		request.user = params[4]
	}
	if request.isUserRequest && params[3] == "userid" {
		request.userid = params[4]
	}

	var err error
	request, err = s.fillIds(request)
	if err != nil {
		log.Error(err)
		return logRequest{}, nil
	}

	if request.isUserRequest && len(params) == 7 {
		request.time.year = params[5]
		request.time.month = params[6]
	} else if request.isUserRequest && len(params) == 8 {
		request.time.year = params[5]
		request.time.month = params[6]
		request.time.day = params[7]
	} else if request.isChannelRequest && len(params) == 6 {
		request.time.year = params[3]
		request.time.month = params[4]
		request.time.day = params[5]
	} else if request.isUserRequest && len(params) == 6 && params[5] == "random" {
		request.time.random = true
	} else if (request.isUserRequest && len(params) == 6 && params[5] == "range") || (request.isChannelRequest && len(params) == 4 && params[3] == "range") {
		request.time.from = r.URL.Query().Get("from")
		request.time.to = r.URL.Query().Get("to")
	} else {
		if request.isChannelRequest {
			request.time.year = fmt.Sprintf("%d", time.Now().Year())
			request.time.month = fmt.Sprintf("%d", time.Now().Month())
		} else {
			year, month, err := s.fileLogger.GetLastLogYearAndMonthForUser(request.channelid, request.userid)
			if err == nil {
				request.time.year = fmt.Sprintf("%d", year)
				request.time.month = fmt.Sprintf("%d", month)
			} else {
				request.time.year = fmt.Sprintf("%d", time.Now().Year())
				request.time.month = fmt.Sprintf("%d", time.Now().Month())
			}
		}

		timePath := request.time.year + "/" + request.time.month

		if request.isChannelRequest {
			request.time.day = fmt.Sprintf("%d", time.Now().Day())
			timePath += "/" + request.time.day
		}

		query := r.URL.Query()
		query.Del("from")
		query.Del("to")

		encodedQuery := ""
		if query.Encode() != "" {
			encodedQuery = "?" + query.Encode()
		}

		return logRequest{redirectPath: fmt.Sprintf("%s/%s%s", params[0], timePath, encodedQuery)}, nil
	}

	if _, ok := r.URL.Query()["reverse"]; ok {
		request.reverse = true
	} else {
		request.reverse = false
	}

	if _, ok := r.URL.Query()["json"]; ok || r.URL.Query().Get("type") == "json" || r.Header.Get("Content-Type") == "application/json" {
		request.responseType = responseTypeJSON
	} else if _, ok := r.URL.Query()["raw"]; ok || r.URL.Query().Get("type") == "raw" {
		request.responseType = responseTypeRaw
	} else {
		request.responseType = responseTypeText
	}

	return request, nil
}

func (s *Server) fillIds(request logRequest) (logRequest, error) {
	usernames := []string{}
	if request.channelid == "" && request.channel != "" {
		usernames = append(usernames, request.channel)
	}
	if request.userid == "" && request.user != "" {
		usernames = append(usernames, request.user)
	}

	ids, err := s.helixClient.GetUsersByUsernames(usernames)
	if err != nil {
		return request, err
	}

	if request.channelid == "" {
		request.channelid = ids[strings.ToLower(request.channel)].ID
	}
	if request.userid == "" {
		request.userid = ids[strings.ToLower(request.user)].ID
	}

	return request, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

package api

import (
	"github.com/mattermost/platform/model"
	"github.com/mattermost/platform/utils"
	"net/url"
	"testing"
)

func TestRegisterApp(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.SystemAdminClient

	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.RegisterApp(app); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	Client.Logout()

	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("not logged in - should have failed")
	}

	th.LoginSystemAdmin()

	if result, err := Client.RegisterApp(app); err != nil {
		t.Fatal(err)
	} else {
		rapp := result.Data.(*model.OAuthApp)
		if len(rapp.Id) != 26 {
			t.Fatal("clientid didn't return properly")
		}
		if len(rapp.ClientSecret) != 26 {
			t.Fatal("client secret didn't return properly")
		}
	}

	app = &model.OAuthApp{Name: "", Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing name - should have failed")
	}

	app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing homepage - should have failed")
	}

	app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing callback url - should have failed")
	}
}

func TestAllowOAuth(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = AdminClient.Must(AdminClient.RegisterApp(app)).Data.(*model.OAuthApp)

	state := "123"

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - oauth providing turned off")
	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	if result, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", state); err != nil {
		t.Fatal(err)
	} else {
		redirect := result.Data.(map[string]string)["redirect"]
		if len(redirect) == 0 {
			t.Fatal("redirect url should be set")
		}

		ru, _ := url.Parse(redirect)
		if ru == nil {
			t.Fatal("redirect url unparseable")
		} else {
			if len(ru.Query().Get("code")) == 0 {
				t.Fatal("authorization code not returned")
			}
			if ru.Query().Get("state") != state {
				t.Fatal("returned state doesn't match")
			}
		}
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "all", state); err == nil {
		t.Fatal("should have failed - no redirect_url given")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "", state); err == nil {
		t.Fatal("should have failed - no redirect_url given")
	}

	if result, err := Client.AllowOAuth("junk", app.Id, app.CallbackUrls[0], "all", state); err != nil {
		t.Fatal(err)
	} else {
		redirect := result.Data.(map[string]string)["redirect"]
		if len(redirect) == 0 {
			t.Fatal("redirect url should be set")
		}

		ru, _ := url.Parse(redirect)
		if ru == nil {
			t.Fatal("redirect url unparseable")
		} else {
			if ru.Query().Get("error") != "unsupported_response_type" {
				t.Fatal("wrong error returned")
			}
			if ru.Query().Get("state") != state {
				t.Fatal("returned state doesn't match")
			}
		}
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "", app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - empty client id")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "junk", app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - bad client id")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "https://somewhereelse.com", "all", state); err == nil {
		t.Fatal("should have failed - redirect uri host does not match app host")
	}
}

func TestGetOAuthAppsByUser(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.GetOAuthAppsByUser(); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	if _, err := Client.GetOAuthAppsByUser(); err == nil {
		t.Fatal("Should have failed. only admin is permitted")
	}

	*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false

	if result, err := Client.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) != 0 {
			t.Fatal("incorrect number of apps should have been 0")
		}
	}

	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if result, err := Client.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) != 1 {
			t.Fatal("incorrect number of apps should have been 1")
		}
	}

	app = &model.OAuthApp{Name: "TestApp4" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = AdminClient.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if result, err := AdminClient.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) != 4 {
			t.Fatal("incorrect number of apps should have been 4")
		}
	}
}

func TestGetOAuthAppInfo(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.GetOAuthAppInfo("fakeId"); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	app := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = AdminClient.Must(AdminClient.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := Client.GetOAuthAppInfo(app.Id); err != nil {
		t.Fatal(err)
	}
}

func TestOAuthDeleteApp(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.DeleteOAuthApp("fakeId"); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
	*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false

	app := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := Client.DeleteOAuthApp(app.Id); err != nil {
		t.Fatal(err)
	}

	app = &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := AdminClient.DeleteOAuthApp(app.Id); err != nil {
		t.Fatal(err)
	}
}
<commit_msg>fix OAuth provider when running test multiple times (#3782)<commit_after>// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.

package api

import (
	"github.com/mattermost/platform/model"
	"github.com/mattermost/platform/utils"
	"net/url"
	"testing"
)

func TestRegisterApp(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.SystemAdminClient

	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.RegisterApp(app); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	Client.Logout()

	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("not logged in - should have failed")
	}

	th.LoginSystemAdmin()

	if result, err := Client.RegisterApp(app); err != nil {
		t.Fatal(err)
	} else {
		rapp := result.Data.(*model.OAuthApp)
		if len(rapp.Id) != 26 {
			t.Fatal("clientid didn't return properly")
		}
		if len(rapp.ClientSecret) != 26 {
			t.Fatal("client secret didn't return properly")
		}
	}

	app = &model.OAuthApp{Name: "", Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing name - should have failed")
	}

	app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing homepage - should have failed")
	}

	app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{}}
	if _, err := Client.RegisterApp(app); err == nil {
		t.Fatal("missing callback url - should have failed")
	}
}

func TestAllowOAuth(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = AdminClient.Must(AdminClient.RegisterApp(app)).Data.(*model.OAuthApp)

	state := "123"

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = false
	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - oauth providing turned off")
	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	if result, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", state); err != nil {
		t.Fatal(err)
	} else {
		redirect := result.Data.(map[string]string)["redirect"]
		if len(redirect) == 0 {
			t.Fatal("redirect url should be set")
		}

		ru, _ := url.Parse(redirect)
		if ru == nil {
			t.Fatal("redirect url unparseable")
		} else {
			if len(ru.Query().Get("code")) == 0 {
				t.Fatal("authorization code not returned")
			}
			if ru.Query().Get("state") != state {
				t.Fatal("returned state doesn't match")
			}
		}
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "all", state); err == nil {
		t.Fatal("should have failed - no redirect_url given")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "", state); err == nil {
		t.Fatal("should have failed - no redirect_url given")
	}

	if result, err := Client.AllowOAuth("junk", app.Id, app.CallbackUrls[0], "all", state); err != nil {
		t.Fatal(err)
	} else {
		redirect := result.Data.(map[string]string)["redirect"]
		if len(redirect) == 0 {
			t.Fatal("redirect url should be set")
		}

		ru, _ := url.Parse(redirect)
		if ru == nil {
			t.Fatal("redirect url unparseable")
		} else {
			if ru.Query().Get("error") != "unsupported_response_type" {
				t.Fatal("wrong error returned")
			}
			if ru.Query().Get("state") != state {
				t.Fatal("returned state doesn't match")
			}
		}
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "", app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - empty client id")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "junk", app.CallbackUrls[0], "all", state); err == nil {
		t.Fatal("should have failed - bad client id")
	}

	if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "https://somewhereelse.com", "all", state); err == nil {
		t.Fatal("should have failed - redirect uri host does not match app host")
	}
}

func TestGetOAuthAppsByUser(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.GetOAuthAppsByUser(); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	if _, err := Client.GetOAuthAppsByUser(); err == nil {
		t.Fatal("Should have failed. only admin is permitted")
	}

	*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false

	if result, err := Client.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) != 0 {
			t.Fatal("incorrect number of apps should have been 0")
		}
	}

	app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if result, err := Client.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) != 1 {
			t.Fatal("incorrect number of apps should have been 1")
		}
	}

	app = &model.OAuthApp{Name: "TestApp4" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
	app = AdminClient.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if result, err := AdminClient.GetOAuthAppsByUser(); err != nil {
		t.Fatal(err)
	} else {
		apps := result.Data.([]*model.OAuthApp)

		if len(apps) < 4 {
			t.Fatal("incorrect number of apps should have been 4 or more")
		}
	}
}

func TestGetOAuthAppInfo(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.GetOAuthAppInfo("fakeId"); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true

	app := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = AdminClient.Must(AdminClient.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := Client.GetOAuthAppInfo(app.Id); err != nil {
		t.Fatal(err)
	}
}

func TestOAuthDeleteApp(t *testing.T) {
	th := Setup().InitBasic().InitSystemAdmin()
	Client := th.BasicClient
	AdminClient := th.SystemAdminClient

	if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
		if _, err := Client.DeleteOAuthApp("fakeId"); err == nil {
			t.Fatal("should have failed - oauth providing turned off")
		}

	}

	utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true
	*utils.Cfg.ServiceSettings.EnableOnlyAdminIntegrations = false

	app := &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := Client.DeleteOAuthApp(app.Id); err != nil {
		t.Fatal(err)
	}

	app = &model.OAuthApp{Name: "TestApp5" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}

	app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)

	if _, err := AdminClient.DeleteOAuthApp(app.Id); err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package debug

import (
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/ije/gox/term"
	"github.com/ije/gox/utils"
)

type Process struct {
	Sudo             bool
	Name             string
	Status           string
	Code             string
	Path             string
	Args             []string
	LinkedPkgs       []string
	LinkedFiles      []string
	TermLinePrefix   string
	TermColorManager func(b []byte) term.Color
	watchingFiles    map[string]time.Time
	*os.Process
}

func (process *Process) PName() (processName string) {
	if len(process.Path) > 0 {
		_, processName = utils.SplitByLastByte(process.Path, os.PathSeparator)
		if len(processName) == 0 {
			processName = process.Path
		}
		return
	}

	processName = strings.ToLower(process.Name)

	return
}

func (process *Process) Build() (err error) {
	process.Status = "building"
	defer func() {
		process.Status = ""
	}()

	for _, pkg := range process.LinkedPkgs {
		output, err := exec.Command("go", "install", pkg).CombinedOutput()
		if err != nil {
			return fmt.Errorf("install pkg '%s' failed: %v", pkg, string(output))
		}
	}

	if len(process.Code) > 0 {
		exePath := path.Join(tempDir, "gox.debug", process.PName())
		goFile := exePath + ".go"

		if err := ioutil.WriteFile(goFile, []byte(process.Code), 0644); err != nil {
			return fmt.Errorf("create the '%s.go' failed: %v", strings.ToLower(process.Name), err)
		}

		if build.Default.GOOS == "windows" {
			exePath += ".exe"
		}
		output, err := exec.Command("go", "build", "-o", exePath, goFile).CombinedOutput()
		if err != nil || len(output) > 0 {
			return fmt.Errorf("build process '%s' failed: %v: %s", process.Name, err, string(output))
		}
		process.Path = exePath
	}
	return
}

func (process *Process) Start() (err error) {
	if len(process.Path) == 0 {
		return fmt.Errorf("missing path")
	}

	var cmd *exec.Cmd
	if process.Sudo && build.Default.GOOS != "windows" {
		exec.Command("/bin/bash", "-c", "sudo echo 0 > /dev/null").Run()
		cmd = exec.Command("/bin/bash", "-c", strings.Join(append([]string{"sudo", process.Path}, process.Args...), " "))
	} else {
		cmd = exec.Command(process.Path, process.Args...)
	}

	termLinePrefix := process.TermLinePrefix
	if len(termLinePrefix) == 0 {
		termLinePrefix = fmt.Sprintf("[%s] ", process.Name)
	}

	cmd.Stderr = &stderr{process.TermColorManager, &term.ColorTerm{LinePrefix: termLinePrefix}}
	cmd.Stdout = &stdout{process.TermColorManager, &term.ColorTerm{LinePrefix: termLinePrefix}}

	runErr := make(chan error)
	go func() {
		process.Status = "running"
		defer func() {
			process.Status = "stop"
		}()

		runErr <- cmd.Run()
	}()

	select {
	case err = <-runErr:
		return
	case <-time.After(time.Second):
	}

	if process.Sudo && build.Default.GOOS != "windows" {
		output, err := exec.Command("pgrep", process.PName()).Output()
		if err != nil || len(output) == 0 {
			return fmt.Errorf("find child process failed: %v", err)
		}
		process.Process = nil
		for _, ps := range utils.ToLines(string(output)) {
			if pid, err := strconv.Atoi(ps); err == nil && pid > cmd.Process.Pid {
				process.Process, err = os.FindProcess(pid)
				if err != nil {
					return fmt.Errorf("find child process failed: %v", err)
				}
				break
			}
		}
		if process.Process == nil {
			return fmt.Errorf("find child process failed: %v", err)
		}
	} else {
		process.Process = cmd.Process
	}
	return
}

func (process *Process) Stop() (err error) {
	if process.Status == "running" && process.Process != nil {
		if process.Sudo && build.Default.GOOS != "windows" {
			output, err := exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo kill %d", process.Pid)).CombinedOutput()
			if err == nil && len(output) > 0 {
				err = fmt.Errorf("stop process '%s' failed: %s", process.Name, string(output))
			}
		} else {
			err = process.Kill()
		}
		if err == nil {
			process.Process = nil
			process.Status = "stop"
		}
	}
	return
}

func (process *Process) Listen() (err error) {
	err = process.Stop()
	if err != nil {
		return
	}

	err = process.Build()
	if err != nil {
		return
	}

	err = process.Start()
	if err != nil {
		return
	}

	if len(process.LinkedPkgs) == 0 && len(process.LinkedFiles) == 0 {
		return
	}

	if process.watchingFiles == nil {
		process.watchingFiles = map[string]time.Time{}
	}

	for _, pkg := range process.LinkedPkgs {
		if len(pkg) > 0 {
			process.watchingFiles[path.Join(build.Default.GOPATH, "pkg", build.Default.GOOS+"_"+build.Default.GOARCH, pkg+".a")] = time.Time{}
		}
	}
	for _, file := range process.LinkedFiles {
		if len(file) > 0 {
			process.watchingFiles[file] = time.Time{}
		}
	}

	process.watch()
	return
}

func (process *Process) watch() {
	if len(process.watchingFiles) == 0 {
		return
	}

	for path, prevModtime := range process.watchingFiles {
		fi, err := os.Stat(path)
		if err != nil && os.IsExist(err) {
			Warn.Printf("watch file '%s' failed: %v", path, err)
			continue
		}

		modtime := fi.ModTime()
		if !modtime.Equal(prevModtime) {
			process.watchingFiles[path] = modtime

			err = process.Stop()
			if err != nil {
				Warn.Print("Stop process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			err = process.Build()
			if err != nil {
				Warn.Print("Rebuild process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			err = process.Start()
			if err != nil {
				Warn.Print("Restart process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			if !prevModtime.IsZero() {
				Ok.Print("The process '" + process.Name + "' has been rebuild and restart")
			}
		}
	}

	time.AfterFunc(time.Second, process.watch)
}

func AddProcess(process *Process) error {
	if process == nil {
		return fmt.Errorf("nil process")
	}

	if len(process.Name) == 0 {
		if len(process.Path) > 0 {
			_, process.Name = utils.SplitByLastByte(process.Path, os.PathSeparator)
			if len(process.Name) == 0 {
				process.Name = process.Path
			}
		} else {
			return fmt.Errorf("missing process name")
		}
	}

	if len(process.Code) == 0 && len(process.Path) == 0 {
		return fmt.Errorf("missing process path")
	}

	processes = append(processes, process)
	return nil
}
<commit_msg>add LinkedOAs option for `Process` strcut<commit_after>package debug

import (
	"fmt"
	"go/build"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"

	"github.com/ije/gox/term"
	"github.com/ije/gox/utils"
)

type Process struct {
	Sudo             bool
	Name             string
	Status           string
	Code             string
	Path             string
	Args             []string
	LinkedOAs        []string
	LinkedPkgs       []string
	LinkedFiles      []string
	TermLinePrefix   string
	TermColorManager func(b []byte) term.Color
	watchingFiles    map[string]time.Time
	*os.Process
}

func (process *Process) PName() (processName string) {
	if len(process.Path) > 0 {
		_, processName = utils.SplitByLastByte(process.Path, os.PathSeparator)
		if len(processName) == 0 {
			processName = process.Path
		}
		return
	}

	processName = strings.ToLower(process.Name)
	return
}

func (process *Process) Build() (err error) {
	process.Status = "building"
	defer func() {
		process.Status = ""
	}()

	for _, pkg := range process.LinkedPkgs {
		output, err := exec.Command("go", "install", pkg).CombinedOutput()
		if err != nil {
			return fmt.Errorf("install pkg '%s' failed: %v", pkg, string(output))
		}
	}

	if len(process.Code) > 0 {
		exePath := path.Join(tempDir, "gox.debug", process.PName())
		goFile := exePath + ".go"

		if err := ioutil.WriteFile(goFile, []byte(process.Code), 0644); err != nil {
			return fmt.Errorf("create the '%s.go' failed: %v", strings.ToLower(process.Name), err)
		}

		if build.Default.GOOS == "windows" {
			exePath += ".exe"
		}
		output, err := exec.Command("go", "build", "-o", exePath, goFile).CombinedOutput()
		if err != nil || len(output) > 0 {
			return fmt.Errorf("build process '%s' failed: %v: %s", process.Name, err, string(output))
		}
		process.Path = exePath
	}
	return
}

func (process *Process) Start() (err error) {
	if len(process.Path) == 0 {
		return fmt.Errorf("missing path")
	}

	var cmd *exec.Cmd
	if process.Sudo && build.Default.GOOS != "windows" {
		exec.Command("/bin/bash", "-c", "sudo echo 0 > /dev/null").Run()
		cmd = exec.Command("/bin/bash", "-c", strings.Join(append([]string{"sudo", process.Path}, process.Args...), " "))
	} else {
		cmd = exec.Command(process.Path, process.Args...)
	}

	termLinePrefix := process.TermLinePrefix
	if len(termLinePrefix) == 0 {
		termLinePrefix = fmt.Sprintf("[%s] ", process.Name)
	}

	cmd.Stderr = &stderr{process.TermColorManager, &term.ColorTerm{LinePrefix: termLinePrefix}}
	cmd.Stdout = &stdout{process.TermColorManager, &term.ColorTerm{LinePrefix: termLinePrefix}}

	runErr := make(chan error)
	go func() {
		process.Status = "running"
		defer func() {
			process.Status = "stop"
		}()

		runErr <- cmd.Run()
	}()

	select {
	case err = <-runErr:
		return
	case <-time.After(time.Second):
	}

	if process.Sudo && build.Default.GOOS != "windows" {
		output, err := exec.Command("pgrep", process.PName()).Output()
		if err != nil || len(output) == 0 {
			return fmt.Errorf("find child process failed: %v", err)
		}
		process.Process = nil
		for _, ps := range utils.ToLines(string(output)) {
			if pid, err := strconv.Atoi(ps); err == nil && pid > cmd.Process.Pid {
				process.Process, err = os.FindProcess(pid)
				if err != nil {
					return fmt.Errorf("find child process failed: %v", err)
				}
				break
			}
		}
		if process.Process == nil {
			return fmt.Errorf("find child process failed: %v", err)
		}
	} else {
		process.Process = cmd.Process
	}
	return
}

func (process *Process) Stop() (err error) {
	if process.Status == "running" && process.Process != nil {
		if process.Sudo && build.Default.GOOS != "windows" {
			output, err := exec.Command("/bin/bash", "-c", fmt.Sprintf("sudo kill %d", process.Pid)).CombinedOutput()
			if err == nil && len(output) > 0 {
				err = fmt.Errorf("stop process '%s' failed: %s", process.Name, string(output))
			}
		} else {
			err = process.Kill()
		}
		if err == nil {
			process.Process = nil
			process.Status = "stop"
		}
	}
	return
}

func (process *Process) Listen() (err error) {
	err = process.Stop()
	if err != nil {
		return
	}

	err = process.Build()
	if err != nil {
		return
	}

	err = process.Start()
	if err != nil {
		return
	}

	if len(process.LinkedPkgs) == 0 && len(process.LinkedFiles) == 0 {
		return
	}

	if process.watchingFiles == nil {
		process.watchingFiles = map[string]time.Time{}
	}

	defalutOA := build.Default.GOOS + "_" + build.Default.GOARCH
	for _, pkg := range process.LinkedPkgs {
		if pkg = strings.TrimSpace(pkg); len(pkg) > 0 {
			process.watchingFiles[path.Join(build.Default.GOPATH, "pkg", defalutOA, pkg+".a")] = time.Time{}
			if len(process.LinkedOAs) > 0 {
				for _, oa := range process.LinkedOAs {
					if oa = strings.TrimSpace(oa); len(oa) > 0 && oa != defalutOA {
						process.watchingFiles[path.Join(build.Default.GOPATH, "pkg", oa, pkg+".a")] = time.Time{}
					}
				}
			}
		}
	}

	for _, file := range process.LinkedFiles {
		if file = strings.TrimSpace(file); len(file) > 0 {
			process.watchingFiles[file] = time.Time{}
		}
	}

	process.watch()
	return
}

func (process *Process) watch() {
	if len(process.watchingFiles) == 0 {
		return
	}

	for path, prevModtime := range process.watchingFiles {
		fi, err := os.Stat(path)
		if err != nil && os.IsExist(err) {
			Warn.Printf("watch file '%s' failed: %v", path, err)
			continue
		}

		modtime := fi.ModTime()
		if !modtime.Equal(prevModtime) {
			process.watchingFiles[path] = modtime

			err = process.Stop()
			if err != nil {
				Warn.Print("Stop process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			err = process.Build()
			if err != nil {
				Warn.Print("Rebuild process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			err = process.Start()
			if err != nil {
				Warn.Print("Restart process '%s' unsuccessfully: %v", process.Name, err)
				continue
			}

			if !prevModtime.IsZero() {
				Ok.Print("The process '" + process.Name + "' has been rebuild and restart")
			}
		}
	}

	time.AfterFunc(time.Second, process.watch)
}

func AddProcess(process *Process) error {
	if process == nil {
		return fmt.Errorf("nil process")
	}

	if len(process.Name) == 0 {
		if len(process.Path) > 0 {
			_, process.Name = utils.SplitByLastByte(process.Path, os.PathSeparator)
			if len(process.Name) == 0 {
				process.Name = process.Path
			}
		} else {
			return fmt.Errorf("missing process name")
		}
	}

	if len(process.Code) == 0 && len(process.Path) == 0 {
		return fmt.Errorf("missing process path")
	}

	processes = append(processes, process)
	return nil
}
<|endoftext|>


<commit_before>package bettermail

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	log_ "log"
	"net/http"
	"strings"
	"time"

	"github.com/mailgun/mailgun-go"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

var templates map[string]*Template

type MailgunConfig struct {
	Domain    string
	APIKey    string
	PublicKey string
	Recipient string
}

var config MailgunConfig

func init() {
	initConfig()
	templates = loadTemplates()

	http.HandleFunc("/hook", hookHandler)
	http.HandleFunc("/hook-test-harness", hookTestHarnessHandler)
	http.HandleFunc("/test-mail-send", testMailSendHandler)
	http.HandleFunc("/_ah/bounce", bounceHandler)
	http.HandleFunc("/test-email-thread", testEmailThreadHandler)
}

func initConfig() {
	path := "config/mailgun"
	if appengine.IsDevAppServer() {
		path = path + "-dev"
	}
	path += ".json"
	configBytes, err := ioutil.ReadFile(path)
	if err != nil {
		log_.Panicf("Could not read config from %s: %s", path, err.Error())
	}
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		log_.Panicf("Could not parse config %s: %s", configBytes, err.Error())
	}
}

type EmailThread struct {
	CommitSHA string `datastore:",noindex"`
	Subject   string `datastore:",noindex"`
	MessageID string `datastore:",noindex"`
}

func createThread(sha string, subject string, messageId string, c context.Context) {
	thread := EmailThread{
		CommitSHA: sha,
		Subject:   subject,
		MessageID: messageId,
	}
	key := datastore.NewKey(c, "EmailThread", sha, 0, nil)
	_, err := datastore.Put(c, key, &thread)
	if err != nil {
		log.Errorf(c, "Error creating thread: %s", err)
	} else {
		log.Infof(c, "Created thread: %v", thread)
	}
}

func getEmailThreadForCommit(sha string, c context.Context) *EmailThread {
	thread := new(EmailThread)
	key := datastore.NewKey(c, "EmailThread", sha, 0, nil)
	err := datastore.Get(c, key, thread)
	if err != nil {
		log.Infof(c, "No thread found for SHA = %s", sha)
		return nil
	}
	return thread
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	eventType := r.Header.Get("X-Github-Event")
	email, commits, err := handlePayload(eventType, r.Body, c)
	if err != nil {
		log.Errorf(c, "Error %s handling %s payload", err, eventType)
		http.Error(w, "Error handling payload", http.StatusInternalServerError)
		return
	}
	if email == nil {
		fmt.Fprint(w, "Unhandled event type: %s", eventType)
		log.Warningf(c, "Unhandled event type: %s", eventType)
		return
	}
	msg, id, err := sendEmail(email, c)
	if commits != nil {
		for _, commit := range commits {
			createThread(commit.SHA, email.Subject, id, c)
		}
	}
	if err != nil {
		log.Errorf(c, "Could not send mail: %s %s", err, msg)
		http.Error(w, "Could not send mail", http.StatusInternalServerError)
		return
	}
	log.Infof(c, "Sent message id=%s", id)
	fmt.Fprint(w, "OK")
}

type Email struct {
	SenderName string
	SenderUserName string
	Subject string
	HTMLBody string
	Headers map [string]string
}

func sendEmail(email* Email, c context.Context) (msg string, id string, err error) {
	httpc := urlfetch.Client(c)
	mg := mailgun.NewMailgun(
		config.Domain,
		config.APIKey,
		config.PublicKey,
	)
	mg.SetClient(httpc)
	sender := fmt.Sprintf("%s <%s@%s>", email.SenderName, email.SenderUserName, config.Domain)
	message := mg.NewMessage(
		sender,
		email.Subject,
		email.HTMLBody,
		config.Recipient,
	)
	message.SetHtml(email.HTMLBody)
	for header, value := range email.Headers {
		message.AddHeader(header, value)
	}
	msg, id, err = mg.Send(message)
	if err != nil {
		log.Errorf(c, "Failed to send message: %v, ID %v, %+v", err, id, msg)
	} else {
		log.Infof(c, "Sent message: %s", id)
	}
	return msg, id, err
}

func handlePayload(eventType string, payloadReader io.Reader, c context.Context) (*Email, []DisplayCommit, error) {
	decoder := json.NewDecoder(payloadReader)
	if eventType == "push" {
		var payload PushPayload
		err := decoder.Decode(&payload)
		if err != nil {
			return nil, nil, err
		}
		return handlePushPayload(payload, c)
	} else if eventType == "commit_comment" {
		var payload CommitCommentPayload
		err := decoder.Decode(&payload)
		if err != nil {
			return nil, nil, err
		}
		email, err := handleCommitCommentPayload(payload, c)
		return email, nil, err
	}
	return nil, nil, nil
}

func handlePushPayload(payload PushPayload, c context.Context) (*Email, []DisplayCommit, error) {
	// TODO: allow location to be customized
	location, _ := time.LoadLocation("America/Los_Angeles")

	displayCommits := make([]DisplayCommit, 0)
	for i := range payload.Commits {
		displayCommits = append(displayCommits, newDisplayCommit(&payload.Commits[i], payload.Sender, payload.Repo, location, c))
	}
	branchName := (*payload.Ref)[11:]
	branchUrl := fmt.Sprintf("https://github.com/%s/tree/%s", *payload.Repo.FullName, branchName)
	pushedDate := payload.Repo.PushedAt.In(location)
	// Last link is a link so that the GitHub Gmail extension
	// (https://github.com/muan/github-gmail) will open the diff view.
	extensionUrl := displayCommits[0].URL
	if len(displayCommits) > 1 {
		extensionUrl = *payload.Compare
	}
	var data = map[string]interface{}{
		"Payload":                  payload,
		"Commits":                  displayCommits,
		"BranchName":               branchName,
		"BranchURL":                branchUrl,
		"PushedDisplayDate":        safeFormattedDate(pushedDate.Format(DisplayDateFormat)),
		"PushedDisplayDateTooltip": pushedDate.Format(DisplayDateFullFormat),
		"ExtensionURL":             extensionUrl,
	}
	var mailHtml bytes.Buffer
	if err := templates["push"].Execute(&mailHtml, data); err != nil {
		return nil, nil, err
	}

	senderUserName := *payload.Pusher.Name
	senderName := senderUserName
	// We don't have the display name in the pusher, but usually it's one of the
	// commiters, so get it from there (without having to do any extra API
	// requests)
	for _, commit := range payload.Commits {
		if *commit.Author.Username == senderUserName {
			senderName = *commit.Author.Name
			break
		}
		if *commit.Committer.Username == senderUserName {
			senderName = *commit.Committer.Name
			break
		}
	}

	subjectCommit := displayCommits[0]
	subject := fmt.Sprintf("[%s] %s: %s", *payload.Repo.FullName, subjectCommit.ShortSHA, subjectCommit.Title)

	message := &Email{
		SenderName: senderName,
		SenderUserName: senderUserName,
		Subject:  subject,
		HTMLBody: mailHtml.String(),
	}
	return message, displayCommits, nil
}

func handleCommitCommentPayload(payload CommitCommentPayload, c context.Context) (*Email, error) {
	// TODO: allow location to be customized
	location, _ := time.LoadLocation("America/Los_Angeles")
	updatedDate := payload.Comment.UpdatedAt.In(location)

	commitSHA := *payload.Comment.CommitID
	commitShortSHA := commitSHA[:7]
	commitURL := *payload.Repo.HTMLURL + "/commit/" + commitSHA

	body := *payload.Comment.Body
	if len(body) > 0 {
		body = renderMessageMarkdown(body, payload.Repo, c)
	}

	var data = map[string]interface{}{
		"Payload":            payload,
		"Comment":            payload.Comment,
		"Sender":             payload.Sender,
		"Repo":               payload.Repo,
		"ShortSHA":           commitShortSHA,
		"Body":               body,
		"CommitURL":          commitURL,
		"UpdatedDisplayDate": safeFormattedDate(updatedDate.Format(DisplayDateFormat)),
	}

	var mailHtml bytes.Buffer
	if err := templates["commit-comment"].Execute(&mailHtml, data); err != nil {
		return nil, err
	}

	senderUserName := *payload.Sender.Login
	senderName := senderUserName

	thread := getEmailThreadForCommit(commitSHA, c)
	subject := fmt.Sprintf("[%s] %s", *payload.Repo.FullName, commitShortSHA)
	messageId := ""
	if thread != nil {
		subject = thread.Subject
		messageId = thread.MessageID
	}
	// We don't control the message ID, but hopefully subject-basic threading
	// wil work.
	subject = "Re: " + subject

	message := &Email{
		SenderName: senderName,
		SenderUserName: senderUserName,
		Subject: subject,
		HTMLBody: mailHtml.String(),
		Headers: make(map [string]string),
	}
	if len(messageId) > 0 {
		message.Headers["In-Reply-To"] = messageId
	}
	return message, nil
}

func hookTestHarnessHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		templates["hook-test-harness"].Execute(w, nil)
		return
	}
	if r.Method == "POST" {
		eventType := r.FormValue("event_type")
		payload := r.FormValue("payload")
		c := appengine.NewContext(r)

		message, _, err := handlePayload(eventType, strings.NewReader(payload), c)
		var data = map[string]interface{}{
			"EventType":  eventType,
			"Payload":    payload,
			"Message":    message,
			"MessageErr": err,
		}
		templates["hook-test-harness"].Execute(w, data)
		return
	}
	http.Error(w, "", http.StatusMethodNotAllowed)
}

func bounceHandler(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if b, err := ioutil.ReadAll(r.Body); err == nil {
		log.Warningf(c, "Bounce: %s", string(b))
	} else {
		log.Warningf(c, "Bounce: <unreadable body>")
	}
}

func testEmailThreadHandler(w http.ResponseWriter, r *http.Request) {
	if !appengine.IsDevAppServer() {
		http.Error(w, "", http.StatusMethodNotAllowed)
		return
	}
	values := r.URL.Query()
	sha, ok := values["sha"]
	if !ok || len(sha) < 1 {
		http.Error(w, "Need to specify sha param", http.StatusInternalServerError)
		return
	}
	c := appengine.NewContext(r)
	thread := getEmailThreadForCommit(sha[0], c)
	if thread == nil {
		http.Error(w, "No thread found", http.StatusInternalServerError)
	}
	fmt.Fprintf(w, "Subject: %s\n", thread.Subject)
	fmt.Fprintf(w, "MessageID: %s\n", thread.MessageID)
}

func testMailSendHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		templates["test-mail-send"].Execute(w, nil)
		return
	}
	if r.Method == "POST" {
		c := appengine.NewContext(r)
		email := &Email{
			SenderName:   r.FormValue("sender"),
			SenderUserName:   r.FormValue("sender"),
			Subject:  r.FormValue("subject"),
			HTMLBody: r.FormValue("html_body"),
		}
		_, id, err := sendEmail(email, c)
		var data = map[string]interface{}{
			"Message": email,
			"SendErr": err,
			"Id": id,
		}
		templates["test-mail-send"].Execute(w, data)
		return
	}
	http.Error(w, "", http.StatusMethodNotAllowed)
}
<commit_msg>Don't overwrite messageIDs for existing commits (#6)<commit_after>package bettermail

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	log_ "log"
	"net/http"
	"strings"
	"time"

	"github.com/mailgun/mailgun-go"

	"golang.org/x/net/context"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
	"google.golang.org/appengine/urlfetch"
)

var templates map[string]*Template

type MailgunConfig struct {
	Domain    string
	APIKey    string
	PublicKey string
	Recipient string
}

var config MailgunConfig

func init() {
	initConfig()
	templates = loadTemplates()

	http.HandleFunc("/hook", hookHandler)
	http.HandleFunc("/hook-test-harness", hookTestHarnessHandler)
	http.HandleFunc("/test-mail-send", testMailSendHandler)
	http.HandleFunc("/_ah/bounce", bounceHandler)
	http.HandleFunc("/test-email-thread", testEmailThreadHandler)
}

func initConfig() {
	path := "config/mailgun"
	if appengine.IsDevAppServer() {
		path = path + "-dev"
	}
	path += ".json"
	configBytes, err := ioutil.ReadFile(path)
	if err != nil {
		log_.Panicf("Could not read config from %s: %s", path, err.Error())
	}
	err = json.Unmarshal(configBytes, &config)
	if err != nil {
		log_.Panicf("Could not parse config %s: %s", configBytes, err.Error())
	}
}

type EmailThread struct {
	CommitSHA string `datastore:",noindex"`
	Subject   string `datastore:",noindex"`
	MessageID string `datastore:",noindex"`
}

func createThread(sha string, subject string, messageId string, c context.Context) {
	key := datastore.NewKey(c, "EmailThread", sha, 0, nil)
	thread := new(EmailThread)
	err := datastore.Get(c, key, thread)
	if err == nil {
		log.Infof(c, "Thread %s already exists for SHA = %s. Skipping.", thread.MessageID, sha)
		return
	}

	thread.CommitSHA = sha
	thread.Subject = subject
	thread.MessageID = messageId
	_, err = datastore.Put(c, key, thread)
	if err != nil {
		log.Errorf(c, "Error creating thread: %s", err)
	} else {
		log.Infof(c, "Created thread: %v", thread)
	}
}

func getEmailThreadForCommit(sha string, c context.Context) *EmailThread {
	thread := new(EmailThread)
	key := datastore.NewKey(c, "EmailThread", sha, 0, nil)
	err := datastore.Get(c, key, thread)
	if err != nil {
		log.Infof(c, "No thread found for SHA = %s", sha)
		return nil
	}
	return thread
}

func hookHandler(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	eventType := r.Header.Get("X-Github-Event")
	email, commits, err := handlePayload(eventType, r.Body, c)
	if err != nil {
		log.Errorf(c, "Error %s handling %s payload", err, eventType)
		http.Error(w, "Error handling payload", http.StatusInternalServerError)
		return
	}
	if email == nil {
		fmt.Fprint(w, "Unhandled event type: %s", eventType)
		log.Warningf(c, "Unhandled event type: %s", eventType)
		return
	}
	msg, id, err := sendEmail(email, c)
	if commits != nil {
		for _, commit := range commits {
			createThread(commit.SHA, email.Subject, id, c)
		}
	}
	if err != nil {
		log.Errorf(c, "Could not send mail: %s %s", err, msg)
		http.Error(w, "Could not send mail", http.StatusInternalServerError)
		return
	}
	log.Infof(c, "Sent message id=%s", id)
	fmt.Fprint(w, "OK")
}

type Email struct {
	SenderName string
	SenderUserName string
	Subject string
	HTMLBody string
	Headers map [string]string
}

func sendEmail(email* Email, c context.Context) (msg string, id string, err error) {
	httpc := urlfetch.Client(c)
	mg := mailgun.NewMailgun(
		config.Domain,
		config.APIKey,
		config.PublicKey,
	)
	mg.SetClient(httpc)
	sender := fmt.Sprintf("%s <%s@%s>", email.SenderName, email.SenderUserName, config.Domain)
	message := mg.NewMessage(
		sender,
		email.Subject,
		email.HTMLBody,
		config.Recipient,
	)
	message.SetHtml(email.HTMLBody)
	for header, value := range email.Headers {
		message.AddHeader(header, value)
	}
	msg, id, err = mg.Send(message)
	if err != nil {
		log.Errorf(c, "Failed to send message: %v, ID %v, %+v", err, id, msg)
	} else {
		log.Infof(c, "Sent message: %s", id)
	}
	return msg, id, err
}

func handlePayload(eventType string, payloadReader io.Reader, c context.Context) (*Email, []DisplayCommit, error) {
	decoder := json.NewDecoder(payloadReader)
	if eventType == "push" {
		var payload PushPayload
		err := decoder.Decode(&payload)
		if err != nil {
			return nil, nil, err
		}
		return handlePushPayload(payload, c)
	} else if eventType == "commit_comment" {
		var payload CommitCommentPayload
		err := decoder.Decode(&payload)
		if err != nil {
			return nil, nil, err
		}
		email, err := handleCommitCommentPayload(payload, c)
		return email, nil, err
	}
	return nil, nil, nil
}

func handlePushPayload(payload PushPayload, c context.Context) (*Email, []DisplayCommit, error) {
	// TODO: allow location to be customized
	location, _ := time.LoadLocation("America/Los_Angeles")

	displayCommits := make([]DisplayCommit, 0)
	for i := range payload.Commits {
		displayCommits = append(displayCommits, newDisplayCommit(&payload.Commits[i], payload.Sender, payload.Repo, location, c))
	}
	branchName := (*payload.Ref)[11:]
	branchUrl := fmt.Sprintf("https://github.com/%s/tree/%s", *payload.Repo.FullName, branchName)
	pushedDate := payload.Repo.PushedAt.In(location)
	// Last link is a link so that the GitHub Gmail extension
	// (https://github.com/muan/github-gmail) will open the diff view.
	extensionUrl := displayCommits[0].URL
	if len(displayCommits) > 1 {
		extensionUrl = *payload.Compare
	}
	var data = map[string]interface{}{
		"Payload":                  payload,
		"Commits":                  displayCommits,
		"BranchName":               branchName,
		"BranchURL":                branchUrl,
		"PushedDisplayDate":        safeFormattedDate(pushedDate.Format(DisplayDateFormat)),
		"PushedDisplayDateTooltip": pushedDate.Format(DisplayDateFullFormat),
		"ExtensionURL":             extensionUrl,
	}
	var mailHtml bytes.Buffer
	if err := templates["push"].Execute(&mailHtml, data); err != nil {
		return nil, nil, err
	}

	senderUserName := *payload.Pusher.Name
	senderName := senderUserName
	// We don't have the display name in the pusher, but usually it's one of the
	// commiters, so get it from there (without having to do any extra API
	// requests)
	for _, commit := range payload.Commits {
		if *commit.Author.Username == senderUserName {
			senderName = *commit.Author.Name
			break
		}
		if *commit.Committer.Username == senderUserName {
			senderName = *commit.Committer.Name
			break
		}
	}

	subjectCommit := displayCommits[0]
	subject := fmt.Sprintf("[%s] %s: %s", *payload.Repo.FullName, subjectCommit.ShortSHA, subjectCommit.Title)

	message := &Email{
		SenderName: senderName,
		SenderUserName: senderUserName,
		Subject:  subject,
		HTMLBody: mailHtml.String(),
	}
	return message, displayCommits, nil
}

func handleCommitCommentPayload(payload CommitCommentPayload, c context.Context) (*Email, error) {
	// TODO: allow location to be customized
	location, _ := time.LoadLocation("America/Los_Angeles")
	updatedDate := payload.Comment.UpdatedAt.In(location)

	commitSHA := *payload.Comment.CommitID
	commitShortSHA := commitSHA[:7]
	commitURL := *payload.Repo.HTMLURL + "/commit/" + commitSHA

	body := *payload.Comment.Body
	if len(body) > 0 {
		body = renderMessageMarkdown(body, payload.Repo, c)
	}

	var data = map[string]interface{}{
		"Payload":            payload,
		"Comment":            payload.Comment,
		"Sender":             payload.Sender,
		"Repo":               payload.Repo,
		"ShortSHA":           commitShortSHA,
		"Body":               body,
		"CommitURL":          commitURL,
		"UpdatedDisplayDate": safeFormattedDate(updatedDate.Format(DisplayDateFormat)),
	}

	var mailHtml bytes.Buffer
	if err := templates["commit-comment"].Execute(&mailHtml, data); err != nil {
		return nil, err
	}

	senderUserName := *payload.Sender.Login
	senderName := senderUserName

	thread := getEmailThreadForCommit(commitSHA, c)
	subject := fmt.Sprintf("[%s] %s", *payload.Repo.FullName, commitShortSHA)
	messageId := ""
	if thread != nil {
		subject = thread.Subject
		messageId = thread.MessageID
	}
	// We don't control the message ID, but hopefully subject-basic threading
	// wil work.
	subject = "Re: " + subject

	message := &Email{
		SenderName: senderName,
		SenderUserName: senderUserName,
		Subject: subject,
		HTMLBody: mailHtml.String(),
		Headers: make(map [string]string),
	}
	if len(messageId) > 0 {
		message.Headers["In-Reply-To"] = messageId
	}
	return message, nil
}

func hookTestHarnessHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		templates["hook-test-harness"].Execute(w, nil)
		return
	}
	if r.Method == "POST" {
		eventType := r.FormValue("event_type")
		payload := r.FormValue("payload")
		c := appengine.NewContext(r)

		message, _, err := handlePayload(eventType, strings.NewReader(payload), c)
		var data = map[string]interface{}{
			"EventType":  eventType,
			"Payload":    payload,
			"Message":    message,
			"MessageErr": err,
		}
		templates["hook-test-harness"].Execute(w, data)
		return
	}
	http.Error(w, "", http.StatusMethodNotAllowed)
}

func bounceHandler(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	if b, err := ioutil.ReadAll(r.Body); err == nil {
		log.Warningf(c, "Bounce: %s", string(b))
	} else {
		log.Warningf(c, "Bounce: <unreadable body>")
	}
}

func testEmailThreadHandler(w http.ResponseWriter, r *http.Request) {
	if !appengine.IsDevAppServer() {
		http.Error(w, "", http.StatusMethodNotAllowed)
		return
	}
	values := r.URL.Query()
	sha, ok := values["sha"]
	if !ok || len(sha) < 1 {
		http.Error(w, "Need to specify sha param", http.StatusInternalServerError)
		return
	}
	c := appengine.NewContext(r)
	thread := getEmailThreadForCommit(sha[0], c)
	if thread == nil {
		http.Error(w, "No thread found", http.StatusInternalServerError)
	}
	fmt.Fprintf(w, "Subject: %s\n", thread.Subject)
	fmt.Fprintf(w, "MessageID: %s\n", thread.MessageID)
}

func testMailSendHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		templates["test-mail-send"].Execute(w, nil)
		return
	}
	if r.Method == "POST" {
		c := appengine.NewContext(r)
		email := &Email{
			SenderName:   r.FormValue("sender"),
			SenderUserName:   r.FormValue("sender"),
			Subject:  r.FormValue("subject"),
			HTMLBody: r.FormValue("html_body"),
		}
		_, id, err := sendEmail(email, c)
		var data = map[string]interface{}{
			"Message": email,
			"SendErr": err,
			"Id": id,
		}
		templates["test-mail-send"].Execute(w, data)
		return
	}
	http.Error(w, "", http.StatusMethodNotAllowed)
}
<|endoftext|>


<commit_before>package testutil

import (
	"fmt"

	"path/filepath"

	"strings"

	"github.com/pmezard/go-difflib/difflib"
	"github.com/sridharv/fakegopath"
	"github.com/surullabs/statictest"
	"regexp"
)

type StaticCheckTest struct {
	File     string
	Content  []byte
	Checker  statictest.Checker
	Validate func(err error) error
}

func (s StaticCheckTest) Test(dir string) error {
	tmp, err := fakegopath.NewTemporaryWithFiles(dir, []fakegopath.SourceFile{
		{Src: s.File, Content: s.Content, Dest: filepath.Join(dir, "file.go")},
	})
	if err != nil {
		return fmt.Errorf("failed to create temporary go path: %v", err)
	}
	defer tmp.Reset()
	return s.Validate(s.Checker.Check(dir))
}

type Errorer interface {
	Error(args ...interface{})
}

func Test(t Errorer, pkg string, tests []StaticCheckTest) {
	for i, test := range tests {
		if err := test.Test(pkg); err != nil {
			t.Error("Check", i, err)
		}
	}
}

func NoError(err error) error { return err }

func HasSuffix(suffix string) func(err error) error {
	return func(err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error with suffix %s", suffix)
		}
		if !strings.HasSuffix(err.Error(), suffix) {
			return err
		}
		return nil
	}
}

func MatchesRegexp(re string) func (err error) error {
	return func (err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error matching RE %s", re)
		}
		if matches, matchErr := regexp.MatchString(re, err.Error()); matchErr != nil {
			return matchErr
		} else if !matches {
			return fmt.Errorf("error %v does not match re %s", err, re)
		}
		return nil
	}
}

func Contains(str string) func(err error) error {
	return func(err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error containing %s", str)
		}
		if !strings.Contains(err.Error(), str) {
			return err
		}
		return nil
	}
}

func Diff(expected, actual string) error {
	if expected == actual {
		return nil
	}
	diff := difflib.UnifiedDiff{
		A:        difflib.SplitLines(expected),
		B:        difflib.SplitLines(actual),
		FromFile: "Golden",
		ToFile:   "Actual",
		Context:  3,
	}
	text, err := difflib.GetUnifiedDiffString(diff)
	if err != nil {
		text = fmt.Sprintf("diff error: %v", err)
	}
	return fmt.Errorf(text)
}
<commit_msg>Delete unused method<commit_after>package testutil

import (
	"fmt"

	"path/filepath"

	"strings"

	"regexp"

	"github.com/sridharv/fakegopath"
	"github.com/surullabs/statictest"
)

type StaticCheckTest struct {
	File     string
	Content  []byte
	Checker  statictest.Checker
	Validate func(err error) error
}

func (s StaticCheckTest) Test(dir string) error {
	tmp, err := fakegopath.NewTemporaryWithFiles(dir, []fakegopath.SourceFile{
		{Src: s.File, Content: s.Content, Dest: filepath.Join(dir, "file.go")},
	})
	if err != nil {
		return fmt.Errorf("failed to create temporary go path: %v", err)
	}
	defer tmp.Reset()
	return s.Validate(s.Checker.Check(dir))
}

type Errorer interface {
	Error(args ...interface{})
}

func Test(t Errorer, pkg string, tests []StaticCheckTest) {
	for i, test := range tests {
		if err := test.Test(pkg); err != nil {
			t.Error("Check", i, err)
		}
	}
}

func NoError(err error) error { return err }

func HasSuffix(suffix string) func(err error) error {
	return func(err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error with suffix %s", suffix)
		}
		if !strings.HasSuffix(err.Error(), suffix) {
			return err
		}
		return nil
	}
}

func MatchesRegexp(re string) func(err error) error {
	return func(err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error matching RE %s", re)
		}
		if matches, matchErr := regexp.MatchString(re, err.Error()); matchErr != nil {
			return matchErr
		} else if !matches {
			return fmt.Errorf("error %v does not match re %s", err, re)
		}
		return nil
	}
}

func Contains(str string) func(err error) error {
	return func(err error) error {
		if err == nil {
			return fmt.Errorf("no error found when expecting error containing %s", str)
		}
		if !strings.Contains(err.Error(), str) {
			return err
		}
		return nil
	}
}
<|endoftext|>


<commit_before>/*
	Tools for implementing XMPP clients and components.

	The package is built on the concept of an XML stream - a pair of XML
	documents written to and read from a TCP connection. Top-level elements in
	the document form the messages processed by either end of the connection.

	An XML stream is then configured for an XMPP conversation, as either a
	client (chat, etc client) or component (a sort of server plugin).

	Create a client:

		jid, err := xmpp.ParseJID("alice@wonderland.lit/some-resource")
		addr, err := xmpp.HomeServerAddrs(jid)
		stream, err := xmpp.NewStream(addr[0])
		X, err := xmpp.NewClientXMPP(stream, jid, "password")

	Create a component:

		jid, err := xmpp.ParseJID("rabbithole.wonderland.lit")
		stream, err := xmpp.NewStream("localhost:5347")
		X, err := xmpp.NewComponentXMPP(stream, jid, "secret")

	Messages are sent using the XMPP.Send method, e.g. a client typically
	announces its presence on the XMPP network as soon as it's connected:

		X.Send(xmpp.Presence{})

	Incoming messages can be received in a simple loop, ended by an os.EOF for
	clean shutdown or any other error for something unexpected. XMPP defines
	four types of stanza: <error/>, <iq/>, <message/> and <presence/>
	represented by Error, Iq, Message and Presence structs respectively.

		for {
			stanza, err := X.Recv()
			if err == os.EOF {
				break
			}
			log.Printf("%T : %v\n", stanza, stanza)
		}

	Note: A "bound" JID is negotatiated during XMPP setup and may be different
	to the JID passed to the New(Client|Component)XMPP() call. Always
	use the XMPP instance's JID attribute in any stanzas.
*/
package xmpp
<commit_msg>Fix client creation example code in overview docs.<commit_after>/*
	Tools for implementing XMPP clients and components.

	The package is built on the concept of an XML stream - a pair of XML
	documents written to and read from a TCP connection. Top-level elements in
	the document form the messages processed by either end of the connection.

	An XML stream is then configured for an XMPP conversation, as either a
	client (chat, etc client) or component (a sort of server plugin).

	Create a client:

		jid, err := xmpp.ParseJID("alice@wonderland.lit/some-resource")
		addr, err := xmpp.HomeServerAddrs(jid)
		stream, err := xmpp.NewStream(addr[0])
		X, err := xmpp.NewClientXMPP(stream, jid, "password", nil)

	Create a component:

		jid, err := xmpp.ParseJID("rabbithole.wonderland.lit")
		stream, err := xmpp.NewStream("localhost:5347")
		X, err := xmpp.NewComponentXMPP(stream, jid, "secret")

	Messages are sent using the XMPP.Send method, e.g. a client typically
	announces its presence on the XMPP network as soon as it's connected:

		X.Send(xmpp.Presence{})

	Incoming messages can be received in a simple loop, ended by an os.EOF for
	clean shutdown or any other error for something unexpected. XMPP defines
	four types of stanza: <error/>, <iq/>, <message/> and <presence/>
	represented by Error, Iq, Message and Presence structs respectively.

		for {
			stanza, err := X.Recv()
			if err == os.EOF {
				break
			}
			log.Printf("%T : %v\n", stanza, stanza)
		}

	Note: A "bound" JID is negotatiated during XMPP setup and may be different
	to the JID passed to the New(Client|Component)XMPP() call. Always
	use the XMPP instance's JID attribute in any stanzas.
*/
package xmpp
<|endoftext|>